Skip to content

Source-code protection: gate raw source (thinking trace + citation fragments) behind source:view (#66) - #67

Merged
gangtao merged 9 commits into
mainfrom
feat/source-code-protection
Aug 20, 2026
Merged

Source-code protection: gate raw source (thinking trace + citation fragments) behind source:view (#66)#67
gangtao merged 9 commits into
mainfrom
feat/source-code-protection

Conversation

@gangtao

@gangtao gangtao commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Closes #66.

What & why

The agent ingests private source and can read it to answer questions — but a user could prompt it to dump whole files, turning the Q&A surface into a source-download channel. This gates the two channels that expose raw source to a web user, while leaving read_source tool access and model reasoning unrestricted (reading code to answer is fine).

Design decisions (from the user)

  • Answer channel: prompt-only, always on. No code enforcement/redaction — a system-prompt rule tells the model to explain and quote minimally and to decline full-file dumps.
  • Thinking trace + citation fragments: one default-deny role capability, source:view.

The three source channels, and how each is handled

  1. Answer (token/done) → system-prompt rule (agent.py), applies to everyone including admin.
  2. Thinking trace (thinking SSE) → withheld unless the caller has source:view (admin always sees it).
  3. Citation fragment bodiesGET /api/graph/source (the only endpoint returning raw lines) now requires source:view. Reference rows (file:line) stay visible to all chat users; only the body is gated.

Changes

  • auth.py — new CAP_SOURCE_VIEW = "source:view", default-deny (absent from DEFAULT_CAPABILITIES, held by admin via ALL_CAPABILITIES).
  • graph_api.py/api/graph/source requires source:view; other graph routes unchanged.
  • server.py/chat computes can_view_source from the already-resolved role (fail-closed) and gates the thinking emit.
  • agent.py — system-prompt rule 7: never reproduce complete/near-complete files; decline dump requests.
  • websource:view in the role editor (capabilities.ts), canViewSource threaded App → Chat → SourceCard; citation preview shows a restricted note (and skips the fetch) without the cap.

Migration / behavior change

Default-deny: existing non-admin roles lose the thinking panel and citation previews until an admin grants source:view. Admin is unaffected.

Testing

  • Backend suite: 239 passed, 10 skipped (skips are proton-backend parametrized tests needing a second instance).
  • New coverage: capability registered + default-deny; /api/graph/source → 403 without the cap / 200 with it / 200 admin; thinking events suppressed without the cap and present with it (and for admin); system-prompt rule asserted.
  • Frontend tsc -b && vite build clean.
  • Deferred to review: a manual browser pass (admin sees thinking + role-editor label; non-admin sees the restricted note) — needs a running app + seeded roles; the enforcement itself is server-side and unit-tested.

Notes for reviewers

  • The /source route replaces CAP_EXPLORE with CAP_SOURCE_VIEW (the spec said "in addition to"); require_cap checks a single cap, and the net effect is stronger protection. Flagged in the final review as a non-blocking wording drift.
  • mcp_server.py's read_source remains unguarded by design — it's the stdio MCP server for local IDE use with no auth layer, outside this feature's boundary.

🤖 Generated with Claude Code

gangtao and others added 9 commits August 20, 2026 09:37
Gate raw-source output to users while leaving read_source tool access
and model reasoning unrestricted. One new default-deny capability
(source:view) governs the thinking trace and citation-fragment bodies;
the answer channel is protected by system-prompt guidance only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPzYZXxyTdj5G25KoujpHb
Six tasks: source:view capability, /api/graph/source gate, thinking-event
suppression, system-prompt rule, frontend gating, full-suite verification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPzYZXxyTdj5G25KoujpHb
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…66)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rs at the API (#66)

A user without source:view still saw the reasoning trace and clickable
citations: the client re-labels between-tool narration as 'thinking' steps,
and the source/tool references were only hidden in the UI while still
streaming over the SSE wire.

Make the /chat stream answer-only for users without source:view: gate the
tool, tool_result, and source events (and empty the done event's sources
list) on can_view_source, alongside the existing thinking gate. Only
token/done/error reach a restricted user. Audit accumulators are still
built and logged server-side. Client renderTrace + Sources panel are gated
on canViewSource as defense-in-depth. Spec amended to the answer-only
decision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPzYZXxyTdj5G25KoujpHb
…, drop dead code (#66)

Review of PR #67 surfaced four items:

1. (real) token narration leak: between-tool narration streams as `token`
   and, because tool events are suppressed for restricted users, the client
   never clears it — it accumulates and renders on the error path, and it
   crosses the wire regardless. Gate `token` too: for users without
   source:view the /chat stream is answer-only (done/error), and the answer
   is delivered via the done event (built from full/final_text).
2. (doc) /source requires only source:view: this is correct, not drift —
   /source backs the chat citation preview, so a source:view chat user
   without explore must reach it. Spec wording corrected to say so.
3. (cleanup) can_view_source reimplemented effective_capabilities by hand;
   now reuses auth.effective_capabilities(user, role) as the single source
   of truth (also drops the second capability-expansion copy).
4. (cleanup) SourceCard's canViewSource restricted path was unreachable
   (parent gates the panel); removed the dead prop/state/branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XPzYZXxyTdj5G25KoujpHb
@gangtao
gangtao merged commit 6feecb2 into main Aug 20, 2026
2 checks passed
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.

Source-code protection: agent may read code to answer, but must not return complete source to the user

1 participant