x190 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- x190-0.8.0/LICENSE +21 -0
- x190-0.8.0/PKG-INFO +208 -0
- x190-0.8.0/README.md +179 -0
- x190-0.8.0/X190.py +459 -0
- x190-0.8.0/pyproject.toml +46 -0
- x190-0.8.0/setup.cfg +4 -0
- x190-0.8.0/tests/test_X190.py +816 -0
- x190-0.8.0/x190.egg-info/PKG-INFO +208 -0
- x190-0.8.0/x190.egg-info/SOURCES.txt +10 -0
- x190-0.8.0/x190.egg-info/dependency_links.txt +1 -0
- x190-0.8.0/x190.egg-info/entry_points.txt +2 -0
- x190-0.8.0/x190.egg-info/top_level.txt +1 -0
x190-0.8.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 unempyd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
x190-0.8.0/PKG-INFO
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: x190
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Does this MCP endpoint serve its tool list to an unauthenticated caller? A runtime probe that emits a signed receipt of what it observed.
|
|
5
|
+
Author: unempyd
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Source, https://github.com/unempyd/X190
|
|
8
|
+
Project-URL: Issues, https://github.com/unempyd/X190/issues
|
|
9
|
+
Project-URL: Evidence, https://github.com/unempyd/X190/blob/main/EVIDENCE.md
|
|
10
|
+
Keywords: mcp,model-context-protocol,oauth,rfc9728,security,probe
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Security
|
|
24
|
+
Classifier: Topic :: Software Development :: Testing
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# X190
|
|
31
|
+
|
|
32
|
+
Does this MCP endpoint enforce authentication?
|
|
33
|
+
|
|
34
|
+
That is the whole tool. It sends `initialize`, replays `tools/list` with no
|
|
35
|
+
token, and records what came back. If the server hands its tool list to an
|
|
36
|
+
unauthenticated caller, that is the finding. If it refuses, the refusal is
|
|
37
|
+
checked for an RFC 9728 challenge a client can actually follow.
|
|
38
|
+
|
|
39
|
+
Single file, Python 3 stdlib only, zero dependencies.
|
|
40
|
+
|
|
41
|
+
## Why only that
|
|
42
|
+
|
|
43
|
+
We probed the 74 HTTP endpoints among the first 100 servers in the official MCP
|
|
44
|
+
registry on 2026-09-10. Every fault observed was auth absence (F1) or a broken
|
|
45
|
+
OAuth resource-server posture (F2). Nothing else was observed, so nothing else
|
|
46
|
+
is checked.
|
|
47
|
+
|
|
48
|
+
The headline rate from that run is **suspended pending a re-measurement**: the
|
|
49
|
+
probe of the day treated HTTP 200 as proof the tool list was served, and
|
|
50
|
+
JSON-RPC routinely refuses *inside* a 200. The probe now requires a JSON-RPC
|
|
51
|
+
`result` echoing the request id before it will claim anything. What survives unqualified is that 25 endpoints
|
|
52
|
+
refused and published resolvable RFC 9728 metadata, 4 refused with a broken or
|
|
53
|
+
absent challenge, and one advertised OAuth correctly and then served its tools
|
|
54
|
+
without a token anyway. See [EVIDENCE.md](EVIDENCE.md).
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
F1 auth-absence tools/list returned a JSON-RPC result with no token
|
|
58
|
+
F2 incomplete-oauth-rs refused, but with no RFC 9728 challenge a client could
|
|
59
|
+
follow, or with metadata that does not resolve
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
python3 X190.py check https://host/mcp # probe -> signed receipt
|
|
66
|
+
python3 X190.py check http://127.0.0.1:3000/mcp
|
|
67
|
+
python3 X190.py verify-receipt x.receipt.json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Exit codes: `0` = pass, or inconclusive only; `1` = at least one fault;
|
|
71
|
+
`2` = the target is not an http(s) URL.
|
|
72
|
+
|
|
73
|
+
### Servers distributed for stdio
|
|
74
|
+
|
|
75
|
+
A package that declares stdio transport can still ship an HTTP mode. In our
|
|
76
|
+
measurement of the registry's local-transport population, 3 of 23 npm packages
|
|
77
|
+
shipped a network-listening MCP surface, and all three could serve MCP without
|
|
78
|
+
authentication in a documented configuration — while the registry listed no
|
|
79
|
+
endpoint to probe.
|
|
80
|
+
|
|
81
|
+
If you ship or run such a server, start its HTTP mode and probe that URL like
|
|
82
|
+
any other. The fault is the same fault; only the discovery is different.
|
|
83
|
+
|
|
84
|
+
## Receipts
|
|
85
|
+
|
|
86
|
+
A receipt records the target, the checks, the raw probe evidence, and a
|
|
87
|
+
timestamp — including what could not be determined.
|
|
88
|
+
|
|
89
|
+
`hmac_sha256` signs the body with `$X190_KEY`, falling back to a
|
|
90
|
+
per-machine key at `~/.X190-key` (created 0600).
|
|
91
|
+
|
|
92
|
+
**What a receipt proves, exactly:** that someone holding the key produced this
|
|
93
|
+
byte-for-byte content. Nothing else. The scheme is symmetric, so a party who
|
|
94
|
+
can verify a receipt can also mint one — which means a receipt is tamper
|
|
95
|
+
evidence for your own archive, *not* an attestation you can hand to a third
|
|
96
|
+
party as proof. Two unrelated organisations cannot use it to distinguish an
|
|
97
|
+
authentic result from a fabricated one, because doing so would require sharing
|
|
98
|
+
the secret that lets either of them fabricate. If you need transferable proof,
|
|
99
|
+
this design cannot give it to you; that would need public-key signatures and a
|
|
100
|
+
key you publish.
|
|
101
|
+
|
|
102
|
+
Receipts signed with the published demo key are marked `"demo_key": true` and
|
|
103
|
+
the verifier warns about them, because that key is in this README and anyone
|
|
104
|
+
can sign anything with it. Set `X190_KEY` to a real secret anywhere you
|
|
105
|
+
intend to verify receipts later, including CI.
|
|
106
|
+
|
|
107
|
+
The receipts under `demo/` were produced against loopback servers in this
|
|
108
|
+
repository's own tests and are signed with a published constant, so anyone can
|
|
109
|
+
verify them:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
X190_KEY=X190-demo-key-not-a-secret python3 X190.py verify-receipt demo/open-server.receipt.json
|
|
113
|
+
X190_KEY=X190-demo-key-not-a-secret python3 X190.py verify-receipt demo/tampered.receipt.json # -> false, exit 1
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
That key is a demo constant, not a secret.
|
|
117
|
+
|
|
118
|
+
## GitHub Action
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
- uses: unempyd/X190@v0.7.0
|
|
122
|
+
with:
|
|
123
|
+
target: https://your-host/mcp
|
|
124
|
+
gate-key: ${{ secrets.X190_KEY }}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The step fails the build when a fault is found. Read
|
|
128
|
+
`steps.<id>.outputs.failures` to gate on the count yourself instead.
|
|
129
|
+
|
|
130
|
+
## Tests
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
python3 -m unittest discover -s tests
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Stdlib only, no outbound network: the tests stand up loopback servers.
|
|
137
|
+
|
|
138
|
+
## What a finding asserts
|
|
139
|
+
|
|
140
|
+
`AUTH-OPEN` is an observation, not a verdict: at this timestamp, from this
|
|
141
|
+
network position, the endpoint returned a JSON-RPC `result` for `tools/list`
|
|
142
|
+
sent with no credentials, and the receipt says how many tools came back.
|
|
143
|
+
|
|
144
|
+
It does not assert that this is a mistake. A deliberately public MCP server is
|
|
145
|
+
a legitimate design. The receipt is evidence of what the endpoint did; whether
|
|
146
|
+
that is a fault is the operator's call.
|
|
147
|
+
|
|
148
|
+
## What this is NOT
|
|
149
|
+
|
|
150
|
+
Not a gateway, agent runtime, identity provider, or hosted platform. Not a
|
|
151
|
+
source-code scanner — earlier versions shipped one, and the measurement above
|
|
152
|
+
is why it was removed: every fault in the population was observable at the
|
|
153
|
+
endpoint, and none of the affected servers published source to scan. Not a
|
|
154
|
+
replacement for the official conformance suite.
|
|
155
|
+
|
|
156
|
+
## Methodology honesty
|
|
157
|
+
|
|
158
|
+
Probes are single-shot and chained (`initialize`, then a tokenless
|
|
159
|
+
`tools/list`). They establish posture at one timestamp from one network
|
|
160
|
+
position — not full OAuth conformance.
|
|
161
|
+
|
|
162
|
+
- A `pass` means the endpoint refused an unauthenticated tool call **then**,
|
|
163
|
+
**from here**, and published resolvable metadata. It is not an audit, and it
|
|
164
|
+
says nothing about token validation, scopes, or authorisation once a token is
|
|
165
|
+
actually presented.
|
|
166
|
+
- Bot walls, dead endpoints and unexpected protocol shapes report
|
|
167
|
+
`inconclusive`. The tool never claims a fault it did not observe. A bot wall
|
|
168
|
+
must look like one: a 403 carrying a JSON body no longer counts, so an MCP
|
|
169
|
+
server cannot buy an inconclusive by putting "access denied" in its JSON.
|
|
170
|
+
- The handshake is completed before anything is asked for — `initialize`, then
|
|
171
|
+
`notifications/initialized`, then `tools/list` carrying the protocol version
|
|
172
|
+
the server negotiated. A spec-strict server that would otherwise reject the
|
|
173
|
+
request is measured rather than filed as inconclusive.
|
|
174
|
+
- Every request this tool makes — the probe itself and the metadata fetch —
|
|
175
|
+
goes through one guarded opener that re-validates each redirect hop. Neither
|
|
176
|
+
will follow a redirect to a private or link-local address, so a scanned server
|
|
177
|
+
cannot use this tool to reach into the network of whoever runs it. This does
|
|
178
|
+
not survive DNS rebinding between the check and the connection.
|
|
179
|
+
- The probe additionally refuses to leave the origin you named. A finding is a
|
|
180
|
+
claim about a specific endpoint, so a redirect to a different host or port
|
|
181
|
+
reports `REDIRECT-OFF-TARGET` / inconclusive rather than quietly measuring
|
|
182
|
+
something else and filing it under your target. Same-origin redirects are
|
|
183
|
+
followed and recorded.
|
|
184
|
+
- Receipts are HMAC-signed, not PKI. Anyone holding the key can mint one; they
|
|
185
|
+
are tamper-evidence for an archive, not third-party attestation.
|
|
186
|
+
- **A receipt has no freshness.** It carries a timestamp and nothing binds it to
|
|
187
|
+
now, so a genuine passing receipt can be presented long after the posture
|
|
188
|
+
changed. `verify-receipt` reports `age_seconds`; decide your own staleness
|
|
189
|
+
policy. Nothing here proves an endpoint is *currently* closed.
|
|
190
|
+
- **An endpoint can single out this prober.** The probe sends a `X190/...`
|
|
191
|
+
User-Agent from one IP; a server that returns a clean 401 to it and its tool
|
|
192
|
+
list to everyone else passes. This is reproduced in our own testing and is
|
|
193
|
+
inherent to remote black-box probing — a receipt records what the endpoint
|
|
194
|
+
returned *to us, then*, not what it returns to everyone.
|
|
195
|
+
- **An endpoint can pass CI by redirecting away.** A 302 to a different host or
|
|
196
|
+
port reports `REDIRECT-OFF-TARGET` / inconclusive, which does not fail the
|
|
197
|
+
gate. That verdict is deliberate — a finding cannot be attributed to an origin
|
|
198
|
+
the caller did not name, and legitimate deployments redirect
|
|
199
|
+
`host/mcp` to `mcp.host/mcp`, so failing here would manufacture false
|
|
200
|
+
positives. The refusal and the URL are recorded in the receipt; re-run against
|
|
201
|
+
the destination if you meant to probe it.
|
|
202
|
+
- **An endpoint can force an inconclusive result.** A response padded past the
|
|
203
|
+
5 MB read cap is reported `RESPONSE-TRUNCATED` / inconclusive, and inconclusive
|
|
204
|
+
findings do not fail the gate. The tool will not claim a fault it could not
|
|
205
|
+
observe, so a server that refuses to be readable is recorded as unread rather
|
|
206
|
+
than as safe. Read the findings, not just the exit code.
|
|
207
|
+
- Probing sends unauthenticated requests to whatever URL you pass. Only probe
|
|
208
|
+
endpoints you are authorised to probe.
|
x190-0.8.0/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# X190
|
|
2
|
+
|
|
3
|
+
Does this MCP endpoint enforce authentication?
|
|
4
|
+
|
|
5
|
+
That is the whole tool. It sends `initialize`, replays `tools/list` with no
|
|
6
|
+
token, and records what came back. If the server hands its tool list to an
|
|
7
|
+
unauthenticated caller, that is the finding. If it refuses, the refusal is
|
|
8
|
+
checked for an RFC 9728 challenge a client can actually follow.
|
|
9
|
+
|
|
10
|
+
Single file, Python 3 stdlib only, zero dependencies.
|
|
11
|
+
|
|
12
|
+
## Why only that
|
|
13
|
+
|
|
14
|
+
We probed the 74 HTTP endpoints among the first 100 servers in the official MCP
|
|
15
|
+
registry on 2026-09-10. Every fault observed was auth absence (F1) or a broken
|
|
16
|
+
OAuth resource-server posture (F2). Nothing else was observed, so nothing else
|
|
17
|
+
is checked.
|
|
18
|
+
|
|
19
|
+
The headline rate from that run is **suspended pending a re-measurement**: the
|
|
20
|
+
probe of the day treated HTTP 200 as proof the tool list was served, and
|
|
21
|
+
JSON-RPC routinely refuses *inside* a 200. The probe now requires a JSON-RPC
|
|
22
|
+
`result` echoing the request id before it will claim anything. What survives unqualified is that 25 endpoints
|
|
23
|
+
refused and published resolvable RFC 9728 metadata, 4 refused with a broken or
|
|
24
|
+
absent challenge, and one advertised OAuth correctly and then served its tools
|
|
25
|
+
without a token anyway. See [EVIDENCE.md](EVIDENCE.md).
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
F1 auth-absence tools/list returned a JSON-RPC result with no token
|
|
29
|
+
F2 incomplete-oauth-rs refused, but with no RFC 9728 challenge a client could
|
|
30
|
+
follow, or with metadata that does not resolve
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
python3 X190.py check https://host/mcp # probe -> signed receipt
|
|
37
|
+
python3 X190.py check http://127.0.0.1:3000/mcp
|
|
38
|
+
python3 X190.py verify-receipt x.receipt.json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Exit codes: `0` = pass, or inconclusive only; `1` = at least one fault;
|
|
42
|
+
`2` = the target is not an http(s) URL.
|
|
43
|
+
|
|
44
|
+
### Servers distributed for stdio
|
|
45
|
+
|
|
46
|
+
A package that declares stdio transport can still ship an HTTP mode. In our
|
|
47
|
+
measurement of the registry's local-transport population, 3 of 23 npm packages
|
|
48
|
+
shipped a network-listening MCP surface, and all three could serve MCP without
|
|
49
|
+
authentication in a documented configuration — while the registry listed no
|
|
50
|
+
endpoint to probe.
|
|
51
|
+
|
|
52
|
+
If you ship or run such a server, start its HTTP mode and probe that URL like
|
|
53
|
+
any other. The fault is the same fault; only the discovery is different.
|
|
54
|
+
|
|
55
|
+
## Receipts
|
|
56
|
+
|
|
57
|
+
A receipt records the target, the checks, the raw probe evidence, and a
|
|
58
|
+
timestamp — including what could not be determined.
|
|
59
|
+
|
|
60
|
+
`hmac_sha256` signs the body with `$X190_KEY`, falling back to a
|
|
61
|
+
per-machine key at `~/.X190-key` (created 0600).
|
|
62
|
+
|
|
63
|
+
**What a receipt proves, exactly:** that someone holding the key produced this
|
|
64
|
+
byte-for-byte content. Nothing else. The scheme is symmetric, so a party who
|
|
65
|
+
can verify a receipt can also mint one — which means a receipt is tamper
|
|
66
|
+
evidence for your own archive, *not* an attestation you can hand to a third
|
|
67
|
+
party as proof. Two unrelated organisations cannot use it to distinguish an
|
|
68
|
+
authentic result from a fabricated one, because doing so would require sharing
|
|
69
|
+
the secret that lets either of them fabricate. If you need transferable proof,
|
|
70
|
+
this design cannot give it to you; that would need public-key signatures and a
|
|
71
|
+
key you publish.
|
|
72
|
+
|
|
73
|
+
Receipts signed with the published demo key are marked `"demo_key": true` and
|
|
74
|
+
the verifier warns about them, because that key is in this README and anyone
|
|
75
|
+
can sign anything with it. Set `X190_KEY` to a real secret anywhere you
|
|
76
|
+
intend to verify receipts later, including CI.
|
|
77
|
+
|
|
78
|
+
The receipts under `demo/` were produced against loopback servers in this
|
|
79
|
+
repository's own tests and are signed with a published constant, so anyone can
|
|
80
|
+
verify them:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
X190_KEY=X190-demo-key-not-a-secret python3 X190.py verify-receipt demo/open-server.receipt.json
|
|
84
|
+
X190_KEY=X190-demo-key-not-a-secret python3 X190.py verify-receipt demo/tampered.receipt.json # -> false, exit 1
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
That key is a demo constant, not a secret.
|
|
88
|
+
|
|
89
|
+
## GitHub Action
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
- uses: unempyd/X190@v0.7.0
|
|
93
|
+
with:
|
|
94
|
+
target: https://your-host/mcp
|
|
95
|
+
gate-key: ${{ secrets.X190_KEY }}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The step fails the build when a fault is found. Read
|
|
99
|
+
`steps.<id>.outputs.failures` to gate on the count yourself instead.
|
|
100
|
+
|
|
101
|
+
## Tests
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
python3 -m unittest discover -s tests
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Stdlib only, no outbound network: the tests stand up loopback servers.
|
|
108
|
+
|
|
109
|
+
## What a finding asserts
|
|
110
|
+
|
|
111
|
+
`AUTH-OPEN` is an observation, not a verdict: at this timestamp, from this
|
|
112
|
+
network position, the endpoint returned a JSON-RPC `result` for `tools/list`
|
|
113
|
+
sent with no credentials, and the receipt says how many tools came back.
|
|
114
|
+
|
|
115
|
+
It does not assert that this is a mistake. A deliberately public MCP server is
|
|
116
|
+
a legitimate design. The receipt is evidence of what the endpoint did; whether
|
|
117
|
+
that is a fault is the operator's call.
|
|
118
|
+
|
|
119
|
+
## What this is NOT
|
|
120
|
+
|
|
121
|
+
Not a gateway, agent runtime, identity provider, or hosted platform. Not a
|
|
122
|
+
source-code scanner — earlier versions shipped one, and the measurement above
|
|
123
|
+
is why it was removed: every fault in the population was observable at the
|
|
124
|
+
endpoint, and none of the affected servers published source to scan. Not a
|
|
125
|
+
replacement for the official conformance suite.
|
|
126
|
+
|
|
127
|
+
## Methodology honesty
|
|
128
|
+
|
|
129
|
+
Probes are single-shot and chained (`initialize`, then a tokenless
|
|
130
|
+
`tools/list`). They establish posture at one timestamp from one network
|
|
131
|
+
position — not full OAuth conformance.
|
|
132
|
+
|
|
133
|
+
- A `pass` means the endpoint refused an unauthenticated tool call **then**,
|
|
134
|
+
**from here**, and published resolvable metadata. It is not an audit, and it
|
|
135
|
+
says nothing about token validation, scopes, or authorisation once a token is
|
|
136
|
+
actually presented.
|
|
137
|
+
- Bot walls, dead endpoints and unexpected protocol shapes report
|
|
138
|
+
`inconclusive`. The tool never claims a fault it did not observe. A bot wall
|
|
139
|
+
must look like one: a 403 carrying a JSON body no longer counts, so an MCP
|
|
140
|
+
server cannot buy an inconclusive by putting "access denied" in its JSON.
|
|
141
|
+
- The handshake is completed before anything is asked for — `initialize`, then
|
|
142
|
+
`notifications/initialized`, then `tools/list` carrying the protocol version
|
|
143
|
+
the server negotiated. A spec-strict server that would otherwise reject the
|
|
144
|
+
request is measured rather than filed as inconclusive.
|
|
145
|
+
- Every request this tool makes — the probe itself and the metadata fetch —
|
|
146
|
+
goes through one guarded opener that re-validates each redirect hop. Neither
|
|
147
|
+
will follow a redirect to a private or link-local address, so a scanned server
|
|
148
|
+
cannot use this tool to reach into the network of whoever runs it. This does
|
|
149
|
+
not survive DNS rebinding between the check and the connection.
|
|
150
|
+
- The probe additionally refuses to leave the origin you named. A finding is a
|
|
151
|
+
claim about a specific endpoint, so a redirect to a different host or port
|
|
152
|
+
reports `REDIRECT-OFF-TARGET` / inconclusive rather than quietly measuring
|
|
153
|
+
something else and filing it under your target. Same-origin redirects are
|
|
154
|
+
followed and recorded.
|
|
155
|
+
- Receipts are HMAC-signed, not PKI. Anyone holding the key can mint one; they
|
|
156
|
+
are tamper-evidence for an archive, not third-party attestation.
|
|
157
|
+
- **A receipt has no freshness.** It carries a timestamp and nothing binds it to
|
|
158
|
+
now, so a genuine passing receipt can be presented long after the posture
|
|
159
|
+
changed. `verify-receipt` reports `age_seconds`; decide your own staleness
|
|
160
|
+
policy. Nothing here proves an endpoint is *currently* closed.
|
|
161
|
+
- **An endpoint can single out this prober.** The probe sends a `X190/...`
|
|
162
|
+
User-Agent from one IP; a server that returns a clean 401 to it and its tool
|
|
163
|
+
list to everyone else passes. This is reproduced in our own testing and is
|
|
164
|
+
inherent to remote black-box probing — a receipt records what the endpoint
|
|
165
|
+
returned *to us, then*, not what it returns to everyone.
|
|
166
|
+
- **An endpoint can pass CI by redirecting away.** A 302 to a different host or
|
|
167
|
+
port reports `REDIRECT-OFF-TARGET` / inconclusive, which does not fail the
|
|
168
|
+
gate. That verdict is deliberate — a finding cannot be attributed to an origin
|
|
169
|
+
the caller did not name, and legitimate deployments redirect
|
|
170
|
+
`host/mcp` to `mcp.host/mcp`, so failing here would manufacture false
|
|
171
|
+
positives. The refusal and the URL are recorded in the receipt; re-run against
|
|
172
|
+
the destination if you meant to probe it.
|
|
173
|
+
- **An endpoint can force an inconclusive result.** A response padded past the
|
|
174
|
+
5 MB read cap is reported `RESPONSE-TRUNCATED` / inconclusive, and inconclusive
|
|
175
|
+
findings do not fail the gate. The tool will not claim a fault it could not
|
|
176
|
+
observe, so a server that refuses to be readable is recorded as unread rather
|
|
177
|
+
than as safe. Read the findings, not just the exit code.
|
|
178
|
+
- Probing sends unauthenticated requests to whatever URL you pass. Only probe
|
|
179
|
+
endpoints you are authorised to probe.
|