feat(gateway): granular resource scoping - #17
Conversation
What it actually does1,195 of the ~1,551 lines are a single new file, A connection can carry a stored granular session policy ( Mechanically it's a tightening pass applied after the engine decision, before injection:
Why it exists#15 gates who reaches a host/app, #16 gates what the request contains, #17 gates which specific resource an already-permitted connection can touch. Without it, "grant this agent GitHub, but only Reading order
What to scrutinise1. The dispatch table is a closed allowlist. match (provider, scope) {
("github-app" | "github", ResourceScope::Repositories(allowed)) => github_scope(strip_port(host), path, &allowed),
("dropbox", ResourceScope::Folders(allowed)) => dropbox_scope(host, path, input, &allowed),
_ => ScopeVerdict::Indeterminate,
}
2.
This is consistent and intentional across the whole stack, not a gateway quirk:
The consequence: there is currently no way anywhere in this product to express "deny this connection all resources" via granular scope. Deselecting everything un-restricts. Fine while every consumer knows the convention — but the schema validates
3. Fail-closed coverage is thorough — spot-check rather than re-derive.
4. The Design decisions worth questioning
Test coverage reality
But every one is a pure-function test of
Genuinely unverified:
Highest-value test to add: one that constructs a How #15, #16 and #17 compose at request time
The one path reaching credential-shaped output without passing steps 3-6 is step 1 — Reviewer orientation guide — produced by analysing this PR's diff and surrounding code, not the commit messages. Claims about line numbers and behaviour are worth spot-checking as you read; where it says something is untested or risky, that was verified against the tree rather than inferred. |
Reconciliation Stage H. The gateway enforces a connection's session_policy as a monotone tightening on the final two-level decision — GitHub repos (from the URL path) and Dropbox folders (from the buffered JSON body or the Dropbox-API-Arg header). Reuses Stage G's body buffer via an OR-composed needs_scope_body (no second buffer; the content host never buffers the file body). provider is threaded through BOTH ResolvedRules construction sites (the http-proxy path was silently dropping it). Fail-closed throughout: dot-segment path traversal, path-less content RPCs, unknown providers with a scope set, and unparseable requests all deny; uncovered providers deny while scoped. +31 tests, no agent-group, no migration.
5fa5509 to
e5fbe1f
Compare
Granular resource scoping in the gateway — restricting a credential to specific GitHub repos or Dropbox folders. 13 files, +1,551/−20 — no test files at this layer beyond
scope.rs's own unit tests.policy_engine/scope.rs::evaluate_scopedispatches on("github-app"|"github", Repositories)and("dropbox", Folders); anything else falls toIndeterminate → Blocked.Two things to know, both documented in #9's review doc:
ee_apps::has_request_guard(a hardcodedfalsehere). Adopting v1.45.0 without implementing that function silently disables everything in this PR, withcargo teststill green.scope.rs::parsemaps an empty list toNone— "empty list = all". A stored{"repositories": []}therefore means unrestricted, not deny-all. Upstream v1.45.0 adds API-side validation against new empty lists; existing rows would still need an audit.Stack
Split out of the original 381-file #8. Upstream catch-up (v1.42.0 → v1.44.0) already landed as #10, so
mainis now v1.44.0 and everything below is our own code.Review and merge in order, top to bottom. Roughly half of each diff is tests.