Skip to content

Commit 64c48be

Browse files
atergaclaude
andauthored
Gate every canister-reaching tool on the discoverability manifest (#184)
* Gate every canister-reaching tool on the discoverability manifest Reads were open on any canister; only writes were gated. They now run the same gate: get_canister_candid, get_canister_api_doc, get_canister_oql_schema and canister_query reach a canister only when the app that owns it declares that canister at /.well-known/ic-architecture, exactly as canister_update_call already did. The arguments are the ones canister_update_call takes. Each read grows an optional `app_url` naming the app whose manifest decides, falling back to `derivation_origin` when the app serves its manifest there; where a call carries both, the identity binding holds them to the same app, which matters for an authenticated read as much as for a write, since a read runs as the user's principal at whatever app they named. The two anonymous metadata reads (get_canister_candid, get_canister_api_doc) accept `derivation_origin` as an origin fallback only, and skip the binding because they sign as nobody. Every successful reply echoes `declared_by`/`declared_at`. One helper, authorize_canister_call, now performs the whole check for all five tools, so a read and a write cannot drift into checking different things; canister_update_call moves onto it and loses its inline copy. Refusals carry a CallKind so each names the operation actually attempted — an agent told its read was refused as "a state-changing call" relays something false — while the rule sentence stays kind-neutral, since a caller who hits it with one operation should be told the whole policy. The sentence promising that reading is unaffected is gone (it would now be false); what keeps a refusal from reading as "this app is off limits" is naming what still works: open_app resolves the app and lists what it declares. Discovery pays for this. open_app now probes only declared canisters for their oql/api-doc flags, because probing an undeclared one would hand back as a capability flag exactly what get_canister_candid would refuse to return; a candidate mined from /env.json or the JS bundle keeps null flags and is marked by the absence of [declared] in the listing. A manifest is now the only route from an app to a usable canister. That cost, and the fact that gating reads is stricter than the protocol asks for, are stated in the module's scope notes rather than left for a reader to discover. There is no manifest cache, so the fetch is paid per call: a read now carries a DNS resolve, a TLS handshake and two HTTP GETs ahead of the boundary-node call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015pKeV5VZvkfEgFR8RX9Niy * Build the [declared] marker from the gate's own parse, and carry app_url into the OQL examples Two findings from review, both real. The `[declared]` marker read the `ic-architecture` provenance, which is not the authorization set. The two manifest parsers deliberately disagree: `canisters_from_app_manifest` keeps an id as spelled, drops blanks BEFORE the 100-entry cap, and never checks what kind of principal it is, while `manifest_canister_ids` caps first, parses, and keeps only canister principals. So open_app could mark — and capability-probe — an id every gated tool then refuses: `aaaaa-aa`, the anonymous principal, or a real id sitting past the cap behind blank entries. The marker now comes from the gate's own parse of the same document, carried on `Found`/ `DiscoveredCanister` as `declared` and surfaced in the structured output, so it cannot over-claim. A test runs both parsers over one body that exercises both divergences. The OQL schema reply's ready-to-run examples carried `derivation_origin` and `account` but not the `app_url` that authorized the schema read, so copying one fell back to checking the manifest at the derivation origin — which fails for the 13 of 17 registry apps whose website and derivation origin differ, exactly the case the fallback was meant to cover. The examples now carry both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015pKeV5VZvkfEgFR8RX9Niy --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent d33476f commit 64c48be

5 files changed

Lines changed: 992 additions & 299 deletions

File tree

0 commit comments

Comments
 (0)