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 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.