Surfaced by the ADR-0096 execution-surface audit. The knowledge/RAG retrieval permission filter fails open when no caller identity is supplied — returning semantic-search hits across the entire corpus, unscoped by RLS.
Detail
packages/services/service-knowledge/src/knowledge-service.ts applyPermissionFilter(hits, ctx) (:308):
if (!ctx || ctx.isSystem) return hits; // :312 — no ctx OR system ⇒ every hit passes through
When ctx is present and non-system, the function correctly re-queries each source object under the caller's context and drops hits the caller can't see (:332-342). But with ctx omitted (or system), the RLS re-scope is skipped and all hits — including sourceRecordId-backed object rows the caller may not be permitted to read — are returned.
Why it's reachable (agent angle)
The AI tool loop that drives retrieval passes identity via ToolExecutionContext.actor, which the contract declares optional with a system-level fallback:
packages/spec/src/contracts/ai-service.ts (:378,393) — "Optional… when omitted, tools fall back to system-level behaviour" / "Omit for internal/system invocations."
So an agent/chat path whose (out-of-framework, cloud-side) route does not populate actor from req.user gets unfiltered semantic search over the whole knowledge corpus — another instance of the D10 agent data ceiling being escaped (cf. #2849/#2845), this time on the read/retrieval side.
Suggested fix
- Make
applyPermissionFilter fail closed on missing identity: no ctx ⇒ drop object-source hits (the same conservative branch already used when dataEngine is unbound, :313-318), rather than returning everything. Reserve the pass-through strictly for an explicit systemContext(reason) (ADR-0096 D2), not for "context happened to be absent."
- Harden the
ToolExecutionContext contract (ai-service.ts): retrieval/data tools should require an actor, or the framework should refuse to run them system-level unless an explicit trusted-run flag is set. Coordinate with the cloud chatWithTools implementation.
Relationship to ADR-0096
Direct instance of the class — a data path that treats "no context" as "full access." Exactly the seam ADR-0096 D1/D5 (fail-closed on principal-less non-system calls) closes. Tracking under the ADR-0096 evidence base.
Refs: ADR-0096 (PR #2975), #2849, #2845 (agent data ceiling framing).
Surfaced by the ADR-0096 execution-surface audit. The knowledge/RAG retrieval permission filter fails open when no caller identity is supplied — returning semantic-search hits across the entire corpus, unscoped by RLS.
Detail
packages/services/service-knowledge/src/knowledge-service.tsapplyPermissionFilter(hits, ctx)(:308):When
ctxis present and non-system, the function correctly re-queries each source object under the caller's context and drops hits the caller can't see (:332-342). But withctxomitted (or system), the RLS re-scope is skipped and all hits — includingsourceRecordId-backed object rows the caller may not be permitted to read — are returned.Why it's reachable (agent angle)
The AI tool loop that drives retrieval passes identity via
ToolExecutionContext.actor, which the contract declares optional with a system-level fallback:packages/spec/src/contracts/ai-service.ts(:378,393) — "Optional… when omitted, tools fall back to system-level behaviour" / "Omit for internal/system invocations."So an agent/chat path whose (out-of-framework, cloud-side) route does not populate
actorfromreq.usergets unfiltered semantic search over the whole knowledge corpus — another instance of the D10 agent data ceiling being escaped (cf. #2849/#2845), this time on the read/retrieval side.Suggested fix
applyPermissionFilterfail closed on missing identity: noctx⇒ drop object-source hits (the same conservative branch already used whendataEngineis unbound,:313-318), rather than returning everything. Reserve the pass-through strictly for an explicitsystemContext(reason)(ADR-0096 D2), not for "context happened to be absent."ToolExecutionContextcontract (ai-service.ts): retrieval/data tools should require an actor, or the framework should refuse to run them system-level unless an explicit trusted-run flag is set. Coordinate with the cloudchatWithToolsimplementation.Relationship to ADR-0096
Direct instance of the class — a data path that treats "no context" as "full access." Exactly the seam ADR-0096 D1/D5 (fail-closed on principal-less non-system calls) closes. Tracking under the ADR-0096 evidence base.
Refs: ADR-0096 (PR #2975), #2849, #2845 (agent data ceiling framing).