Skip to content

security(summary): prevent post_read GET from mutating global corporate catalog #1078

Description

@seonghobae

Finding

Strix exact-head run 34746057545 on #1055 head 50c4935eef1029467595f7004818643598b737c9 completed SUCCESS and uploaded strix-reports artifact 10318318320 (digest sha256:45c82c7f1f044185a4795b4b4db37d2ce30346e44e516ce027f2bf53fe89581e). The report contains one Medium finding, CWE-862: the post_read-gated GET /api/posts/{post_id}/summary can persistently create/update the shared corporate_entity catalog when no stored summary exists.

This is independently reproducible on protected main@83eba56149eb802cd63642c507c324c9976ec78e: read_post_summary authorizes visibility through _load_visible_post, derives a missing summary, then calls persist_post_summary(...) with live _corporate_hierarchy_inference_client() and _relation_verification_client(). persist_post_summary can reach canonical get_or_create_corporate_entity, whose mutation is global catalog state rather than a read-only projection.

The same codebase already treats catalog-enriching LLM work as a write boundary on extract-keymen / customer-hint resolution (post_admin). The mismatch is therefore an authorization/integrity defect, not merely an LLM-cost concern.

Realistic RED

Add a real-service API regression using an authenticated account with post_read but no post_admin and a visible post with no persisted summary:

  1. configure summary generation so the returned role contains an organization actor not already uniquely bound in the corporate catalog;
  2. call GET /api/posts/{post_id}/summary as the post_read-only account;
  3. assert the summary read may still succeed/persist its summary projection, but the global corporate_entity table and hierarchy remain unchanged and live hierarchy/relation-verification clients are not admitted for catalog mutation;
  4. call the same path as a post_admin account and prove catalog enrichment remains available where the write permission is explicit;
  5. repeat for stale-summary fallback/provider failure to prove no privilege-dependent cleanup regression.

Do not turn the GET itself into post_admin-only unless product authority explicitly changes the read contract. Do not hide the finding by disabling summary persistence or by introducing a tenant-local duplicate catalog.

Minimal causal repair

At the read-model/application boundary, separate summary read/persistence from optional global catalog enrichment. A post_read account may consume/derive a summary, but only an account with post_admin may provide live hierarchy-inference/relation-verification capabilities that can create or modify global catalog entries. Preserve existing exact-match consumption of already-known catalog identities where it is read-only.

Prefer an explicit capability/policy argument over implicit provider availability if that makes the invariant enforceable in domain tests. The canonical catalog implementation stays in corporate_entity_ingestion; no matching SQL/provider routing is copied into main.py.

Acceptance

  • post_read-only summary GET cannot create/update/delete corporate_entity or its hierarchy;
  • post_admin summary derivation may still perform the currently intended catalog enrichment;
  • existing ABAC visibility and post_read ability to read/derive a summary are preserved;
  • summary projection persistence remains deterministic/idempotent and does not gain broader authorization;
  • executable tests cover both permission classes and prove global catalog row-count/content invariance for post_read;
  • no provider/network call capable of catalog mutation is admitted from the low-privilege path;
  • Strix finding is re-run on the repaired exact head and becomes absent/closed by code-path evidence;
  • no provider/model override, local catalog clone, broad post_admin substitution for all summary reads, warning suppression, force push, destructive rebase, or gate weakening.

Coordinate with #1077: the permission fix and the catalog connection-lease repair are separate invariants. One controls who may mutate the shared catalog; the other controls how long DB resources are retained around provider I/O.

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions