Skip to content

Findings-catalogue fixes (authz, auth, gateway, SSRF, events) - #3

Merged
Harshith029 merged 6 commits into
mainfrom
catalogue-split
Oct 18, 2026
Merged

Harshith029 merged 6 commits into
mainfrom
catalogue-split

Conversation

@Harshith029

Copy link
Copy Markdown
Owner

Fixes from the 2026-10-17 findings catalogue. ChatGPT drafted them; they were reviewed and corrected here.

Commits

  1. style: ruff format on the files the fixes touch. Formatting only, so the next commit's diff is semantic.
  2. fix: findings-catalogue fixes: grouped by area in the commit message (authz/policy, auth/gateway, downstream/SSRF, control plane/forensics, deploy/docs).
  3. test(gateway): a test still expected the old HTTP 503 capacity refusal.
  4. fix(authz,downstream): two problems found in review.
    • The AUTHZ-1 filter also dropped fields SENTINEL derives itself, so "evil.test" in recipient_domains denied every message.
    • The new 30s downstream deadline couldn't be changed. It is now SENTINEL_DOWNSTREAM_TIMEOUT_SECONDS.

Breaking changes (see CHANGELOG)

  • Catalogue fingerprints now cover the whole tool definition, so existing catalogue_approvals stop matching. Re-run sentinel check and approve again.
  • POST /runs/custom and /runs/custom/baseline reject allowed_domains (and any other unknown field) with 422.
  • Bundled policy v4 limits web_fetch to allowed_fetch_hosts.
  • Right-hand comparands come from config only. A rule whose comparand isn't declared denies, and the policy load logs a warning naming it.
  • SentinelProxy(authorization_config=) and run_demo_session(authorization_config=) are removed.

Testing

  • Windows, Python 3.12: full suite 594 passed, 11 skipped, 1 xfailed (the known strict-xfail teardown wedge), on two consecutive runs. Three earlier runs each had one deterministic failure, the stale 503 test fixed in commit 3. None of the five runs hung.
  • Each new test in commit 4 fails on the code before it.
  • ruff check, mypy --strict src: clean.

🤖 Generated with Claude Code

Harshith029 and others added 6 commits October 17, 2026 22:55
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>
@Harshith029
Harshith029 merged commit a645604 into main Oct 18, 2026
7 checks passed
@Harshith029
Harshith029 deleted the catalogue-split branch October 18, 2026 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant