Skip to content

fix(mothership): admit stored content to Chat per item through one gate - #8941

Open
waleedlatif1 wants to merge 5 commits into
stagingfrom
fix/file-content-egress-provenance
Open

waleedlatif1 wants to merge 5 commits into
stagingfrom
fix/file-content-egress-provenance

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

Content derived from workspace files, run files, knowledge and table rows now reaches Chat's model only after each item it came from is classified and admitted by the gate of the surface it leaves through. One primitive replaces the file and row delivery observers, the hand-maintained table route list, the sandbox catalog routes and the per-surface checks.

  • Primitive (lib/core/application/content-delivery.ts): a use case describes what it returns as sources (kind, id, provenance, view: complete | {derived} | opaque) and calls admitContent / admitEach / requireAdmittedContent before loading bytes. Only Copilot principals are classified; every other caller is admitted without a provenance read.
  • Gates (lib/mothership/content-delivery/): the registry gate (Chat tools, CLI, run_code mounts) imports entries into the registry egress projects with, staged per item so one bad item never latches the shared registry. The session gate (run_code sandbox proxy) records into the workbench history. Neither ever admits unknown.
  • Structural enforcement: every operation that admits Copilot (workspace and organization) must declare contentDelivery: 'sourced' | 'none' at definition. A sourced use case that returns without accounting throws for Chat. Both transports refuse an unaccounted successful response for every HTTP method (the old CLI forward and proxy paths failed open).
  • Committed writes that return content (update_content, share update, chunk/row/document writes) keep their audit and effects when the returned item is refused or the gate fails. They then answer 403 "saved, not shown", or 502 from the proxy.
  • Tests that keep it closed: copilot-route-inventory.test.ts fails on any undeclared Chat-reachable route and publishes the 32 sourced routes to copilot-content-routes.json. content-delivery.integration.ts fails when a listed route is not driven through Chat's real transports.
  • Policy is documented in .claude/rules/sim-content-delivery.md, indexed in apps/sim/AGENTS.md.

Policy (before → after)

U = unknown, E = exact with secret entries, R/C = unrecorded or clean.

Path Before After: U After: E After: R / C
Bulk download zip (P1/P2) Leaked via run_code; CLI passed through Left out of the zip Left out (opaque) Included
files search (P1, misroute) Leaked via run_code; CLI misrouted Results dropped Results dropped (a match is an oracle) Returned; now works in Chat
File download, CLI files get Delivered, call withheld Same, kept as the one exception (D7) Redacted Delivered
File download / mounts, run_code Delivered; session poisoned (O1) Refused, not recorded, listed in excludedInputs Recorded in history Delivered
File text, version text, grep (P3) Per file; grep clipped before redaction 403 Redacted; grep redacts before clipping Delivered
File edit PATCH Unchecked oracle 403 before reading Diagnostics redacted Unchanged
Chat public share Leaked 403 403 (a link is opaque) Shares
Run file download Leaked 403 Redacted Delivered
Run read / execute / resume base64 Leaked base64 removed, descriptor kept Same Inlined (sync runs)
KB chunks, search, documents, connector docs, tags Leaked; one bad chunk withheld all (O5) Item dropped (single read: 403) Redacted Delivered
KB export, table export download Leaked / proxy leaked Not available to Chat
Table rows, query, read, table_selection Whole page withheld (O5); selection leaked Row dropped (single read: 403) Redacted Delivered
Row search Unprotected oracle Match dropped Match in a secret column dropped Returned
Table import from a file Laundered to clean rows Rows stored unknown Per-cell entries Exact-empty, as before
KB add-from-files, files read knowledge/… Refused E too (O3) Refused Accepted, entries carried Accepted
Doc compile, rendered view, streamed image Unchecked contributors; error text unredacted Refused Text redacted; images refused Delivered
tools execute with file/knowledge/table/memory/function/custom/MCP tools Results unprojected Not available to Chat
Initial attachments Whole-request throw over the entry limit Dropped with notice, as before; throw removed Same Sent

Decisions

  • D1 Chat may not share a file publicly if it is unknown or holds secret entries; human sharing is unchanged.
  • D2 Table import carries the source file's provenance into each row.
  • D3 A compile is admitted per referenced file. For a Chat compile, the isolated-VM broker only reads files the source names literally.
  • D4 An edit admits the existing text before reading it. A full replacement reads nothing.
  • D7 The CLI direct download of one unknown file is the only path where unknown bytes move: the call is withheld from the model and the workbench copy stays unknown.
  • D9 Whole-archive routes no longer admit Copilot.
  • Out of scope: the workflow executor and the workflow Mothership block.

Compatibility

  • UI sessions, API keys, OAuth, MCP and the workflow executor are byte-identical.
  • content-delivery-compat.integration.ts calls ~35 shared routes as an API key. It compares their responses against a baseline recorded on the base commit, which is the first commit of this PR.
  • Deliberate changes for every caller:
    • KB add-from-files accepts files with secret entries.
    • Table import stamps source provenance onto rows.
    • Knowledge export and table export download refuse Copilot.

Migrations

None. Every provenance shape already exists. Table import resolves provenance by storage key, so in-flight jobs are covered and there is no payload change.

Performance

  • Provenance is read only for Chat.
  • Collections classify in one batched query per page (1,000 ids per statement).
  • Non-Chat run outputs are no longer walked.
  • Known follow-ups:
    • run_code directory mounts still read one provenance row per file;
    • the staged import decrypts entries twice.

Type of Change

  • Bug fix

Testing

  • bun run type-check, bun run lint, bun run check:audits (59 audits)
  • Generated-artifact checks: openapi, cli-api, mcp-operations, cli-docs, v2-route-table, docs-manifest, block registry
  • apps/sim unit suite: 3,349 files, 37,143 tests
  • packages/sim-cli: 479 tests; scripts: 430 tests
  • All integration suites via scripts/test-integration.ts on throwaway Postgres/Redis: 1,875 + 125 tests
  • New tests:
    • content-delivery.integration.ts: 27 cases over every sourced route, each against clean, secret-bearing, unknown and unrecorded items, through the real CLI transport and sandbox proxy
    • the compat baseline suite
    • unit tests for the primitive, both gates, dispatch and run-file withholding
    • P3 grep straddle cases, which fail with redaction bypassed
  • Mobile e2e not run: no UI changes.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing (new tests pass the test-audit authoring gate)
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Oct 12, 2026 11:08pm UTC

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 197 files

Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.

Turn on auto-fix | Re-trigger cubic

Comment thread apps/sim/lib/mothership/chat/process-contents.ts
Comment thread apps/sim/lib/workflows/application/operations.ts
Comment thread apps/sim/lib/mothership/tools/handlers/function-execute.ts
Comment thread apps/sim/lib/mothership/agent-cli/file-read-transport.ts
Comment thread apps/sim/lib/workflows/application/admit-run-files.ts Outdated
Comment thread apps/sim/lib/workspace-files/application/search-workspace-file-content.ts Outdated
Comment thread apps/sim/lib/knowledge/application/tags.ts Outdated
Comment thread apps/sim/lib/table/application/rows.ts Outdated
@greptile-apps

greptile-apps Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 1/5

[High impact] Adds content-delivery gate for Chat access to stored files and knowledge.

Do not merge until knowledge paging stops revealing hidden matches and CSV imports preserve secret tracking through parsing.

Findings

  1. P1 Security Cursors reveal hidden matches ▶
  2. P1 Security CSV imports lose secret tracking ▶

Summary

The PR replaces file and row observers with one per-item gate for content returned to Chat.

  • Chat checks each stored item before it reaches a model or sandbox.
  • Imported and returned content keeps the provenance of its source.
  • Chat routes declare when they return stored content.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Chat operation] --> B[Classify each stored item]
  B --> C{Surface gate}
  C -->|CLI and tools| D[Import secrets for redaction]
  C -->|Sandbox proxy| E[Record secrets in workbench history]
  D --> F[Return admitted content]
  E --> F
  C -->|Refused| G[Drop item or refuse response]
  H[Committed write] --> B
  G --> I[Finish audit and refresh before withholding write result]
Loading

Reviews (4) · Last reviewed commit: "fix(mothership): refuse Chat table predi..." · Reviewed by Greptile

Comment thread apps/sim/lib/core/application/content-delivery.ts Outdated
Comment thread apps/sim/lib/mothership/chat/application/inline-images.ts
Comment thread apps/sim/lib/mothership/tools/sandbox-resource-transport.ts
@waleedlatif1
waleedlatif1 force-pushed the fix/file-content-egress-provenance branch from 3bb1031 to c77ebc3 Compare October 12, 2026 21:20
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 207 files

Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Turn on auto-fix | Re-trigger cubic

Comment thread apps/sim/lib/knowledge/application/chunks.ts Outdated
Comment thread apps/sim/lib/mothership/chat/process-contents.ts
Comment thread apps/sim/lib/knowledge/application/admit-knowledge-content.ts Outdated
Comment thread apps/sim/lib/mothership/tools/server/files/doc-compile.ts Outdated
Comment thread apps/sim/lib/core/application/content-delivery.ts Outdated
Comment thread apps/sim/lib/mothership/__integration__/content-delivery-compat.integration.ts Outdated
Comment thread apps/sim/lib/mothership/chat/process-contents.ts Outdated
Comment thread apps/sim/lib/mothership/chat/process-contents.ts Outdated
Comment thread .cursor/rules/sim-content-delivery.mdc Outdated
Comment thread apps/sim/lib/mothership/content-delivery/registry-gate.ts
Comment thread apps/sim/lib/workspace-files/application/share-workspace-file.ts Outdated
Comment thread apps/sim/lib/mothership/chat/application/read-sandbox-file.ts
Comment thread apps/sim/lib/knowledge/application/tags.ts
Comment thread apps/sim/lib/mothership/tool-executor/executor.ts
@waleedlatif1
waleedlatif1 force-pushed the fix/file-content-egress-provenance branch from c77ebc3 to 335f1d9 Compare October 12, 2026 22:12
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 212 files

Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Turn on auto-fix | Re-trigger cubic

Comment thread apps/sim/lib/workflows/application/admit-run-files.ts Outdated
Comment thread apps/sim/lib/mothership/chat/application/inline-images.ts
Comment thread apps/sim/lib/table/rows/secret-provenance.ts Outdated
Comment thread apps/sim/lib/table/rows/secret-provenance.ts
Comment thread apps/sim/lib/mothership/chat/process-contents.ts Outdated
Comment thread apps/sim/lib/workspace-files/application/share-workspace-file.ts
Comment thread apps/sim/lib/table/application/rows.ts
…before gating

Seeds one clean, secret-bearing, unknown and unrecorded item per content store and snapshots
every shared content route as an API-key caller sees it, before any Chat delivery gate changes.
Content from workspace files, run files, knowledge and table rows now reaches
Chat's model only after each item is classified and admitted by the gate of
the surface it leaves through: the registry gate (Chat tools, CLI, mounts) or
the session gate (run_code sandbox proxy). Unknown items are excluded per
item instead of withholding whole results; items with recorded secrets are
redacted where text, refused where opaque. Non-Chat callers are unchanged.

Every operation that admits Copilot declares contentDelivery, and a sourced
use case must account for what it returns, or Chat's transports refuse the
response. Closes the bulk-download, search, run-file, base64, knowledge,
share, export, table and tools-execute paths, and redacts grep lines before
clipping them.
Committed writes answer saved-not-shown instead of a retryable error, keep
their panel refresh, and account per operation. Chat streams pin inline
bytes off, taint needs a registry, compile admits before its cache, owned
files bind to their context, and count/truncation oracles are closed for
Chat in knowledge, files search and row search. Table selection cells are
projected before escaping and coerced import cells keep their taint.
…ent delivery

Chat may share or see links only to clean files whatever the surface, text
is redacted before a line window cuts it, accounting follows a decision,
cached compiles and knowledge metadata bind to the versions they show,
row-search matches classify at their matched revision, and table counts
follow admitted rows.
A table read whose rows, counts, cursor or affected count depend on cell
values is refused for Chat, before the predicate runs, when the table has an
unvouched row or a secret in a referenced column. Inline run-file bytes are
collected per storage key, and table-selection fallbacks are projected.
@waleedlatif1
waleedlatif1 force-pushed the fix/file-content-egress-provenance branch from 335f1d9 to 612fb47 Compare October 12, 2026 23:02
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

Comment on lines +172 to +174
...(filters.search && isCopilotPrincipal(principal)
? { pagination: { ...result.pagination, total: result.pagination.offset + chunks.length } }
: {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Cursors reveal hidden matches

listKnowledgeChunks removes refused chunks but keeps nextCursorKeys from the raw search. With search=<guess> and limit=1, two unknown chunks matching the guess produce an empty page with a cursor; no matches produce an empty page without one. Chat can use that difference to test guesses against hidden text.

listKnowledgeDocuments has the same problem with the unchanged pagination.hasMore. Rewriting total does not protect either route because their responses use the cursor fields instead. Build paging from admitted matches, or refuse searches over content Chat cannot inspect before running them.

How this was verified: The queries search stored text before admission, and the Chat-reachable routes return cursors computed from those unfiltered matches.

Comment on lines +54 to +62
for (const [columnId, raw] of Object.entries(source ?? {})) {
const stored = row[columnId]
if (stored === null || stored === undefined || Object.is(stored, raw)) continue
const rawProvenance = registry.exportCommittedProvenanceForValue(raw)
if (!rawProvenance.complete || rawProvenance.entries.length > 0) {
return createUnknownTableRowSecretProvenance()
}
}
return createTableRowSecretProvenanceFromRegistry(row, registry)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security CSV imports lose secret tracking

resolveImportedRowProvenance checks cells only after CSV parsing has already changed them. For a recorded secret such as sk_live_example123 in an unquoted CSV cell:

  • trim: true removes the spaces before both rowSources and the stored string are built.
  • Those values compare equal, so the guard skips the check.
  • The exact matcher no longer finds the original secret and stamps the row with no secret entries.

Chat can then read the trimmed secret without redaction. Carry evidence from before CSV parsing, or mark affected rows unknown when parsing changes secret text.

How this was verified: CSV parsing trims the input before rowSources is captured, while the import matcher checks the original decrypted secret literally.

This branch was successfully deployed

1 active deployment
Preview — 612fb47e Deployed Oct 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant