Skip to content

security: separate Post Chat read authority from shared persistence mutation #1053

Description

@seonghobae

Protected main@83eba56149eb802cd63642c507c324c9976ec78e permits a post_read caller to invoke POST /api/posts/{post_id}/chat; on a cache miss that path runs the LLM and then mutates shared post_chat_result / post_chat_citation state. The protected implementation makes the integrity collision explicit: persist_post_chat normalizes the question, executes DELETE FROM post_chat_result WHERE post_id=$1 AND question_norm=$2, reinserts one answer under the same two-part identity, then inserts its citations. The schema makes citations cascade from that same (post_id, question_norm) result identity. No caller scope or evidence-set identity participates in the durable key.

The exact #1042 Strix artifact (34704740554, artifact digest sha256:bcdc13b76adf4a2b5b9cbbcab2de217db2e25f445f69d2de20a7d9f4cdf19b72) correctly flags the integrity/cost boundary, but remediation must preserve product semantics rather than mechanically declaring every Ask action post_admin.

#1044/#1047 is the prerequisite replay-disclosure repair and remains the single owner of generation-scope receipts, captured-source reauthorization, source-deletion invalidation, and fail-closed replay. This issue owns the remaining write-authority/identity invariant only; do not duplicate #1047 authorization SQL or replay policy.

Chosen invariant

Use scope-owned durable answers, not low-privilege mutation of one canonical shared transcript and not admin-only ephemeral compute.

  • A post_read caller may obtain a live answer and may persist it only under an immutable normalized authorization-scope identity derived from the fix(security): bind persisted chat replay to reader scope #1047 generation receipt. Persistence identity must distinguish at least corporate scope and restricted/unrestricted process scope; it must not infer privilege from the HTTP verb.
  • Durable answer identity must also bind normalized question and captured evidence-set identity/digest. A low-scope answer therefore cannot DELETE/replace a broader/different scope's persisted answer merely because focal post + normalized question match.
  • Replay still passes through fix(security): bind persisted chat replay to reader scope #1047's current-reader/subsumption and captured-source visibility checks. Scope-owned identity prevents integrity collisions; fix(security): bind persisted chat replay to reader scope #1047 remains disclosure authority.
  • If product later needs one promoted/canonical answer across scopes, that promotion is a separate explicit elevated capability/domain event. Do not overload ordinary post_read persistence with that authority.
  • Replace delete-then-insert with deterministic insert/upsert/version semantics whose conflict target includes the scope-owned identity. Preserve prior immutable/provenance evidence rather than deleting another scope's result.
  • LLM/retrieval/embedding work runs outside every database transaction/explicit lock. After generation, start only a short write transaction, revalidate the fix(security): bind persisted chat replay to reader scope #1047 authorization/evidence receipt, then persist idempotently. Concurrent source/scope change must fail closed without a partially promoted answer.
  • Admission/rate/cost control stays with the product admission owner and contextual-orchestrator contract; no provider/model override or local paid fallback.

Realistic RED required before implementation

On the protected/main-compatible prerequisite tree, prove with authenticated HTTP + PostgreSQL that two callers with different valid scopes asking the same normalized question currently target the same (post_id, question_norm) result row and that the later low-privilege request can replace/delete durable result/citation state belonging to the other scope. Assert the concrete row identity and citation cascade, not only HTTP payload differences; the current main implementation already provides the deterministic delete/reinsert mechanism to exercise.

Also cover concurrent same-scope writes: two identical authorized requests must converge idempotently rather than delete/recreate or produce ambiguous winners. A scope/evidence change between generation and persistence must reject the write after short-transaction revalidation.

Integration order / acceptance

Do not open a competing source writer while #1055 is the active Customer Master repair lane. After #1047 is normally integrated (or a verified successor carries all valid receipt/source-evidence deltas), implement this issue as a separate Post Chat persistence lane and non-force converge it onto the then-protected base.

Acceptance requires the realistic RED above, normalized/versioned schema and rollback, application/repository contract, authenticated reader-vs-reader and any elevated-promotion HTTP/E2E, concurrent-write/idempotency tests, no long-lived transaction around LLM work, exact-head PostgreSQL/full suite, Security/SAST/Required CodeQL/Strix/model review, qualifying independent current-head approval, and normal protected integration.

No broad post_admin substitution, replay-policy copy, cross-service SQL, mutable dependency, self-approval, force push, destructive rebase, no-op validation churn, or gate weakening.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions