Skip to content

security(streaming): applyStreamPolicy passes non-EventMessage / empty table_name payloads through unfiltered (fail-open) #323

Description

@EricAndrechek

Area: api · observability (streaming) — security (defense-in-depth, fail-open) · found via pre-launch audit

Expected: every message on the stream path is policy-filtered, including malformed or edge payloads (fail closed).

Actual: applyStreamPolicy passes a non-EventMessage or empty-table_name JSON payload through unfiltered (no policy applied). Not publicly reachable on Stats today, but a fail-open default: an unexpected payload shape skips filtering instead of failing closed.

Impact: low (the broadcast source is internal), but the wrong default direction for a policy gate — a future bug or new producer that emits an off-shape payload would bypass filtering silently.

Scope: fail closed on un-typed / empty-table_name payloads.

Related: #294 (SSE delivery path), the SSE row-filter drift issue.


From WaveHouse-Stats pre-launch security audit (WAVEHOUSE-FEEDBACK.md dogfooding), audited dev 60fed15 (2026-06-10). Filed via /pm-triage.

Activity

  1. added
    area/observabilityMetrics, logs, traces, health, profiling
    area/apiHTTP handlers, routing, middleware
    securitySecurity-sensitive issue or fix
    on Jun 10, 2026
  2. coderabbitai commented on Jun 10, 2026

    @coderabbitai
    🔗 Related PRs

    #124 - chore(api)!: drop hub wildcard fan-out; SSE/WS use ?table= (closes #100) [merged]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  3. added
    area/policyAccess control policies (Hasura-style)
    bugSomething isn't working
    on Jun 10, 2026
  4. EricAndrechek commented on Aug 12, 2026

    @EricAndrechek
    MemberAuthor

    Status from the #457 review — this is fixed; only a test is missing.

    The function named here, applyStreamPolicy, no longer exists. It was dissolved into project() by ccd7549 ("perf(stream): project SSE frames once per role, not per subscriber", #353, 2026-07-06). The fail-closed default came along as a side effect of moving the decode out of the per-subscriber loop, which is why nothing linked it back here.

    Both conditions in this issue's title collapse into one flag at internal/stream/hub.go:160:

    decoded := json.Unmarshal(raw, &evt) == nil && evt.TableName != ""

    !decoded then routes into the guard at :180-189, which returns nil, false whenever a policy store is wired. Both call sites go through it — Broadcast (live, :129) and ReplayFrame (replay, :162). Verified by execution:

    empty table_name, policy wired     -> dropped (fail closed)
    missing table_name, policy wired   -> dropped (fail closed)
    non-EventMessage, policy wired     -> dropped (fail closed)
    empty table_name, NO policy wired  -> DELIVERED   (intended: no policy to apply)
    

    The trigger is also unreachable today by construction: the only producer rejects an empty table with 400 missing table (internal/api/ingest.go:128) before publishing, and the embedded NATS server has no network listener at all (internal/mq/embedded.go:60, DontListen: true) — there is no external-NATS mode, so no second producer can exist.

    Remaining work: hub_test.go:188 pins only the non-EventMessage half. The empty-table_name half rests entirely on the seven characters && evt.TableName != ""; delete them and half of this issue silently returns with the suite green. #457 adds that regression test and will close this issue on merge.

  5. moved this from Backlog to In progress in WaveHouse Task Boardon Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/apiHTTP handlers, routing, middlewarearea/observabilityMetrics, logs, traces, health, profilingarea/policyAccess control policies (Hasura-style)area/streamingSSE / live-query delivery path (/v1/stream)bugSomething isn't workingsecuritySecurity-sensitive issue or fix

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions