mcp-authscan 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leroy H. Mason
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.
@@ -0,0 +1,238 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-authscan
3
+ Version: 0.8.0
4
+ Summary: Static security scanner for self-rolled OAuth/auth failure classes in MCP servers, gateways, and OAuth clients. Rules seeded from live-reproduced, chain-of-custody-anchored VATA findings. SARIF output for GitHub code scanning.
5
+ Author: Leroy H. Mason
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/LHMisme420/mcp-authscan
8
+ Project-URL: Repository, https://github.com/LHMisme420/mcp-authscan
9
+ Project-URL: Dashboard, https://lhmisme420.github.io/VATA-SCORES-0311
10
+ Keywords: mcp,security,oauth,pkce,sast,sarif,scanner,vulnerability
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Information Technology
15
+ Classifier: Topic :: Security
16
+ Classifier: Topic :: Software Development :: Quality Assurance
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Operating System :: OS Independent
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Dynamic: license-file
23
+
24
+ # mcp-authscan
25
+
26
+ ![license](https://img.shields.io/badge/license-MIT-blue)
27
+ ![python](https://img.shields.io/badge/python-3%20%C2%B7%20stdlib%20only-blue)
28
+ ![output](https://img.shields.io/badge/output-SARIF%202.1.0-brightgreen)
29
+ ![findings](https://img.shields.io/badge/findings-anchored%20on--chain-8A2BE2)
30
+
31
+ > Static security scanner for the classic web-auth failures that keep shipping in
32
+ > MCP servers, gateways, and OAuth clients — the boring bugs the AI-native scanners skip.
33
+
34
+ *Built and maintained by **Leroy H. Mason** (RU∞X / VATA) — an independent security
35
+ researcher applying two decades of law-enforcement and risk-management
36
+ chain-of-custody discipline to AI-agent security.*
37
+
38
+ Most MCP security tools chase AI-native failures (tool poisoning, prompt injection,
39
+ rug pulls). `mcp-authscan` targets the plain web-auth failures that keep shipping in
40
+ MCP gateway and OAuth code — authorization servers that skip PKCE, authorization
41
+ codes that replay or never expire, issuer-binding gaps in M2M clients, hardcoded
42
+ admin credentials. **Every rule is seeded from a vulnerability VATA found and
43
+ live-reproduced in a real MCP project.** Stdlib Python 3, zero dependencies.
44
+
45
+ **What makes it different:** every scan emits a SHA-256 over its findings that you can
46
+ anchor to a public blockchain — a tamper-evident, timestamped receipt. Commodity
47
+ scanners give you output; this gives you proof. *Receipts Over Promises.*
48
+
49
+ ## Quick start
50
+
51
+ ```
52
+ git clone https://github.com/LHMisme420/mcp-authscan.git
53
+ python3 mcp-authscan/mcp_authscan.py <path-to-repo> # scan
54
+ python3 mcp-authscan/mcp_authscan.py <path> --sarif # SARIF for GitHub code scanning
55
+ python3 mcp-authscan/mcp_authscan.py <path> --json # emits report_sha256 to anchor
56
+ ```
57
+
58
+ No install step, no dependencies — stdlib Python 3. *(PyPI package coming next.)*
59
+
60
+ ## Findings behind the rules
61
+
62
+ `mcp-authscan` is not theoretical. Its rules encode real vulnerabilities VATA
63
+ discovered, live-reproduced, and disclosed through coordinated channels:
64
+
65
+ - **PraisonAI** — CVE-2026-60085 (GHSA-5r6c-gj4g-r697), unenforced security policy in
66
+ the subprocess sandbox; fixed in 4.6.78, credited to LHMisme420.
67
+ - **B-series rules (PKCE / authorization-code lifecycle)** are seeded from findings
68
+ live-reproduced in the webrix, atrawog, and akshay5995 self-rolled OAuth gateways —
69
+ each anchored in the VATA corpus below.
70
+
71
+ Every finding is reproduced before disclosure and anchored on-chain for
72
+ chain-of-custody. Full corpus → [VATA dashboard](https://lhmisme420.github.io/VATA-SCORES-0311)
73
+ · [Zenodo (DOI, ORCID-linked)](https://doi.org/10.5281/zenodo.21839413)
74
+
75
+ ## Rules
76
+
77
+ | ID | Sev / Conf | Class | Type |
78
+ |----|-----------|-------|------|
79
+ | A1 | CRITICAL / HIGH | OAuth authorize handler reads `client_id`/`redirect_uri` but never validates against an allowlist (auth-bypass / open-redirect / ATO) | detector |
80
+ | A2 | CRITICAL–HIGH | Hardcoded default admin credentials; admin re-seed routines run on restart | detector |
81
+ | A3 | HIGH / MEDIUM | Auth middleware validates a token but enforces no role/tenant/scope (cross-tenant, CWE-863) | detector |
82
+ | A4 | MEDIUM / LOW | SSRF surface: outbound HTTP request to a non-literal, externally-influenced URL with no allowlist / internal-IP guard | review-list |
83
+ | A5 | LOW | `excluded_tools`/blocklist defined but enforcement unverified across surfaces | review pointer |
84
+ | C1 | HIGH / HIGH (MEDIUM when options are spread) | M2M MCP client provider built without an issuer binding: Python `ClientCredentialsOAuthProvider`/`PrivateKeyJWTOAuthProvider` without `issuer=`, TS `ClientCredentialsProvider`/`PrivateKeyJwtProvider`/`StaticPrivateKeyJwtProvider`/`CrossAppAccessProvider` without `expectedIssuer` | detector |
85
+ | C2 | HIGH / HIGH | Deprecated Python `RFC7523OAuthClientProvider` — has no issuer option, cannot be bound by config | detector |
86
+ | C3 | HIGH / MEDIUM | Issuer validation switched off (`skipIssuerMetadataValidation: true`, `verify_iss: False`, etc.) | detector |
87
+ | B1 | HIGH / HIGH | Self-rolled authorization server told to skip PKCE (`skipLocalPkceValidation: true`) with nothing else verifying the challenge (CWE-287) | detector |
88
+ | B1 | MEDIUM / MEDIUM | AS receives a PKCE `code_verifier` as input but no SHA-256/S256 transform appears in the file — verifier never checked against the stored challenge (CWE-287) | review-list |
89
+ | B2 | HIGH / MEDIUM | Authorization code redeemed at the token endpoint but never invalidated (delete/mark-used/revoke) in the same file — possible replay (CWE-294) | review-list |
90
+ | B3 | HIGH / HIGH | Authorization code issued with a lifetime far exceeding RFC 6749's ~600s recommendation (CWE-613) | detector |
91
+
92
+ A1–A3 are detectors: a hit is a finding to triage. A4–A5 are review-lists:
93
+ SSRF and enforcement-gap detection need dataflow, not pattern matching, so these
94
+ enumerate the surface to inspect rather than claiming a bug. Confidence labels
95
+ say which is which — read them.
96
+
97
+ ### Why the C-series exists
98
+
99
+ Upgrading the MCP SDK does not close the client-side issuer gap. The fixed
100
+ versions only enforce issuer binding on M2M providers when the issuer is
101
+ configured; without it, pre-provisioned client secrets and signed JWT
102
+ assertions are sent to whatever authorization server the MCP server
103
+ advertises. Version-based scanners report a pass on code that is still
104
+ exposed. Refs: python-sdk GHSA-qx49-fqc8-xw99; typescript-sdk PR #2887
105
+ (SEP-2352). C1 is a call-site check: options built in another file show up
106
+ as MEDIUM-confidence review items.
107
+
108
+ A runtime proof of the C1/C2 gap - a rogue authorization server harvesting
109
+ the client's credentials across all three M2M shapes, and refusing once
110
+ issuer= is set - is in
111
+ rule_corpus/client_issuer_binding/live_repro/ (--json emits a
112
+ report_sha256 to anchor).
113
+
114
+ ### Why the B-series exists
115
+
116
+ Every self-rolled OAuth 2.1 authorization server VATA has audited that
117
+ hand-rolls RFC 6749 / PKCE has failed at least one of three lifecycle
118
+ invariants; gateways delegating to an established IdP (Keycloak, Cognito, Dex)
119
+ have not. The B-series encodes the three most-repeated failures, seeded from
120
+ live-repro'd findings:
121
+
122
+ - **B1 — PKCE accepted but never enforced.** Either the provider is explicitly
123
+ told to skip local PKCE (`skipLocalPkceValidation: true`, webrix) or a
124
+ `code_verifier` is accepted but never hashed and compared (atrawog, akshay5995).
125
+ - **B2 — authorization code not single-use.** The code is redeemed but never
126
+ invalidated, so it replays (webrix).
127
+ - **B3 — authorization code never expires / excessive TTL.** Codes issued with
128
+ lifetimes orders of magnitude over the ~10-minute recommendation (atrawog, 1yr).
129
+
130
+ Honest limits, stated so findings are not over-claimed:
131
+
132
+ - B1 does **not** detect *optional* PKCE (validated when present, skippable when
133
+ absent) — that is a control-flow property, not a pattern, and is left to manual
134
+ review rather than guessed at.
135
+ - B2 is **same-file**: a server that invalidates its code in a separate storage
136
+ module will be flagged here as a review item (MEDIUM), not asserted as a bug.
137
+ Confirm the storage layer before filing.
138
+ - B3 fires only on an **explicit** excessive number tied to an authorization
139
+ code; expiry that is simply *absent* across a schema (a cross-file absence) is
140
+ not detected.
141
+
142
+ B1-skip and B3 are detectors; B1-verifier and B2 are review-lists, for the same
143
+ reason A4/A5 are — the certain cases are findings, the dataflow-dependent cases
144
+ enumerate surface to inspect. The confidence column says which.
145
+
146
+ ## Usage (all options)
147
+
148
+ ```
149
+ python3 mcp_authscan.py <path> --include-tests # test files skipped by default
150
+ python3 mcp_authscan.py <path> --fail-on high # CI gate: nonzero exit on >= sev
151
+ python3 mcp_authscan.py <path> --exclude DIR # skip a dir (repeatable)
152
+ ```
153
+
154
+ ## Ground truth
155
+
156
+ The rules are validated by re-detecting findings already filed and anchored by
157
+ VATA. A rule that stops re-detecting its seed finding is a regression, not a
158
+ release.
159
+
160
+ | Repo | Rule | Location |
161
+ |------|------|----------|
162
+ | lucky-aeon/mcp-gateway | A1 | `internal/gateway/auth.go` `handleOAuthAuthorize` |
163
+ | lucky-aeon/mcp-gateway | A2 | `internal/platform/config/config.go` defaults |
164
+ | lucky-aeon/mcp-gateway | A3 | `internal/gateway/auth.go` `mcpAuthMiddleware` |
165
+ | mcpjungle/mcpjungle | A4 | `internal/service/mcp/upstream_oauth.go` `RegistrationEndpoint` fetch |
166
+ | rule_corpus fixture | C1–C3 | `rule_corpus/client_issuer_binding` (bound providers must stay silent) |
167
+ | webrix-ai/secure-mcp-gateway | B1, B2 | `src/services/mcp-auth-provider.ts` (`skipLocalPkceValidation`, `exchangeAuthorizationCode`) |
168
+ | atrawog/mcp-oauth-dynamicclient | B1, B3 | `src/mcp_oauth_dynamicclient/routes.py` (1-year auth-code TTL) |
169
+ | akshay5995/mcp-oauth-gateway | B1 | `src/gateway.py` (`code_verifier` received, never validated) |
170
+
171
+ ## Provenance
172
+
173
+ Every scan is anchorable. `--json` emits a `report_sha256` over the findings;
174
+ anchor it to Ethereum before disclosure so the report is timestamped and
175
+ tamper-evident. That chain-of-custody is the differentiator — commodity scanners
176
+ don't give you a receipt.
177
+
178
+ Dashboard: https://lhmisme420.github.io/VATA-SCORES-0311
179
+
180
+ ## Limitations
181
+
182
+ Heuristic static analysis. It flags patterns; it does not prove exploitability.
183
+ A4/A5 are review-lists by design. **Absence of a finding is not proof of safety.**
184
+
185
+ ## GitHub code scanning (SARIF)
186
+
187
+ `mcp-authscan` can emit SARIF 2.1.0 so findings surface natively in a repo's
188
+ **Security → Code scanning** tab. Each rule carries a `security-severity` score
189
+ and its CWE identifier, so GitHub renders severity and taxonomy with no extra
190
+ configuration.
191
+
192
+ Result level follows severity: CRITICAL/HIGH map to `error`, MEDIUM to
193
+ `warning`, LOW to `note` — so review-list (MEDIUM) findings surface as
194
+ warnings, not errors, in the Security tab.
195
+
196
+ ```bash
197
+ # write SARIF to a file
198
+ mcp-authscan . --sarif results.sarif
199
+ ```
200
+
201
+ To scan on every push/PR and upload results, drop this into a consuming repo at
202
+ `.github/workflows/mcp-authscan.yml` (identical copy shipped at
203
+ `examples/github-code-scanning.yml`):
204
+
205
+ ```yaml
206
+ name: MCP auth scan
207
+ on:
208
+ push:
209
+ branches: [main]
210
+ pull_request:
211
+ schedule:
212
+ - cron: '0 6 * * 1'
213
+
214
+ permissions:
215
+ contents: read
216
+ security-events: write
217
+
218
+ jobs:
219
+ mcp-authscan:
220
+ runs-on: ubuntu-latest
221
+ steps:
222
+ - uses: actions/checkout@v4
223
+ - uses: actions/setup-python@v5
224
+ with:
225
+ python-version: '3.12'
226
+ - run: pip install git+https://github.com/LHMisme420/mcp-authscan.git
227
+ - run: mcp-authscan . --sarif results.sarif
228
+ continue-on-error: true
229
+ - if: always()
230
+ uses: github/codeql-action/upload-sarif@v3
231
+ with:
232
+ sarif_file: results.sarif
233
+ category: mcp-authscan
234
+ ```
235
+
236
+ `security-events: write` is mandatory for the upload. `continue-on-error` +
237
+ `if: always()` ensure findings still reach the Security tab when the scan exits
238
+ non-zero on a hit.
@@ -0,0 +1,215 @@
1
+ # mcp-authscan
2
+
3
+ ![license](https://img.shields.io/badge/license-MIT-blue)
4
+ ![python](https://img.shields.io/badge/python-3%20%C2%B7%20stdlib%20only-blue)
5
+ ![output](https://img.shields.io/badge/output-SARIF%202.1.0-brightgreen)
6
+ ![findings](https://img.shields.io/badge/findings-anchored%20on--chain-8A2BE2)
7
+
8
+ > Static security scanner for the classic web-auth failures that keep shipping in
9
+ > MCP servers, gateways, and OAuth clients — the boring bugs the AI-native scanners skip.
10
+
11
+ *Built and maintained by **Leroy H. Mason** (RU∞X / VATA) — an independent security
12
+ researcher applying two decades of law-enforcement and risk-management
13
+ chain-of-custody discipline to AI-agent security.*
14
+
15
+ Most MCP security tools chase AI-native failures (tool poisoning, prompt injection,
16
+ rug pulls). `mcp-authscan` targets the plain web-auth failures that keep shipping in
17
+ MCP gateway and OAuth code — authorization servers that skip PKCE, authorization
18
+ codes that replay or never expire, issuer-binding gaps in M2M clients, hardcoded
19
+ admin credentials. **Every rule is seeded from a vulnerability VATA found and
20
+ live-reproduced in a real MCP project.** Stdlib Python 3, zero dependencies.
21
+
22
+ **What makes it different:** every scan emits a SHA-256 over its findings that you can
23
+ anchor to a public blockchain — a tamper-evident, timestamped receipt. Commodity
24
+ scanners give you output; this gives you proof. *Receipts Over Promises.*
25
+
26
+ ## Quick start
27
+
28
+ ```
29
+ git clone https://github.com/LHMisme420/mcp-authscan.git
30
+ python3 mcp-authscan/mcp_authscan.py <path-to-repo> # scan
31
+ python3 mcp-authscan/mcp_authscan.py <path> --sarif # SARIF for GitHub code scanning
32
+ python3 mcp-authscan/mcp_authscan.py <path> --json # emits report_sha256 to anchor
33
+ ```
34
+
35
+ No install step, no dependencies — stdlib Python 3. *(PyPI package coming next.)*
36
+
37
+ ## Findings behind the rules
38
+
39
+ `mcp-authscan` is not theoretical. Its rules encode real vulnerabilities VATA
40
+ discovered, live-reproduced, and disclosed through coordinated channels:
41
+
42
+ - **PraisonAI** — CVE-2026-60085 (GHSA-5r6c-gj4g-r697), unenforced security policy in
43
+ the subprocess sandbox; fixed in 4.6.78, credited to LHMisme420.
44
+ - **B-series rules (PKCE / authorization-code lifecycle)** are seeded from findings
45
+ live-reproduced in the webrix, atrawog, and akshay5995 self-rolled OAuth gateways —
46
+ each anchored in the VATA corpus below.
47
+
48
+ Every finding is reproduced before disclosure and anchored on-chain for
49
+ chain-of-custody. Full corpus → [VATA dashboard](https://lhmisme420.github.io/VATA-SCORES-0311)
50
+ · [Zenodo (DOI, ORCID-linked)](https://doi.org/10.5281/zenodo.21839413)
51
+
52
+ ## Rules
53
+
54
+ | ID | Sev / Conf | Class | Type |
55
+ |----|-----------|-------|------|
56
+ | A1 | CRITICAL / HIGH | OAuth authorize handler reads `client_id`/`redirect_uri` but never validates against an allowlist (auth-bypass / open-redirect / ATO) | detector |
57
+ | A2 | CRITICAL–HIGH | Hardcoded default admin credentials; admin re-seed routines run on restart | detector |
58
+ | A3 | HIGH / MEDIUM | Auth middleware validates a token but enforces no role/tenant/scope (cross-tenant, CWE-863) | detector |
59
+ | A4 | MEDIUM / LOW | SSRF surface: outbound HTTP request to a non-literal, externally-influenced URL with no allowlist / internal-IP guard | review-list |
60
+ | A5 | LOW | `excluded_tools`/blocklist defined but enforcement unverified across surfaces | review pointer |
61
+ | C1 | HIGH / HIGH (MEDIUM when options are spread) | M2M MCP client provider built without an issuer binding: Python `ClientCredentialsOAuthProvider`/`PrivateKeyJWTOAuthProvider` without `issuer=`, TS `ClientCredentialsProvider`/`PrivateKeyJwtProvider`/`StaticPrivateKeyJwtProvider`/`CrossAppAccessProvider` without `expectedIssuer` | detector |
62
+ | C2 | HIGH / HIGH | Deprecated Python `RFC7523OAuthClientProvider` — has no issuer option, cannot be bound by config | detector |
63
+ | C3 | HIGH / MEDIUM | Issuer validation switched off (`skipIssuerMetadataValidation: true`, `verify_iss: False`, etc.) | detector |
64
+ | B1 | HIGH / HIGH | Self-rolled authorization server told to skip PKCE (`skipLocalPkceValidation: true`) with nothing else verifying the challenge (CWE-287) | detector |
65
+ | B1 | MEDIUM / MEDIUM | AS receives a PKCE `code_verifier` as input but no SHA-256/S256 transform appears in the file — verifier never checked against the stored challenge (CWE-287) | review-list |
66
+ | B2 | HIGH / MEDIUM | Authorization code redeemed at the token endpoint but never invalidated (delete/mark-used/revoke) in the same file — possible replay (CWE-294) | review-list |
67
+ | B3 | HIGH / HIGH | Authorization code issued with a lifetime far exceeding RFC 6749's ~600s recommendation (CWE-613) | detector |
68
+
69
+ A1–A3 are detectors: a hit is a finding to triage. A4–A5 are review-lists:
70
+ SSRF and enforcement-gap detection need dataflow, not pattern matching, so these
71
+ enumerate the surface to inspect rather than claiming a bug. Confidence labels
72
+ say which is which — read them.
73
+
74
+ ### Why the C-series exists
75
+
76
+ Upgrading the MCP SDK does not close the client-side issuer gap. The fixed
77
+ versions only enforce issuer binding on M2M providers when the issuer is
78
+ configured; without it, pre-provisioned client secrets and signed JWT
79
+ assertions are sent to whatever authorization server the MCP server
80
+ advertises. Version-based scanners report a pass on code that is still
81
+ exposed. Refs: python-sdk GHSA-qx49-fqc8-xw99; typescript-sdk PR #2887
82
+ (SEP-2352). C1 is a call-site check: options built in another file show up
83
+ as MEDIUM-confidence review items.
84
+
85
+ A runtime proof of the C1/C2 gap - a rogue authorization server harvesting
86
+ the client's credentials across all three M2M shapes, and refusing once
87
+ issuer= is set - is in
88
+ rule_corpus/client_issuer_binding/live_repro/ (--json emits a
89
+ report_sha256 to anchor).
90
+
91
+ ### Why the B-series exists
92
+
93
+ Every self-rolled OAuth 2.1 authorization server VATA has audited that
94
+ hand-rolls RFC 6749 / PKCE has failed at least one of three lifecycle
95
+ invariants; gateways delegating to an established IdP (Keycloak, Cognito, Dex)
96
+ have not. The B-series encodes the three most-repeated failures, seeded from
97
+ live-repro'd findings:
98
+
99
+ - **B1 — PKCE accepted but never enforced.** Either the provider is explicitly
100
+ told to skip local PKCE (`skipLocalPkceValidation: true`, webrix) or a
101
+ `code_verifier` is accepted but never hashed and compared (atrawog, akshay5995).
102
+ - **B2 — authorization code not single-use.** The code is redeemed but never
103
+ invalidated, so it replays (webrix).
104
+ - **B3 — authorization code never expires / excessive TTL.** Codes issued with
105
+ lifetimes orders of magnitude over the ~10-minute recommendation (atrawog, 1yr).
106
+
107
+ Honest limits, stated so findings are not over-claimed:
108
+
109
+ - B1 does **not** detect *optional* PKCE (validated when present, skippable when
110
+ absent) — that is a control-flow property, not a pattern, and is left to manual
111
+ review rather than guessed at.
112
+ - B2 is **same-file**: a server that invalidates its code in a separate storage
113
+ module will be flagged here as a review item (MEDIUM), not asserted as a bug.
114
+ Confirm the storage layer before filing.
115
+ - B3 fires only on an **explicit** excessive number tied to an authorization
116
+ code; expiry that is simply *absent* across a schema (a cross-file absence) is
117
+ not detected.
118
+
119
+ B1-skip and B3 are detectors; B1-verifier and B2 are review-lists, for the same
120
+ reason A4/A5 are — the certain cases are findings, the dataflow-dependent cases
121
+ enumerate surface to inspect. The confidence column says which.
122
+
123
+ ## Usage (all options)
124
+
125
+ ```
126
+ python3 mcp_authscan.py <path> --include-tests # test files skipped by default
127
+ python3 mcp_authscan.py <path> --fail-on high # CI gate: nonzero exit on >= sev
128
+ python3 mcp_authscan.py <path> --exclude DIR # skip a dir (repeatable)
129
+ ```
130
+
131
+ ## Ground truth
132
+
133
+ The rules are validated by re-detecting findings already filed and anchored by
134
+ VATA. A rule that stops re-detecting its seed finding is a regression, not a
135
+ release.
136
+
137
+ | Repo | Rule | Location |
138
+ |------|------|----------|
139
+ | lucky-aeon/mcp-gateway | A1 | `internal/gateway/auth.go` `handleOAuthAuthorize` |
140
+ | lucky-aeon/mcp-gateway | A2 | `internal/platform/config/config.go` defaults |
141
+ | lucky-aeon/mcp-gateway | A3 | `internal/gateway/auth.go` `mcpAuthMiddleware` |
142
+ | mcpjungle/mcpjungle | A4 | `internal/service/mcp/upstream_oauth.go` `RegistrationEndpoint` fetch |
143
+ | rule_corpus fixture | C1–C3 | `rule_corpus/client_issuer_binding` (bound providers must stay silent) |
144
+ | webrix-ai/secure-mcp-gateway | B1, B2 | `src/services/mcp-auth-provider.ts` (`skipLocalPkceValidation`, `exchangeAuthorizationCode`) |
145
+ | atrawog/mcp-oauth-dynamicclient | B1, B3 | `src/mcp_oauth_dynamicclient/routes.py` (1-year auth-code TTL) |
146
+ | akshay5995/mcp-oauth-gateway | B1 | `src/gateway.py` (`code_verifier` received, never validated) |
147
+
148
+ ## Provenance
149
+
150
+ Every scan is anchorable. `--json` emits a `report_sha256` over the findings;
151
+ anchor it to Ethereum before disclosure so the report is timestamped and
152
+ tamper-evident. That chain-of-custody is the differentiator — commodity scanners
153
+ don't give you a receipt.
154
+
155
+ Dashboard: https://lhmisme420.github.io/VATA-SCORES-0311
156
+
157
+ ## Limitations
158
+
159
+ Heuristic static analysis. It flags patterns; it does not prove exploitability.
160
+ A4/A5 are review-lists by design. **Absence of a finding is not proof of safety.**
161
+
162
+ ## GitHub code scanning (SARIF)
163
+
164
+ `mcp-authscan` can emit SARIF 2.1.0 so findings surface natively in a repo's
165
+ **Security → Code scanning** tab. Each rule carries a `security-severity` score
166
+ and its CWE identifier, so GitHub renders severity and taxonomy with no extra
167
+ configuration.
168
+
169
+ Result level follows severity: CRITICAL/HIGH map to `error`, MEDIUM to
170
+ `warning`, LOW to `note` — so review-list (MEDIUM) findings surface as
171
+ warnings, not errors, in the Security tab.
172
+
173
+ ```bash
174
+ # write SARIF to a file
175
+ mcp-authscan . --sarif results.sarif
176
+ ```
177
+
178
+ To scan on every push/PR and upload results, drop this into a consuming repo at
179
+ `.github/workflows/mcp-authscan.yml` (identical copy shipped at
180
+ `examples/github-code-scanning.yml`):
181
+
182
+ ```yaml
183
+ name: MCP auth scan
184
+ on:
185
+ push:
186
+ branches: [main]
187
+ pull_request:
188
+ schedule:
189
+ - cron: '0 6 * * 1'
190
+
191
+ permissions:
192
+ contents: read
193
+ security-events: write
194
+
195
+ jobs:
196
+ mcp-authscan:
197
+ runs-on: ubuntu-latest
198
+ steps:
199
+ - uses: actions/checkout@v4
200
+ - uses: actions/setup-python@v5
201
+ with:
202
+ python-version: '3.12'
203
+ - run: pip install git+https://github.com/LHMisme420/mcp-authscan.git
204
+ - run: mcp-authscan . --sarif results.sarif
205
+ continue-on-error: true
206
+ - if: always()
207
+ uses: github/codeql-action/upload-sarif@v3
208
+ with:
209
+ sarif_file: results.sarif
210
+ category: mcp-authscan
211
+ ```
212
+
213
+ `security-events: write` is mandatory for the upload. `continue-on-error` +
214
+ `if: always()` ensure findings still reach the Security tab when the scan exits
215
+ non-zero on a hit.