Repository navigation
Findings-catalogue fixes (authz, auth, gateway, SSRF, events) - #3
Merged
Merged
Conversation
Formatting only (ruff format, line-length 100), no behaviour change. Split out so the catalogue-fix commits that follow show only semantic changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes from the 2026-10-17 findings catalogue. Drafted by ChatGPT, reviewed and corrected here. Formatting-only changes are in the previous commit, so this diff is semantic. Grouped by area: authz / policy - Policy config is owned by the engine; ToolCall.config is ignored and the proxy/demo `authorization_config` inputs are gone. Right-hand comparands (allowlists, caps) come from config only, so an agent cannot pass the allowlist that judges it (AUTHZ-1). - Duplicate YAML keys are rejected rather than last-wins; scaffold flattens U+0085 (NEL), which ends a YAML comment (DCC-01). - `in` with a string on the right is an error, not substring matching. - Bundled policy v4: web_fetch limited to allowed_fetch_hosts; the proxy derives url_host from the url argument. - Live MCP sessions read the tenant's current policy on each call. auth / gateway - Repeated Authorization headers fail closed; non-ASCII bearer tokens fail authentication instead of raising; an invalid SENTINEL_API_TOKENS table (duplicate tenant or token, non-string value) fails closed for everyone. - MCP sessions are counted per tenant from initialize. A tenant at its limit is refused at initialize with a JSON-RPC error. downstream / SSRF - Deadlines on downstream connect, discovery and tool calls; a downstream exception is recorded as a withheld, tainted result. - The downstream client refuses cross-origin redirects. - web_fetch pins the validated address (Host and SNI keep the name), blocks 100.64.0.0/10 and refuses userinfo. - Catalogue fingerprint and poisoning scan cover the whole tool definition; a post-connect name collision is recorded as drift. control plane / forensics - Event cursors that expired or come from another process get 409 (SSE: resync-required); malformed Last-Event-ID is 422. - The run manager is always closed on shutdown; SQLite history is restored once and respects max_retained_runs. - Argument names in spans are fingerprinted; logged identifiers escape control characters; trace/span ids come from `secrets`; the dashboard audit view renders text, not HTML. - Local injection scanner: bounded search replaces a quadratic regex, and its flags no longer lower trust. deploy / docs - compose omits unset SENTINEL_MCP_SERVERS/SENTINEL_POLICY_FILE; Bicep runs one replica and sets SENTINEL_MCP_ALLOWED_HOST for the demo tool servers; /capabilities stops reporting Key Vault as in use; README warns that PyPI only serves 0.1.1. Corrections made in review: SENTINEL_API_TOKEN works again alongside a valid tenant table; the capacity refusal became a JSON-RPC error (a bare 503 crashed the MCP client's transport); AUTHZ-1 was still open and is closed here; stale tests were updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The capacity refusal at initialize became a JSON-RPC error (so MCP clients raise McpError instead of crashing on an HTTP error status); this test still asserted the old 503. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ream deadline configurable Review corrections on top of the catalogue fixes: * The AUTHZ-1 filter removed every right-hand variable from a call's arguments, including fields SENTINEL derives itself, so a rule like "'evil.test' in recipient_domains" denied every message. Derived fields (recipient_domain(s), url_host) are exempt; the proxy and engine now share one list of them. A rule whose comparand the config does not declare is logged by name when the policy loads. * The new 30s downstream deadline was fixed, so a tool that legitimately runs longer always failed. SENTINEL_DOWNSTREAM_TIMEOUT_SECONDS (and downstream_timeout_seconds in sentinel.yaml) sets it for serve, check and scaffold. * CHANGELOG: document the catalogue fixes, including the breaking ones (approval fingerprints change, custom runs reject allowed_domains, bundled policy v4 restricts web_fetch hosts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GHSA-84m7-p3x7-pcfv (medium, published 2026-10-15): the MCP SDK's streamable-HTTP transport let sessions accumulate without bound. Fixed in 1.30.0. pip-audit started failing CI on the 1.28.1 pin, on every branch. - pyproject floor and versions.lock: mcp 1.30.0. It installs against the existing pins with no new dependencies. - PYSEC-2026-4146 (PyJWT) exception re-checked as the CI comment requires on an mcp upgrade: mcp 1.30.0 still calls jwt.encode only. - The teardown defect is still present on 1.30.0 (its reproduction failed 3/3 with --runxfail); noted in docs/mcp-streamable-http-teardown.md. - pytest-timeout, a dev dependency, was installed unpinned; pinned 2.4.0. Verified in a fresh Python 3.11 venv from the lock: full suite 594 passed, 1 known xfail; check_versions_lock 0 conflicts; CI's pip-audit command reports no known vulnerabilities. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-origin refusal over TCP The quarantined real-MCP job started failing after the mcp 1.30.0 upgrade: test_factory_follows_the_mcp_trailing_slash_redirect asserted that a client with redirects disabled cannot complete the handshake. Since 1.30.0 the SDK sends every request with following off and follows same-origin redirects itself, ignoring the client's setting, so that client now succeeds. Dropped that step; the factory still follows the 307 (asserted). Added test_factory_refuses_a_cross_origin_redirect_over_tcp: a downstream that 307s to another origin must not get SENTINEL's POST there. With the response hook removed it fails on mcp 1.28.1 and passes on 1.30.0, so it detects a missing guard, and the hook stays as a check that does not rely on the SDK. The factory docstring now says so. All 12 diagnostics pass one per process on mcp 1.30.0, as CI runs them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 17, 2026
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.
Fixes from the 2026-10-17 findings catalogue. ChatGPT drafted them; they were reviewed and corrected here.
Commits
ruff formaton the files the fixes touch. Formatting only, so the next commit's diff is semantic."evil.test" in recipient_domainsdenied every message.SENTINEL_DOWNSTREAM_TIMEOUT_SECONDS.Breaking changes (see CHANGELOG)
catalogue_approvalsstop matching. Re-runsentinel checkand approve again.POST /runs/customand/runs/custom/baselinerejectallowed_domains(and any other unknown field) with 422.web_fetchtoallowed_fetch_hosts.configonly. A rule whose comparand isn't declared denies, and the policy load logs a warning naming it.SentinelProxy(authorization_config=)andrun_demo_session(authorization_config=)are removed.Testing
ruff check,mypy --strict src: clean.🤖 Generated with Claude Code