doc/plans/2026-06-03-low-trust-review-contract.md
Date: 2026-06-03 Issue: PAP-10217 Status: Proposed contract for CTO approval
Lock the Phase 1 security contract for a low_trust_review execution preset before implementation starts.
This contract assumes the reviewer may process hostile PRs, diffs, comments, attachments, and generated output. Treat that content as untrusted prompt input and untrusted data.
low_trust_review must be deny-by-default and issue-scoped.issues.execution_policy JSONB, but the shape must be app-typed and validator-locked, not free-form JSON.OWASP LLM01 Prompt Injection: hostile PR/comment output tries to steer the reviewer into tool abuse.OWASP API BOLA / Broken Function-Level Authorization: same-company agent reads or mutates resources outside its assigned review issue.OWASP A01 Broken Access Control: reviewer reaches agent config, attachments, or runtime controls it does not need.OWASP A10 SSRF / outbound abuse: plugin or runtime surfaces create network reach.STRIDE Information Disclosure: secrets/config/artifacts leak through issue reads, GET /agents/me, plugin secret resolution, or attachments.STRIDE Elevation of Privilege: reviewer uses plugin tools, runtime service controls, or wake/recovery APIs to act as a more trusted worker.OWASP LLM02 Insecure Output Handling: low-trust raw output is copied into higher-trust comments, wake payloads, or summaries.The current product default is company-wide visibility for agents. That is correct for normal trusted workers, but it is too broad for a hostile-content review preset.
Relevant current behavior:
server/src/routes/issues.ts
GET /issues/:id, /heartbeat-context, /comments, /documents, /work-products, /attachments, and /attachments/:id/content after assertCompanyAccess.assertAgentIssueMutationAllowed, which protects against mutating another agent's active issue but does not narrow reads to a review issue.server/src/routes/agents.ts
GET /agents/me returns full agent detail, including raw adapterConfig and runtimeConfig, while other config routes are access-gated or redacted.server/src/routes/workspace-runtime-service-authz.ts
server/src/routes/plugins.ts and server/src/services/plugin-capability-validator.ts
server/src/services/plugin-secrets-handler.ts
secrets.read-ref.server/src/services/issue-continuation-summary.ts
server/src/services/recovery/* and server/src/__tests__/heartbeat-process-recovery.test.ts
low_trust_review| API area | Allowed in low_trust_review | Denied in low_trust_review | Why |
|---|---|---|---|
| Assigned issue core | Read assigned issue title, description, status, parent identifier/title, goal/project labels, own checkout state | Read arbitrary other issues, subtree reads, blocker traversal beyond minimal identifiers, company issue search/list outside assignee inbox entry | Least Privilege, Complete Mediation |
| Issue comments | Read and create comments on the assigned review issue only | Comment on other issues; use reopen, resume, or interrupt; create comments that auto-flow into higher-trust wake context | Prevent tampering and prompt-injection pivot |
| Issue documents | Read/write documents on the assigned review issue only | Read/write documents on any other issue; lock/unlock/delete system docs; alter plans on higher-trust issues | Keep outputs local to quarantine boundary |
| Work products | Create/update/list work products on the assigned review issue only | Cross-issue work products; provider actions outside the review issue | Same trust boundary as documents |
| Attachments | Upload/list attachments on the assigned review issue only | Read attachment bytes from unrelated issues; browse company attachment inventory | Information disclosure risk |
| Issue status | Move assigned review issue between todo, in_progress, in_review, done, blocked within preset rules | Change assignee, blockers, execution policy, approvals, recovery actions, or other issues' status | Prevent authority expansion |
| Agent identity | Read redacted self identity only: id, name, role, companyId | Raw adapterConfig, runtimeConfig, session info, instructions paths, env bindings, config revisions | GET /agents/me is a current must-block leak |
| Other agents / org | Optional read-only labels needed for mention rendering | Agent configuration routes, session routes, skill sync, agent wake/invoke, pause/resume | Avoid lateral movement |
| Plugins | None in Phase 1 | Plugin tools, plugin bridge routes, plugin state, DB namespace, local folders, outbound HTTP, webhooks, jobs, secret refs | Plugin capability model is too broad for low trust |
| Secrets / env | None | Secret routes, provider health, plugin secret resolution, direct secret-ref materialization, env/lease introspection | Secrets are outside review scope |
| Runtime services | None | Start/stop/restart runtime services, environment probes, lease operations, execution workspace runtime control | Prevent runtime pivot and SSRF |
| Recovery / watchdog | None except passive viewing of the review issue's own status notices | Resolve recovery actions, interrupt active runs, schedule monitors, wake other agents | Too much control-plane authority |
| Interactions / approvals / child tasks | None in Phase 1 | Create approvals, interactions, child issues, blocker graphs | Reviewer should report findings, not orchestrate the company |
Raw output from a low_trust_review run is quarantined and must not be injected into a higher-trust agent's heartbeat context automatically.
Only a sanitized derivative may cross from low trust to higher trust:
pass, fail, needs_human_reviewWhen a higher-trust agent is woken on a related issue, its wake payload and heartbeat-context may include:
They may not include:
This blocks the most likely confused-deputy chain:
These surfaces must be explicitly denied or specially filtered for low_trust_review:
GET /api/agents/me
Reason: current route returns raw adapterConfig and runtimeConfig.
Same-company issue read fanout
Surfaces: GET /api/issues/:id, /comments, /documents, /work-products, /attachments, /attachments/:id/content
Reason: current reads are company-scoped, not review-issue-scoped.
Plugin execution and bridge surfaces
Surfaces: /api/plugins/tools, /api/plugins/tools/execute, plugin bridge/state/DB/local-folder capabilities
Reason: secrets, HTTP, DB, filesystem, and tool pivot risk.
Secret resolution
Surfaces: secret routes plus plugin secrets.read-ref
Reason: plaintext secret disclosure is catastrophic and unrelated to review scope.
Runtime service management Surfaces: workspace/project runtime control and environment lease/probe operations Reason: lets hostile content turn a reviewer into an infrastructure operator.
Recovery and wake control Surfaces: recovery resolution, active-run interruption, wake/monitor creation, status-changing comment flags Reason: control-plane tampering and DoS.
Phase 1 should store the preset in issues.execution_policy JSONB.
Do not store an open-ended policy blob. Extend the app-typed policy schema with a locked shape such as:
reviewPreset: {
id: "low_trust_review";
version: 1;
rawOutputDisposition: "quarantine";
}
and keep the actual allow/deny matrix in server code, not user-editable JSON.
Add typed columns only if one of these becomes a real requirement:
GET /agents/me payload.low_trust_review beats plugin capability allowlists.low_trust_review enforcement layer for route access and mutation filtering.GET /agents/me does not expose raw config under the preset.