Stranger Test
Stranger Test is a free, standard-library-only Python tool and library for testing MCP (Model Context Protocol) servers for cross-identity and delegated-authority authorization leaks.
An MCP server is a trusted middleman that usually holds many users' credentials and data. The question Stranger Test asks is simple: can one identity's data, events, or authority reach another identity through the server? We organise these failures into a taxonomy of 14 XID leak classes, and build detection ahead of a class becoming common rather than after. Two of the earliest-built detectors are:
- XID-13 — cross-identity subscription/notification leak.
- XID-10 — delegation dropped or forged: the on-behalf-of / token-exchange confused-deputy problem.
Try it
See a leak caught in under a minute, with no third-party dependencies. This
starts the bundled practice MCP subscription server on a loopback port and runs
the XID-13 subscription probe against it — first in leaky mode, then in safe
mode. From the repository root, with plain system Python:
python3 -m strangertest.demo
In leaky mode, identity B hears identity A's resource update — a leak:
Subscription-leak probe (XID-13) against the bundled practice server.
B subscribes to A's resource; A changes it; did B hear about it?
[leaky] LEAK: identity 'B' received update notifications for 'note://alice/secret', a resource owned by identity 'A' — cross-identity subscription/notification leak.
[safe] OK: identity 'B' received no notifications for 'A''s resource 'note://alice/secret'.
The leaky line is the detection; the safe line is the same probe reporting a
correctly-scoped server clean.
How it works
Every probe is an A/B probe: act as identity A, observe as identity B, and check whether the line between them held.
- Positive controls. A probe first proves its observer actually works, so that "no leak" cannot be a false negative caused by a broken observer.
- An explicit
inconclusivegrade. When the probe cannot observe what it needs — an undecodable delegated token, a stream that will not open, a server too old to carry the channel — it reports inconclusive, which is distinct from no leak. It never reports a leak it could not actually see, and never calls an unobservable server clean. - Authorized-first. Every probe passes the target through an authorization guard before any connection is made (see Scope and safety below).
The three probes:
- Read probe (
strangertest.probe) — A plants a uniquely-marked record; B tries to read it back. If B sees A's marker, authority crossed identities. - Subscription probe / XID-13 (
strangertest.subscribe) — B opens a subscription stream on a resource owned by A; A then changes it. If B receives an update notification for A's resource, one identity's events reached another. Detection is exact-URI, exact-method — not a substring search — and the stream is opened and acknowledged before A acts, because the channel does not replay. - Delegation probe / XID-10 (
strangertest.delegate) — the server acts on behalf of a user against an upstream you control. The probe drives it as A, then B, then A again (the second A call catches a deputy that cached or bled B's token into A's request) and inspects what reached the upstream: subject preserved, acting agent present, audience correct, scope narrowed, a distinct token per principal, and no cross-identity bleed. Identity is(issuer, subject), so two users named the same from different providers are not conflated.
Transports (strangertest.transports), standard library only: stdio
(subprocess, newline-delimited JSON-RPC) and Streamable HTTP (JSON or SSE),
including the 2026-07-28 "modern" per-request envelope.
Install and run
Requires Python 3.9+. The core library and unit tests use the standard library only — no third-party dependencies.
Unit tests, from the repository root:
python3 -m unittest discover
(106 tests.)
Integration gates
The integration "gates" in integration/ exercise the probes end-to-end
against real servers. They need extra dependencies (pyjwt[crypto], plus the
target server itself), are not run by python3 -m unittest discover, and
skip cleanly when their dependencies are absent.
- XID-13 subscription gate — drives the subscription probe against the official MCP SDK subscriptions example server.
- XID-10 thin-deputy gate (
integration/xid10_thin_deputy_gate.py) — runs the delegation probe over a real RFC 8693 token exchange against a set of deliberately-broken deputy fixtures:static_credential,token_passthrough,cached_token_reuse, and a conflated-issuer case. Each is a known leak the probe must flag, for the right reason. - XID-10 ContextForge gate (
integration/xid10_contextforge_gate.py) — drives the delegation probe through a real IBM ContextForge gateway doing RFC 8693 token exchange. Configured byXID10_MODE(delegation|impersonation|two_issuers) andXID10_TRIGGER(tool|resource). - XID-10 agentgateway gate (
integration/xid10_agentgateway_gate.py) — drives the same delegation probe, same six checks and scenarios, through a second, unrelated third-party gateway: agentgateway v1.7.0 (the official release binary, configured by its config file only). A second gateway so the probe's behaviour is not tied to any one implementation.
The setup recipes are in
integration/CONTEXTFORGE_GATE.md and
integration/AGENTGATEWAY_GATE.md.
Everything in the gates runs on localhost only.
A sample result
Run against a real IBM ContextForge 1.1.1 gateway (macOS, Python 3.13); the same scenarios run against agentgateway v1.7.0 with the same results:
- Faithful delegation grades clean — all checks pass.
- Impersonation (no actor token) is flagged, because the exchanged token
carries no acting-agent
actclaim, so the agent acting for the user is invisible. - Two issuers — two users both named
alicefrom different providers are kept distinct through the exchange (distinct tokens, no cross-identity reuse); no conflation.
To be clear about what this does and does not show: no vulnerability was found in either gateway. The probe's ability to detect leaks is demonstrated by the deliberately-broken thin-deputy fixtures above, each of which the probe flags. A comparison against the older 1.0.6 release (to check a since-fixed historical behaviour) is noted as future work and has not yet been run.
Scope and safety
Point Stranger Test only at localhost, servers you run, or targets you are
explicitly authorized to test. The authorization guard allows local
development hosts (loopback, .localhost, .test) automatically and refuses
every other target unless you pass explicit authorization recording who
permitted the test. There is deliberately no "scan everything" mode: one target,
one authorization.
Any real finding is for private, responsible disclosure to the server's owner. Do not name-and-shame.
License
Apache-2.0 (see LICENSE).
Metadata
Release files for stranger-test 0.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| stranger_test-0.0.1.tar.gz | 55.5 kB | Without provenance | Details for stranger_test-0.0.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| stranger_test-0.0.1-py3-none-any.whl | Python 3 | none | any | Details for stranger_test-0.0.1-py3-none-any.whl |
Total release size: 107.0 kB
Release files / stranger_test-0.0.1.tar.gz
| Download URL | stranger_test-0.0.1.tar.gz |
|---|---|
| Size | 55.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7dbe2627680cc4b37ed46a314aff4018687225b5d5aa36b84b2c69f8c2a60b15
|
|
BLAKE2b-256 checksum How to use checksums |
0cf44826c42b72e2cd5902a765de46a35969e35990cd8cbf1a7f6c5a41faddab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / stranger_test-0.0.1-py3-none-any.whl
| Download URL | stranger_test-0.0.1-py3-none-any.whl |
|---|---|
| Size | 51.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2d90552e935fefe94a9f3fa4914754c68f0a2fa07936ea8782bfbe257bd1cb20
|
|
BLAKE2b-256 checksum How to use checksums |
b0038256e0e13a818984205b7078b22b1174f6a9e4d917f0d7b2319ba1387fdd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Security for stranger-test 0.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.