feat(egress): optional per-host HTTPS passthrough route allowlist (OPT-1171) - #16
Merged
Merged
Conversation
…T-1171)
The signed network policy is host-only: a host with httpsPassthrough accepts any
method and path, so a Read Only code run can send any request to any Console
route (for example POST /api/auth/requestPasswordReset).
A host may now list httpsPassthroughRoutes: exact {method, path} pairs. When
listed, the gateway's passthrough validation (openHttpsPassthrough, with the
envelope's method) allows only a listed method on a listed, already-normalized
path with no query, and refuses anything else as HOST_NOT_ALLOWED before
connecting. Paths must be canonical (no query, fragment, percent-encoding,
backslash or dot segments). Routes are part of the snapshot, so the signed digest
covers them, and the deployment policy caps them: listed on both sides, every
signed route must be in the deployment's; listed on one side, that side applies.
The field is optional and absent from today's policies, which serialize, digest
and intersect exactly as before.
Optale-ops
added a commit
that referenced
this pull request
Oct 4, 2026
…th (#17) * fix(egress): refused requests log a hash of the requested host and path Egress audit lines carried destinationHost and pathHash only after a request passed policy validation, so a HOST_NOT_ALLOWED refusal could not be traced to a destination (seen on AX41 after the #16 deploy). The gateway now records destinationHostHash (hashLabel of the lowercased host) plus pathHash and queryPresent from the requested URL right after the grant is read, for HTTPS passthrough, package transport and typed fetch. The host itself is not logged for refusals, since sandbox code chooses it freely; an operator compares the hash with hashLabel of a candidate host. * fix(egress): host hash only on records without a validated destination; tests select by route and outcome (#17 review)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OPT-1171 (Console security audit), engine half: "Read Only code can reach any Console page; limit it to the agent tool routes".
Problem
The signed
network_policyis host-only.validatePolicyUrllooks a host up by hostname, and the passthrough copies the code's method, headers and body. So a Read Only run, whose snapshot names only the Console host, can still send any method to any Console route, for examplePOST /api/auth/requestPasswordReset.Change
ExternalFetchHostPolicy/ snapshot: optionalhttpsPassthroughRoutes: [{ method, path }](1-64 entries; methodsDELETE GET HEAD PATCH POST PUT). Allowed only on a host withhttpsPassthrough. A path must already be canonical: it starts with/, contains no? # % \or whitespace, and equals its ownURLpathname, so no dot segments and no second spelling. Duplicates and extra keys are rejected. Routes are stored sorted by path, then method.validateHttpsPassthroughUrl(raw, policy, method): when the host lists routes, the uppercased method and the parsedurl.pathnamemust match one exactly, and the URL must carry no query. OtherwiseHOST_NOT_ALLOWED, thrown before any DNS lookup or connection (openHttpsPassthroughvalidates first and now passesargs.method). The gateway already rejects passthrough redirects, so the check on the requested URL is the whole check.serializeExternalFetchPolicyincludes routes, so the signed digest covers them.intersectExternalFetchPolicies: if neither side lists routes, there's no restriction (today's behaviour). If one side lists them, that side applies. If both list them, every signed route must be in the deployment's, orHOST_NOT_ALLOWED.Encoded, case-changed or trailing-slash variants of a listed path are refused: they fail the exact match. Express would route some of them to the same handler, so the direction is safe.
Backward compatibility (requested by Foundation)
The field is optional and absent from today's deployment file and from every snapshot the Console signs today. Proof:
old-new-equivalence.tsimports theaa3cb4bmodule and this branch's module side by side. It uses a deployment policy shaped like production'sexternal-fetch-policy.v2.json(R2 PDF host, four Console passthrough hosts at 300 s / 8 fetches / 2 MiB, three package registries) and today's two Console snapshot shapes (Read Only: Console passthrough only; Auto: Console, package registries, R2). It digests each snapshot the way the Console does (sha256(JSON.stringify(<sorted keys>)),canonicalizeCodeApiNetworkPolicy):cbeM2O…25sojOvrO…FIZQNfdaNM…BtEPOST /api/auth/requestPasswordResetPOST /api/optale/mcpThe same compatibility is pinned in
external-fetch-policy.test.ts("compatibility with the policies the Console signs today").Tests
external-fetch-policy.test.ts: route parsing (canonical order, digest coverage) and ten rejected shapes. Enforcement: the listed route is allowed; wrong method, query, trailing slash, case change, percent-encoding, another route, and the other listed route with the wrong method are all refused. Intersection: subset allowed, outside route refused, deployment routes applied to an unrouted signature.external-fetch-boundary.fixture.ts(real DNS/TLS in a private netns): with routes,POST /passthroughreaches the origin;GET /passthrough,POST /successandPOST /passthrough?x=1are refused and the origin sees exactly one request.Rollout (agreed with Foundation)
httpsPassthroughRoutesfor Read Only runs:GET /api/optale/composio/catalog/OPTALE_CORE/actionsandPOST /api/optale/mcp. Those are the only two sandbox→Console requests (ops/optale-agent/transport.cjs, confirmed by Agent/MCP). It must not ship before step 1: anaa3cb4bengine rejects the unknown key and would refuse every Read Only grant. The Console must emit routes sorted by path, then method, or its digest won't match.