Commit a589de3
fix(security): enforce management-plane isolation for API tokens, fix team scoping, and fix blocked usage logging (#3414)
* fix: block API tokens from token management, fix team_id inheritance, and fix blocked/revoked usage logging
Signed-off-by: Shoumi <shoumimukherjee@gmail.com>
* update test coverage
Signed-off-by: Shoumi <shoumimukherjee@gmail.com>
* test(security): add differential coverage for 401/403 middleware paths and list_team_tokens
Add 4 tests covering uncovered edge-case paths in the token usage
middleware's 401/403 handler (no Bearer header, non-API-token JWT,
malformed token) and the missing list_team_tokens API-token-blocked
test, achieving 100% differential coverage on new code.
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* style: apply linter formatting to tokens.py
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* fix(security): enforce token revocation by propagating 401 from auth middleware
Three root causes allowed revoked API tokens to bypass rejection:
1. AuthContextMiddleware caught ALL exceptions from get_current_user()
(including HTTPException 401 for revoked tokens) and silently
continued as anonymous, letting the request reach public endpoints.
Fix: Re-raise 401/403 HTTPExceptions as hard deny responses instead
of swallowing them.
2. The fallback revocation check in auth.py silently swallowed
_check_token_revoked_sync errors (e.g. missing table, DB unreachable)
and allowed the token through. Fix: Fail-secure — reject the token
when the revocation check itself fails.
3. Cache invalidation on revoke used asyncio.create_task (fire-and-forget),
creating a race window where the next request could arrive before the
invalidation task ran. Fix: Await invalidate_revocation() directly in
both revoke_token() and admin_revoke_token().
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* fix(security): cross-worker revocation via Redis revoke key check
In a multi-worker deployment (gunicorn behind nginx), revoking a token
only updated the local worker's _revoked_jtis set. Other workers
served stale L1 cache entries with is_token_revoked=False until the
30-second TTL expired.
Fix: In get_auth_context(), check the Redis revocation marker key
(mcpgw:auth:revoke:{jti}) BEFORE the L1 in-memory cache. When found,
promote the JTI to the local _revoked_jtis set and evict stale L1
entries, so subsequent requests on this worker skip the Redis call.
One Redis EXISTS per request with a JTI — sub-millisecond overhead.
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* fix(security): validate JTI before logging, preserve browser redirect, scope blocked to 4xx
Address three review findings from Codex:
1. Forged JWT audit-log poisoning: the 401/403 rejected-token logging
path decoded unverified JWTs and wrote jti/sub claims directly to
token_usage_logs. An attacker could craft a JWT with fake identity
claims to pollute audit data. Fix: verify the JTI exists in the
email_api_tokens table before logging.
2. Browser redirect broken: the hard-deny JSONResponse for 401/403 in
AuthContextMiddleware also applied to browser/HTMX requests with
stale cookies, returning raw JSON instead of letting the RBAC layer
redirect to /admin/login. Fix: detect browser requests (Accept:
text/html or HX-Request) and let them pass through without user
context.
3. 5xx folded into blocked metrics: status_code >= 400 included
backend errors (5xx) in the blocked count, skewing security denial
analytics. Fix: scope to 400 <= status_code < 500.
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* style: fix import ordering in token_usage_middleware
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* test(security): achieve 100% diff coverage for auth middleware, cache, and usage logging
Add 11 tests covering all uncovered lines from diff-cover:
- auth_middleware.py: HTTPException 401/403 hard deny for API requests,
browser/HTMX passthrough for redirect, failure logging with DB errors,
DB close errors, and non-401/403 HTTPException anonymous fallthrough
- auth_cache.py: Redis revocation marker detection with L1 eviction and
local set promotion, Redis error fallthrough to L1/L2
- token_usage_middleware.py: forged JWT with unknown JTI skipped,
DB error during JTI verification skipped
Coverage: auth_middleware.py 100%, auth_cache.py 100%,
token_usage_middleware.py 100%
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* fix(security): use DB-stored email for usage logs, align browser detection, add security headers
Address three findings from final review:
1. Forged user attribution: the rejected-token logging path used the
unverified JWT sub/email claim for usage logs. An attacker knowing
a valid JTI could forge a JWT with an arbitrary email to poison
another user's blocked-request stats. Fix: query EmailApiToken for
both id and user_email by JTI, use the DB-stored owner email.
2. Referer-based browser detection gap: AuthContextMiddleware only
checked Accept and HX-Request headers for browser detection, but
rbac.py also treats Referer containing /admin as browser traffic.
Admin UI fetch requests with Accept: */* and an /admin Referer got
a JSON 401 instead of passing through for redirect. Fix: include
/admin Referer check to match RBAC behavior.
3. Missing security headers on JSON 401/403: responses returned
directly from AuthContextMiddleware bypassed SecurityHeadersMiddleware.
Fix: add X-Content-Type-Options: nosniff and Referrer-Policy headers
to the JSONResponse.
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* fix(auth): only hard-deny revocation/disabled 401s, not all auth failures
The auth middleware was hard-denying ALL 401/403 HTTPExceptions, which
broke registration scripts and other callers using minimal JWT claims
(no user/teams/token_use). These previously fell through to route-level
auth handlers.
Narrow the hard-deny to only security-critical rejections: "Token has
been revoked", "Account disabled", and "Token validation failed". All
other 401/403s continue as anonymous for route-level handling.
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* embeeded auth
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
* pylint
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
---------
Signed-off-by: Shoumi <shoumimukherjee@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>1 parent c6c2ad0 commit a589de3
File tree
12 files changed
+1056
-201
lines changed- mcpgateway
- cache
- middleware
- routers
- services
- tests/unit/mcpgateway
- cache
- middleware
- routers
12 files changed
+1056
-201
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
76 | | - | |
| 76 | + | |
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
| |||
149 | 149 | | |
150 | 150 | | |
151 | 151 | | |
152 | | - | |
| 152 | + | |
153 | 153 | | |
154 | 154 | | |
155 | 155 | | |
| |||
209 | 209 | | |
210 | 210 | | |
211 | 211 | | |
212 | | - | |
213 | | - | |
214 | | - | |
215 | | - | |
216 | | - | |
217 | | - | |
218 | | - | |
219 | | - | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1286 | 1286 | | |
1287 | 1287 | | |
1288 | 1288 | | |
1289 | | - | |
1290 | | - | |
| 1289 | + | |
| 1290 | + | |
| 1291 | + | |
| 1292 | + | |
| 1293 | + | |
| 1294 | + | |
| 1295 | + | |
| 1296 | + | |
| 1297 | + | |
1291 | 1298 | | |
1292 | 1299 | | |
1293 | 1300 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
266 | 266 | | |
267 | 267 | | |
268 | 268 | | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
269 | 289 | | |
270 | 290 | | |
271 | 291 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
26 | | - | |
| 27 | + | |
27 | 28 | | |
28 | 29 | | |
29 | 30 | | |
| |||
35 | 36 | | |
36 | 37 | | |
37 | 38 | | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
38 | 45 | | |
39 | 46 | | |
40 | 47 | | |
| |||
144 | 151 | | |
145 | 152 | | |
146 | 153 | | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
147 | 206 | | |
148 | | - | |
| 207 | + | |
149 | 208 | | |
150 | 209 | | |
151 | 210 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
| 27 | + | |
26 | 28 | | |
27 | 29 | | |
28 | 30 | | |
| |||
108 | 110 | | |
109 | 111 | | |
110 | 112 | | |
111 | | - | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
112 | 116 | | |
113 | 117 | | |
114 | 118 | | |
115 | | - | |
116 | | - | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
117 | 151 | | |
118 | | - | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
124 | 155 | | |
125 | | - | |
126 | | - | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
127 | 169 | | |
128 | | - | |
129 | 170 | | |
130 | 171 | | |
131 | 172 | | |
132 | 173 | | |
| 174 | + | |
133 | 175 | | |
134 | | - | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
135 | 181 | | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
145 | 185 | | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
146 | 212 | | |
147 | | - | |
| 213 | + | |
148 | 214 | | |
| 215 | + | |
| 216 | + | |
149 | 217 | | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | | - | |
| 218 | + | |
155 | 219 | | |
156 | 220 | | |
157 | 221 | | |
158 | | - | |
159 | 222 | | |
160 | 223 | | |
161 | | - | |
162 | | - | |
163 | 224 | | |
164 | 225 | | |
165 | 226 | | |
166 | | - | |
167 | 227 | | |
168 | 228 | | |
169 | 229 | | |
| |||
173 | 233 | | |
174 | 234 | | |
175 | 235 | | |
176 | | - | |
177 | | - | |
| 236 | + | |
| 237 | + | |
178 | 238 | | |
179 | 239 | | |
180 | 240 | | |
0 commit comments