agentsentinel-cli 0.9.6__tar.gz → 0.9.7__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.
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/PKG-INFO +47 -20
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/README.md +46 -19
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/cli.py +20 -4
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/mcp_oauth.py +97 -13
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/mcp_preauth.py +77 -4
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/pyproject.toml +1 -1
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/.gitignore +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/DOCUMENTATION.md +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/LICENSE +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/__init__.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/a2a_report.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/a2a_rules.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/a2a_scanner.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/discover.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/discover_report.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/fingerprint.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/frameworks.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/host_report.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/host_rules.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/host_scanner.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/inspect.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/inspect_report.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/mcp_client.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/mcp_report.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/mcp_rules.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/__init__.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/mcp_auth.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/mcp_fuzz.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/mcp_inject.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/mcp_poison.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/mcp_recon.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/models.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/payloads.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/report.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/transport.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/report.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/rules.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/scanner.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/secrets.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/secrets_report.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/secrets_rules.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/supply_chain_ai.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/supply_chain_report.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/supply_chain_rules.py +0 -0
- {agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/suppress.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentsentinel-cli
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.7
|
|
4
4
|
Summary: AI agent and MCP server security scanner — discovery, static analysis, supply chain audit, and multi-agent trust analysis
|
|
5
5
|
Project-URL: Homepage, https://github.com/jaydenaung/agentsentinel-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/jaydenaung/agentsentinel-cli
|
|
@@ -91,8 +91,9 @@ sentinel host-scan --fail-on HIGH
|
|
|
91
91
|
|
|
92
92
|
# Active red-team — real attacks, confirmed exploitation
|
|
93
93
|
sentinel redteam mcp full http://localhost:8000
|
|
94
|
+
sentinel redteam mcp preauth http://localhost:8000 # works with zero credentials
|
|
94
95
|
sentinel redteam mcp inject http://localhost:8000 --type traverse --type ssrf
|
|
95
|
-
sentinel redteam mcp auth http://localhost:8000
|
|
96
|
+
sentinel redteam mcp auth http://localhost:8000 # credential bypass + OAuth 2.0
|
|
96
97
|
```
|
|
97
98
|
|
|
98
99
|
---
|
|
@@ -392,20 +393,27 @@ No API key required. No network calls.
|
|
|
392
393
|
|
|
393
394
|
The active red-team module for MCP servers. Every finding is backed by confirmed evidence from the server's actual response — no heuristics, no noise. If a traversal finding says it read `/etc/passwd`, it read `/etc/passwd`.
|
|
394
395
|
|
|
396
|
+
**`preauth` and OAuth probing run with zero credentials** — useful when the target server blocks unauthenticated MCP access entirely.
|
|
397
|
+
|
|
395
398
|
Requires `httpx`: `pip install "agentsentinel-cli[mcp]"`
|
|
396
399
|
|
|
397
400
|
```bash
|
|
398
|
-
# Full run — all
|
|
401
|
+
# Full run — all 7 phases, unified report
|
|
399
402
|
sentinel redteam mcp full http://localhost:8000
|
|
400
403
|
sentinel redteam mcp full http://localhost:8000 --intensity high --format json
|
|
401
404
|
|
|
402
405
|
# Targeted phases
|
|
406
|
+
sentinel redteam mcp preauth http://localhost:8000 # HTTP fingerprint — zero creds required
|
|
403
407
|
sentinel redteam mcp recon http://localhost:8000 # enumerate attack surface
|
|
404
|
-
sentinel redteam mcp auth http://localhost:8000 #
|
|
408
|
+
sentinel redteam mcp auth http://localhost:8000 # credential bypass + OAuth 2.0 attacks
|
|
405
409
|
sentinel redteam mcp inject http://localhost:8000 # all injection techniques
|
|
406
410
|
sentinel redteam mcp poison http://localhost:8000 # tool description + result injection
|
|
407
411
|
sentinel redteam mcp fuzz http://localhost:8000 # schema and type boundary fuzzing
|
|
408
412
|
|
|
413
|
+
# Preauth — works even when server blocks unauthenticated MCP
|
|
414
|
+
sentinel redteam mcp preauth http://localhost:8000
|
|
415
|
+
sentinel redteam mcp preauth http://locked-server:8000 # CORS, OAuth metadata, version disclosure
|
|
416
|
+
|
|
409
417
|
# Surgical injection — pick your techniques
|
|
410
418
|
sentinel redteam mcp inject http://localhost:8000 --type traverse
|
|
411
419
|
sentinel redteam mcp inject http://localhost:8000 --type traverse --type ssrf
|
|
@@ -425,15 +433,31 @@ sentinel redteam mcp full http://localhost:8000 --fail-on CRITICAL
|
|
|
425
433
|
sentinel redteam mcp full http://localhost:8000 --output report.json
|
|
426
434
|
```
|
|
427
435
|
|
|
428
|
-
**Phases:**
|
|
429
|
-
|
|
430
|
-
| Phase | Command | What it tests |
|
|
431
|
-
|
|
432
|
-
| 1 —
|
|
433
|
-
| 2 —
|
|
434
|
-
| 3 —
|
|
435
|
-
| 4 —
|
|
436
|
-
| 5 —
|
|
436
|
+
**Phases (`full` runs all 7):**
|
|
437
|
+
|
|
438
|
+
| Phase | Command | Needs credentials | What it tests |
|
|
439
|
+
|-------|---------|:-----------------:|---------------|
|
|
440
|
+
| 1 — Pre-auth | `preauth` | No | CORS, OAuth metadata, version disclosure, unauthenticated paths, SSE stream, error disclosure |
|
|
441
|
+
| 2 — OAuth | *(auto in `auth`/`full`)* | No | Public client registration, token without secret, PKCE downgrade, scope escalation, X-Agent-Scopes forgery |
|
|
442
|
+
| 3 — Recon | `recon` | Yes | Tool inventory, resource listing, dangerous capability flags with input schemas |
|
|
443
|
+
| 4 — Auth Bypass | `auth` | Optional | 5 credential scenarios: no creds, empty bearer, garbage token, invalid JWT, JWT alg:none |
|
|
444
|
+
| 5 — Injection | `inject` | Yes | Path traversal, SSRF, command injection, SQL injection — evidence-confirmed only |
|
|
445
|
+
| 6 — Poison | `poison` | Yes | Adversarial instructions in tool descriptions; LLM injection via tool parameters |
|
|
446
|
+
| 7 — Fuzz | `fuzz` | Yes | Stack traces, path disclosure, template injection eval, type confusion, input reflection |
|
|
447
|
+
|
|
448
|
+
**Pre-auth probes (zero credentials):**
|
|
449
|
+
|
|
450
|
+
| Probe | CRITICAL | HIGH | MEDIUM |
|
|
451
|
+
|-------|----------|------|--------|
|
|
452
|
+
| CORS wildcard + credentials | ✓ | | |
|
|
453
|
+
| CORS wildcard / reflected origin | | ✓ | |
|
|
454
|
+
| Unauthenticated SSE stream | | ✓ | |
|
|
455
|
+
| Public OAuth client registration | | ✓ | |
|
|
456
|
+
| Token issued without `client_secret` | ✓ | | |
|
|
457
|
+
| X-Agent-Scopes header forgery | ✓ | | |
|
|
458
|
+
| PKCE `plain` method accepted | | | ✓ |
|
|
459
|
+
| Server/framework version disclosure | | | ✓ |
|
|
460
|
+
| Unauthenticated `/docs`, `/metrics` | | | ✓ |
|
|
437
461
|
|
|
438
462
|
**Injection techniques (`--type`):**
|
|
439
463
|
|
|
@@ -457,13 +481,13 @@ sentinel redteam mcp full http://localhost:8000 --output report.json
|
|
|
457
481
|
|
|
458
482
|
| Severity | Example |
|
|
459
483
|
|----------|---------|
|
|
460
|
-
| CRITICAL | Path traversal confirmed — `/etc/passwd` content in response |
|
|
461
|
-
| HIGH | LLM instruction injection — sentinel reflected in clean tool result |
|
|
462
|
-
| MEDIUM | Input reflected in error message
|
|
484
|
+
| CRITICAL | Path traversal confirmed — `/etc/passwd` content in response; OAuth token issued without secret |
|
|
485
|
+
| HIGH | LLM instruction injection — sentinel reflected in clean tool result; CORS wildcard |
|
|
486
|
+
| MEDIUM | Input reflected in error message; PKCE plain method accepted; version disclosure |
|
|
463
487
|
| LOW | Unexpected content returned on malformed input |
|
|
464
|
-
| INFO | Auth enforced on handshake, tool inventory |
|
|
488
|
+
| INFO | Auth enforced on handshake, tool inventory, OAuth metadata discovered |
|
|
465
489
|
|
|
466
|
-
Every finding includes a **MITRE ATLAS** ID and **OWASP ASI** ID. Use `--verbose` to see full request/response bodies.
|
|
490
|
+
Every finding includes a **Fix** line, a **MITRE ATLAS** ID, and an **OWASP ASI** ID. Confirmed multi-step attack chains are synthesized in the report. Use `--verbose` to see full request/response bodies.
|
|
467
491
|
|
|
468
492
|
---
|
|
469
493
|
|
|
@@ -496,10 +520,10 @@ Supported on: `sentinel scan`, `sentinel a2a`, `sentinel mcp scan`, `sentinel su
|
|
|
496
520
|
|------------|-----|------------------|
|
|
497
521
|
| Agent Goal Hijack | ASI01 | `sentinel scan` (PROMPT_INJECTION_VECTOR), `sentinel supply-chain` (SC01), **`sentinel redteam mcp poison`** (confirmed injection) |
|
|
498
522
|
| Tool Misuse & Exploitation | ASI02 | `sentinel mcp scan`, `sentinel scan`, **`sentinel redteam mcp inject`** (confirmed exploitation) |
|
|
499
|
-
| Agent Identity & Privilege Abuse | ASI03 | `sentinel scan` (PRIVILEGE_EXCESS), `sentinel host-scan` (HOST_SHELL_UNRESTRICTED), **`sentinel redteam mcp auth`** (bypass
|
|
523
|
+
| Agent Identity & Privilege Abuse | ASI03 | `sentinel scan` (PRIVILEGE_EXCESS), `sentinel host-scan` (HOST_SHELL_UNRESTRICTED), **`sentinel redteam mcp auth`** (credential bypass + OAuth scope escalation + X-Agent-Scopes forgery) |
|
|
500
524
|
| **Agentic Supply Chain Compromise** | **ASI04** | **`sentinel supply-chain`** (static + AI semantic analysis), **`sentinel redteam mcp poison`** (static description scan) |
|
|
501
525
|
| Unexpected Code Execution | ASI05 | `sentinel scan` (CODE_EXECUTION_GRANT), `sentinel mcp scan` (CODE_EXECUTION_TOOL), **`sentinel redteam mcp inject --type cmd`** |
|
|
502
|
-
| **Memory & Context Poisoning** | **ASI06** | **`sentinel secrets`** (memory contamination, system prompt leakage), `sentinel host-scan` (HOST_LARGE_MEMORY) |
|
|
526
|
+
| **Memory & Context Poisoning** | **ASI06** | **`sentinel secrets`** (memory contamination, system prompt leakage), `sentinel host-scan` (HOST_LARGE_MEMORY), **`sentinel redteam mcp preauth`** (CORS misconfiguration enabling cross-origin data theft) |
|
|
503
527
|
| **Insecure Inter-Agent Communication** | **ASI07** | **`sentinel a2a`** (call graph + trust rules) |
|
|
504
528
|
| Cascading Agent Failures | ASI08 | `sentinel discover` (surface unmonitored agents) |
|
|
505
529
|
| Rogue Agents | ASI10 | `sentinel discover` (find agents that shouldn't exist), `sentinel host-scan` (HOST_AI_PROCESS_EXPOSED) |
|
|
@@ -540,6 +564,9 @@ jobs:
|
|
|
540
564
|
- name: Host AI security posture
|
|
541
565
|
run: sentinel host-scan --fail-on HIGH
|
|
542
566
|
|
|
567
|
+
- name: MCP pre-auth probe (zero credentials needed)
|
|
568
|
+
run: sentinel redteam mcp preauth http://localhost:8000 --fail-on HIGH
|
|
569
|
+
|
|
543
570
|
- name: MCP red-team (active exploitation check)
|
|
544
571
|
run: sentinel redteam mcp full http://localhost:8000 --fail-on CRITICAL
|
|
545
572
|
```
|
|
@@ -57,8 +57,9 @@ sentinel host-scan --fail-on HIGH
|
|
|
57
57
|
|
|
58
58
|
# Active red-team — real attacks, confirmed exploitation
|
|
59
59
|
sentinel redteam mcp full http://localhost:8000
|
|
60
|
+
sentinel redteam mcp preauth http://localhost:8000 # works with zero credentials
|
|
60
61
|
sentinel redteam mcp inject http://localhost:8000 --type traverse --type ssrf
|
|
61
|
-
sentinel redteam mcp auth http://localhost:8000
|
|
62
|
+
sentinel redteam mcp auth http://localhost:8000 # credential bypass + OAuth 2.0
|
|
62
63
|
```
|
|
63
64
|
|
|
64
65
|
---
|
|
@@ -358,20 +359,27 @@ No API key required. No network calls.
|
|
|
358
359
|
|
|
359
360
|
The active red-team module for MCP servers. Every finding is backed by confirmed evidence from the server's actual response — no heuristics, no noise. If a traversal finding says it read `/etc/passwd`, it read `/etc/passwd`.
|
|
360
361
|
|
|
362
|
+
**`preauth` and OAuth probing run with zero credentials** — useful when the target server blocks unauthenticated MCP access entirely.
|
|
363
|
+
|
|
361
364
|
Requires `httpx`: `pip install "agentsentinel-cli[mcp]"`
|
|
362
365
|
|
|
363
366
|
```bash
|
|
364
|
-
# Full run — all
|
|
367
|
+
# Full run — all 7 phases, unified report
|
|
365
368
|
sentinel redteam mcp full http://localhost:8000
|
|
366
369
|
sentinel redteam mcp full http://localhost:8000 --intensity high --format json
|
|
367
370
|
|
|
368
371
|
# Targeted phases
|
|
372
|
+
sentinel redteam mcp preauth http://localhost:8000 # HTTP fingerprint — zero creds required
|
|
369
373
|
sentinel redteam mcp recon http://localhost:8000 # enumerate attack surface
|
|
370
|
-
sentinel redteam mcp auth http://localhost:8000 #
|
|
374
|
+
sentinel redteam mcp auth http://localhost:8000 # credential bypass + OAuth 2.0 attacks
|
|
371
375
|
sentinel redteam mcp inject http://localhost:8000 # all injection techniques
|
|
372
376
|
sentinel redteam mcp poison http://localhost:8000 # tool description + result injection
|
|
373
377
|
sentinel redteam mcp fuzz http://localhost:8000 # schema and type boundary fuzzing
|
|
374
378
|
|
|
379
|
+
# Preauth — works even when server blocks unauthenticated MCP
|
|
380
|
+
sentinel redteam mcp preauth http://localhost:8000
|
|
381
|
+
sentinel redteam mcp preauth http://locked-server:8000 # CORS, OAuth metadata, version disclosure
|
|
382
|
+
|
|
375
383
|
# Surgical injection — pick your techniques
|
|
376
384
|
sentinel redteam mcp inject http://localhost:8000 --type traverse
|
|
377
385
|
sentinel redteam mcp inject http://localhost:8000 --type traverse --type ssrf
|
|
@@ -391,15 +399,31 @@ sentinel redteam mcp full http://localhost:8000 --fail-on CRITICAL
|
|
|
391
399
|
sentinel redteam mcp full http://localhost:8000 --output report.json
|
|
392
400
|
```
|
|
393
401
|
|
|
394
|
-
**Phases:**
|
|
395
|
-
|
|
396
|
-
| Phase | Command | What it tests |
|
|
397
|
-
|
|
398
|
-
| 1 —
|
|
399
|
-
| 2 —
|
|
400
|
-
| 3 —
|
|
401
|
-
| 4 —
|
|
402
|
-
| 5 —
|
|
402
|
+
**Phases (`full` runs all 7):**
|
|
403
|
+
|
|
404
|
+
| Phase | Command | Needs credentials | What it tests |
|
|
405
|
+
|-------|---------|:-----------------:|---------------|
|
|
406
|
+
| 1 — Pre-auth | `preauth` | No | CORS, OAuth metadata, version disclosure, unauthenticated paths, SSE stream, error disclosure |
|
|
407
|
+
| 2 — OAuth | *(auto in `auth`/`full`)* | No | Public client registration, token without secret, PKCE downgrade, scope escalation, X-Agent-Scopes forgery |
|
|
408
|
+
| 3 — Recon | `recon` | Yes | Tool inventory, resource listing, dangerous capability flags with input schemas |
|
|
409
|
+
| 4 — Auth Bypass | `auth` | Optional | 5 credential scenarios: no creds, empty bearer, garbage token, invalid JWT, JWT alg:none |
|
|
410
|
+
| 5 — Injection | `inject` | Yes | Path traversal, SSRF, command injection, SQL injection — evidence-confirmed only |
|
|
411
|
+
| 6 — Poison | `poison` | Yes | Adversarial instructions in tool descriptions; LLM injection via tool parameters |
|
|
412
|
+
| 7 — Fuzz | `fuzz` | Yes | Stack traces, path disclosure, template injection eval, type confusion, input reflection |
|
|
413
|
+
|
|
414
|
+
**Pre-auth probes (zero credentials):**
|
|
415
|
+
|
|
416
|
+
| Probe | CRITICAL | HIGH | MEDIUM |
|
|
417
|
+
|-------|----------|------|--------|
|
|
418
|
+
| CORS wildcard + credentials | ✓ | | |
|
|
419
|
+
| CORS wildcard / reflected origin | | ✓ | |
|
|
420
|
+
| Unauthenticated SSE stream | | ✓ | |
|
|
421
|
+
| Public OAuth client registration | | ✓ | |
|
|
422
|
+
| Token issued without `client_secret` | ✓ | | |
|
|
423
|
+
| X-Agent-Scopes header forgery | ✓ | | |
|
|
424
|
+
| PKCE `plain` method accepted | | | ✓ |
|
|
425
|
+
| Server/framework version disclosure | | | ✓ |
|
|
426
|
+
| Unauthenticated `/docs`, `/metrics` | | | ✓ |
|
|
403
427
|
|
|
404
428
|
**Injection techniques (`--type`):**
|
|
405
429
|
|
|
@@ -423,13 +447,13 @@ sentinel redteam mcp full http://localhost:8000 --output report.json
|
|
|
423
447
|
|
|
424
448
|
| Severity | Example |
|
|
425
449
|
|----------|---------|
|
|
426
|
-
| CRITICAL | Path traversal confirmed — `/etc/passwd` content in response |
|
|
427
|
-
| HIGH | LLM instruction injection — sentinel reflected in clean tool result |
|
|
428
|
-
| MEDIUM | Input reflected in error message
|
|
450
|
+
| CRITICAL | Path traversal confirmed — `/etc/passwd` content in response; OAuth token issued without secret |
|
|
451
|
+
| HIGH | LLM instruction injection — sentinel reflected in clean tool result; CORS wildcard |
|
|
452
|
+
| MEDIUM | Input reflected in error message; PKCE plain method accepted; version disclosure |
|
|
429
453
|
| LOW | Unexpected content returned on malformed input |
|
|
430
|
-
| INFO | Auth enforced on handshake, tool inventory |
|
|
454
|
+
| INFO | Auth enforced on handshake, tool inventory, OAuth metadata discovered |
|
|
431
455
|
|
|
432
|
-
Every finding includes a **MITRE ATLAS** ID and **OWASP ASI** ID. Use `--verbose` to see full request/response bodies.
|
|
456
|
+
Every finding includes a **Fix** line, a **MITRE ATLAS** ID, and an **OWASP ASI** ID. Confirmed multi-step attack chains are synthesized in the report. Use `--verbose` to see full request/response bodies.
|
|
433
457
|
|
|
434
458
|
---
|
|
435
459
|
|
|
@@ -462,10 +486,10 @@ Supported on: `sentinel scan`, `sentinel a2a`, `sentinel mcp scan`, `sentinel su
|
|
|
462
486
|
|------------|-----|------------------|
|
|
463
487
|
| Agent Goal Hijack | ASI01 | `sentinel scan` (PROMPT_INJECTION_VECTOR), `sentinel supply-chain` (SC01), **`sentinel redteam mcp poison`** (confirmed injection) |
|
|
464
488
|
| Tool Misuse & Exploitation | ASI02 | `sentinel mcp scan`, `sentinel scan`, **`sentinel redteam mcp inject`** (confirmed exploitation) |
|
|
465
|
-
| Agent Identity & Privilege Abuse | ASI03 | `sentinel scan` (PRIVILEGE_EXCESS), `sentinel host-scan` (HOST_SHELL_UNRESTRICTED), **`sentinel redteam mcp auth`** (bypass
|
|
489
|
+
| Agent Identity & Privilege Abuse | ASI03 | `sentinel scan` (PRIVILEGE_EXCESS), `sentinel host-scan` (HOST_SHELL_UNRESTRICTED), **`sentinel redteam mcp auth`** (credential bypass + OAuth scope escalation + X-Agent-Scopes forgery) |
|
|
466
490
|
| **Agentic Supply Chain Compromise** | **ASI04** | **`sentinel supply-chain`** (static + AI semantic analysis), **`sentinel redteam mcp poison`** (static description scan) |
|
|
467
491
|
| Unexpected Code Execution | ASI05 | `sentinel scan` (CODE_EXECUTION_GRANT), `sentinel mcp scan` (CODE_EXECUTION_TOOL), **`sentinel redteam mcp inject --type cmd`** |
|
|
468
|
-
| **Memory & Context Poisoning** | **ASI06** | **`sentinel secrets`** (memory contamination, system prompt leakage), `sentinel host-scan` (HOST_LARGE_MEMORY) |
|
|
492
|
+
| **Memory & Context Poisoning** | **ASI06** | **`sentinel secrets`** (memory contamination, system prompt leakage), `sentinel host-scan` (HOST_LARGE_MEMORY), **`sentinel redteam mcp preauth`** (CORS misconfiguration enabling cross-origin data theft) |
|
|
469
493
|
| **Insecure Inter-Agent Communication** | **ASI07** | **`sentinel a2a`** (call graph + trust rules) |
|
|
470
494
|
| Cascading Agent Failures | ASI08 | `sentinel discover` (surface unmonitored agents) |
|
|
471
495
|
| Rogue Agents | ASI10 | `sentinel discover` (find agents that shouldn't exist), `sentinel host-scan` (HOST_AI_PROCESS_EXPOSED) |
|
|
@@ -506,6 +530,9 @@ jobs:
|
|
|
506
530
|
- name: Host AI security posture
|
|
507
531
|
run: sentinel host-scan --fail-on HIGH
|
|
508
532
|
|
|
533
|
+
- name: MCP pre-auth probe (zero credentials needed)
|
|
534
|
+
run: sentinel redteam mcp preauth http://localhost:8000 --fail-on HIGH
|
|
535
|
+
|
|
509
536
|
- name: MCP red-team (active exploitation check)
|
|
510
537
|
run: sentinel redteam mcp full http://localhost:8000 --fail-on CRITICAL
|
|
511
538
|
```
|
|
@@ -1166,20 +1166,25 @@ def redteam_mcp_recon(
|
|
|
1166
1166
|
@click.option("--output", "output_path", default=None, metavar="FILE")
|
|
1167
1167
|
@click.option("--verbose", "-v", is_flag=True, default=False)
|
|
1168
1168
|
@click.option("--fail-on", type=click.Choice(["CRITICAL", "HIGH", "MEDIUM", "LOW"]), default=None)
|
|
1169
|
+
@click.option("--skip-oauth", "skip_oauth", is_flag=True, default=False,
|
|
1170
|
+
help="Skip OAuth tests against external authorization servers (use when AS is out of scope).")
|
|
1169
1171
|
def redteam_mcp_preauth(
|
|
1170
1172
|
target: str | None, timeout: float, fmt: str,
|
|
1171
1173
|
output_path: str | None, verbose: bool, fail_on: str | None,
|
|
1174
|
+
skip_oauth: bool,
|
|
1172
1175
|
) -> None:
|
|
1173
1176
|
"""HTTP-layer fingerprinting — runs with zero credentials.
|
|
1174
1177
|
|
|
1175
1178
|
Probes the server before any MCP initialize attempt: CORS config,
|
|
1176
1179
|
OAuth metadata, version disclosure, unauthenticated paths, SSE stream.
|
|
1180
|
+
Follows the MCP 2025 OAuth discovery chain to find external auth servers.
|
|
1177
1181
|
Produces findings even when the server blocks unauthenticated MCP access.
|
|
1178
1182
|
|
|
1179
1183
|
\b
|
|
1180
1184
|
Examples:
|
|
1181
1185
|
sentinel redteam mcp preauth http://localhost:3000
|
|
1182
1186
|
sentinel redteam mcp preauth http://localhost:3000 --verbose
|
|
1187
|
+
sentinel redteam mcp preauth http://localhost:3000 --skip-oauth
|
|
1183
1188
|
"""
|
|
1184
1189
|
import time
|
|
1185
1190
|
from agentsentinel_cli.redteam.mcp_preauth import run_preauth
|
|
@@ -1198,6 +1203,7 @@ def redteam_mcp_preauth(
|
|
|
1198
1203
|
preauth_findings, preauth_count = run_preauth(url=target, timeout=timeout, verbose=verbose)
|
|
1199
1204
|
oauth_findings, oauth_count = run_oauth(
|
|
1200
1205
|
url=target, original_headers={}, timeout=timeout, verbose=verbose,
|
|
1206
|
+
skip_external=skip_oauth,
|
|
1201
1207
|
)
|
|
1202
1208
|
all_findings = preauth_findings + oauth_findings
|
|
1203
1209
|
|
|
@@ -1229,9 +1235,12 @@ def redteam_mcp_preauth(
|
|
|
1229
1235
|
@click.option("--output", "output_path", default=None, metavar="FILE")
|
|
1230
1236
|
@click.option("--verbose", "-v", is_flag=True, default=False)
|
|
1231
1237
|
@click.option("--fail-on", type=click.Choice(["CRITICAL", "HIGH", "MEDIUM", "LOW"]), default=None)
|
|
1238
|
+
@click.option("--skip-oauth", "skip_oauth", is_flag=True, default=False,
|
|
1239
|
+
help="Skip OAuth tests against external authorization servers.")
|
|
1232
1240
|
def redteam_mcp_auth(
|
|
1233
1241
|
target: str | None, stdio_cmd: str | None, auth_header: str | None,
|
|
1234
1242
|
timeout: float, fmt: str, output_path: str | None, verbose: bool, fail_on: str | None,
|
|
1243
|
+
skip_oauth: bool,
|
|
1235
1244
|
) -> None:
|
|
1236
1245
|
"""Test authentication bypass — calls every tool with invalid credentials.
|
|
1237
1246
|
|
|
@@ -1281,6 +1290,7 @@ def redteam_mcp_auth(
|
|
|
1281
1290
|
from agentsentinel_cli.redteam.mcp_oauth import run_oauth
|
|
1282
1291
|
oauth_findings, oauth_count = run_oauth(
|
|
1283
1292
|
url=target, original_headers=headers, timeout=timeout, verbose=verbose,
|
|
1293
|
+
skip_external=skip_oauth,
|
|
1284
1294
|
)
|
|
1285
1295
|
findings = findings + oauth_findings
|
|
1286
1296
|
scenarios_tested += oauth_count
|
|
@@ -1557,10 +1567,13 @@ def redteam_mcp_fuzz(
|
|
|
1557
1567
|
help="Save complete JSON evidence bundle.")
|
|
1558
1568
|
@click.option("--verbose", "-v", is_flag=True, default=False)
|
|
1559
1569
|
@click.option("--fail-on", type=click.Choice(["CRITICAL", "HIGH", "MEDIUM", "LOW"]), default=None)
|
|
1570
|
+
@click.option("--skip-oauth", "skip_oauth", is_flag=True, default=False,
|
|
1571
|
+
help="Skip OAuth tests against external authorization servers.")
|
|
1560
1572
|
def redteam_mcp_full(
|
|
1561
1573
|
target: str | None, stdio_cmd: str | None, auth_header: str | None,
|
|
1562
1574
|
intensity: str, include_dangerous: bool,
|
|
1563
1575
|
timeout: float, fmt: str, output_path: str | None, verbose: bool, fail_on: str | None,
|
|
1576
|
+
skip_oauth: bool,
|
|
1564
1577
|
) -> None:
|
|
1565
1578
|
"""Run all red-team modules in sequence — full engagement.
|
|
1566
1579
|
|
|
@@ -1610,6 +1623,7 @@ def redteam_mcp_full(
|
|
|
1610
1623
|
|
|
1611
1624
|
oauth_findings, oauth_count = run_oauth(
|
|
1612
1625
|
url=target, original_headers=headers, timeout=timeout, verbose=verbose,
|
|
1626
|
+
skip_external=skip_oauth,
|
|
1613
1627
|
)
|
|
1614
1628
|
all_findings.extend(oauth_findings)
|
|
1615
1629
|
total_attacks += oauth_count
|
|
@@ -1673,14 +1687,16 @@ def redteam_mcp_full(
|
|
|
1673
1687
|
total_attacks += fuzz_count
|
|
1674
1688
|
|
|
1675
1689
|
except McpAuthRequired as exc:
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1690
|
+
if fmt == "text":
|
|
1691
|
+
console.print(f"\n[bold yellow]Auth required[/bold yellow] (HTTP {exc.status_code})")
|
|
1692
|
+
console.print(" Preauth + OAuth findings above are still valid without a token.")
|
|
1693
|
+
console.print(" Use: [bold]--auth-header 'Authorization: Bearer <token>'[/bold]")
|
|
1679
1694
|
if not all_findings:
|
|
1680
1695
|
sys.exit(1)
|
|
1681
1696
|
# Fall through — report whatever preauth/oauth found
|
|
1682
1697
|
except McpError as exc:
|
|
1683
|
-
|
|
1698
|
+
if fmt == "text":
|
|
1699
|
+
console.print(f"\n[red]Connection failed:[/red] {exc}")
|
|
1684
1700
|
if not all_findings:
|
|
1685
1701
|
sys.exit(1)
|
|
1686
1702
|
|
|
@@ -32,6 +32,7 @@ def run_oauth(
|
|
|
32
32
|
original_headers: dict[str, str],
|
|
33
33
|
timeout: float,
|
|
34
34
|
verbose: bool,
|
|
35
|
+
skip_external: bool = False,
|
|
35
36
|
) -> tuple[list[RedTeamFinding], int]:
|
|
36
37
|
"""
|
|
37
38
|
Run OAuth 2.0 attack surface tests.
|
|
@@ -45,16 +46,59 @@ def run_oauth(
|
|
|
45
46
|
findings: list[RedTeamFinding] = []
|
|
46
47
|
probes = 0
|
|
47
48
|
parsed = urllib.parse.urlparse(url.rstrip("/"))
|
|
48
|
-
|
|
49
|
+
mcp_origin = f"{parsed.scheme}://{parsed.netloc}"
|
|
49
50
|
|
|
50
51
|
with httpx.Client(timeout=timeout, follow_redirects=True) as client:
|
|
51
|
-
meta = _discover_metadata(client,
|
|
52
|
+
meta, as_origin, is_external = _discover_metadata(client, mcp_origin)
|
|
52
53
|
|
|
53
54
|
if meta is None:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
return findings, probes + 3 # count the discovery probes
|
|
56
|
+
|
|
57
|
+
probes += 3
|
|
58
|
+
|
|
59
|
+
# Report external AS — always emit the INFO, then optionally skip tests
|
|
60
|
+
if is_external:
|
|
61
|
+
findings.append(RedTeamFinding(
|
|
62
|
+
attack_type="oauth",
|
|
63
|
+
severity="INFO",
|
|
64
|
+
title=f"External OAuth AS discovered — {as_origin}",
|
|
65
|
+
tool_name="<auth-server>",
|
|
66
|
+
parameter=None,
|
|
67
|
+
payload=f"GET {mcp_origin}/.well-known/oauth-protected-resource",
|
|
68
|
+
evidence=(
|
|
69
|
+
f"MCP server ({mcp_origin}) delegates authentication to:\n"
|
|
70
|
+
f" Authorization server: {as_origin}\n"
|
|
71
|
+
f" token_endpoint: {meta.get('token_endpoint', 'unknown')}"
|
|
72
|
+
),
|
|
73
|
+
exploit_scenario=(
|
|
74
|
+
"The MCP server follows the MCP 2025 spec and delegates OAuth to a "
|
|
75
|
+
f"separate authorization server at {as_origin}. "
|
|
76
|
+
"All OAuth attack tests below target that AS directly. "
|
|
77
|
+
"Use --skip-oauth if this AS is out of scope for your engagement."
|
|
78
|
+
),
|
|
79
|
+
mitre_id="T1078.004",
|
|
80
|
+
owasp_id="ASI06",
|
|
81
|
+
confidence="HIGH",
|
|
82
|
+
))
|
|
83
|
+
|
|
84
|
+
if skip_external:
|
|
85
|
+
findings.append(RedTeamFinding(
|
|
86
|
+
attack_type="oauth",
|
|
87
|
+
severity="INFO",
|
|
88
|
+
title=f"OAuth tests skipped — external AS out of scope (--skip-oauth)",
|
|
89
|
+
tool_name="<auth-server>",
|
|
90
|
+
parameter=None,
|
|
91
|
+
payload="",
|
|
92
|
+
evidence=f"Authorization server at {as_origin} not tested per --skip-oauth.",
|
|
93
|
+
exploit_scenario=(
|
|
94
|
+
"OAuth attack tests were suppressed because --skip-oauth was set. "
|
|
95
|
+
"Re-run without --skip-oauth if you have authorization to test the AS."
|
|
96
|
+
),
|
|
97
|
+
mitre_id=None,
|
|
98
|
+
owasp_id=None,
|
|
99
|
+
confidence="HIGH",
|
|
100
|
+
))
|
|
101
|
+
return findings, probes
|
|
58
102
|
|
|
59
103
|
# Tests that don't need a valid token
|
|
60
104
|
probes += _test_public_registration(client, meta, findings, verbose)
|
|
@@ -71,16 +115,49 @@ def run_oauth(
|
|
|
71
115
|
return findings, probes
|
|
72
116
|
|
|
73
117
|
|
|
74
|
-
def _discover_metadata(
|
|
75
|
-
|
|
76
|
-
|
|
118
|
+
def _discover_metadata(
|
|
119
|
+
client,
|
|
120
|
+
mcp_origin: str,
|
|
121
|
+
) -> tuple[dict | None, str, bool]:
|
|
122
|
+
"""
|
|
123
|
+
Follow the MCP 2025 OAuth discovery chain.
|
|
124
|
+
|
|
125
|
+
Returns (metadata, as_origin, is_external):
|
|
126
|
+
metadata — OAuth AS metadata dict, or None if no AS found
|
|
127
|
+
as_origin — base URL where the AS lives (may differ from MCP server)
|
|
128
|
+
is_external — True when the AS is hosted on a different origin
|
|
129
|
+
"""
|
|
130
|
+
# Step 1: MCP 2025 spec — oauth-protected-resource on the MCP server
|
|
131
|
+
# advertises which authorization server to use
|
|
132
|
+
try:
|
|
133
|
+
resp = client.get(f"{mcp_origin}/.well-known/oauth-protected-resource")
|
|
134
|
+
if resp.status_code == 200:
|
|
135
|
+
protected = resp.json()
|
|
136
|
+
auth_servers = protected.get("authorization_servers", [])
|
|
137
|
+
if auth_servers:
|
|
138
|
+
as_base = auth_servers[0].rstrip("/")
|
|
139
|
+
as_meta = _fetch_as_metadata(client, as_base)
|
|
140
|
+
if as_meta:
|
|
141
|
+
return as_meta, as_base, _is_different_origin(mcp_origin, as_base)
|
|
142
|
+
except Exception:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
# Step 2: Co-located AS — check AS metadata directly on MCP server origin
|
|
146
|
+
as_meta = _fetch_as_metadata(client, mcp_origin)
|
|
147
|
+
if as_meta:
|
|
148
|
+
return as_meta, mcp_origin, False
|
|
149
|
+
|
|
150
|
+
return None, mcp_origin, False
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _fetch_as_metadata(client, base_url: str) -> dict | None:
|
|
154
|
+
"""Try standard OAuth AS discovery paths on a given base URL."""
|
|
155
|
+
for path in [
|
|
77
156
|
"/.well-known/oauth-authorization-server",
|
|
78
157
|
"/.well-known/openid-configuration",
|
|
79
|
-
|
|
80
|
-
]
|
|
81
|
-
for path in paths:
|
|
158
|
+
]:
|
|
82
159
|
try:
|
|
83
|
-
resp = client.get(f"{
|
|
160
|
+
resp = client.get(f"{base_url}{path}")
|
|
84
161
|
if resp.status_code == 200:
|
|
85
162
|
return resp.json()
|
|
86
163
|
except Exception:
|
|
@@ -88,6 +165,13 @@ def _discover_metadata(client, origin: str) -> dict | None:
|
|
|
88
165
|
return None
|
|
89
166
|
|
|
90
167
|
|
|
168
|
+
def _is_different_origin(url1: str, url2: str) -> bool:
|
|
169
|
+
"""Return True when url1 and url2 have different scheme+host+port."""
|
|
170
|
+
p1 = urllib.parse.urlparse(url1)
|
|
171
|
+
p2 = urllib.parse.urlparse(url2)
|
|
172
|
+
return p1.netloc != p2.netloc
|
|
173
|
+
|
|
174
|
+
|
|
91
175
|
def _test_public_registration(
|
|
92
176
|
client,
|
|
93
177
|
meta: dict,
|
{agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/redteam/mcp_preauth.py
RENAMED
|
@@ -291,6 +291,10 @@ def _probe_oauth_metadata(
|
|
|
291
291
|
parsed = urllib.parse.urlparse(base)
|
|
292
292
|
origin = f"{parsed.scheme}://{parsed.netloc}"
|
|
293
293
|
|
|
294
|
+
# Track registration endpoints already reported to avoid duplicate findings
|
|
295
|
+
# when both oauth-authorization-server and openid-configuration expose the same URL
|
|
296
|
+
seen_reg_endpoints: set[str] = set()
|
|
297
|
+
|
|
294
298
|
for path in _OAUTH_META_PATHS:
|
|
295
299
|
probes += 1
|
|
296
300
|
try:
|
|
@@ -306,7 +310,68 @@ def _probe_oauth_metadata(
|
|
|
306
310
|
except Exception:
|
|
307
311
|
continue
|
|
308
312
|
|
|
309
|
-
#
|
|
313
|
+
# ── oauth-protected-resource (MCP 2025 spec) ──────────────────────────
|
|
314
|
+
# This document is on the MCP *resource* server and points to the real AS.
|
|
315
|
+
if path == "/.well-known/oauth-protected-resource":
|
|
316
|
+
auth_servers = meta.get("authorization_servers", [])
|
|
317
|
+
resource_fields: list[str] = []
|
|
318
|
+
for key in ("resource", "scopes_provided", "bearer_methods_supported"):
|
|
319
|
+
if key in meta:
|
|
320
|
+
resource_fields.append(f"{key}: {meta[key]}")
|
|
321
|
+
|
|
322
|
+
if auth_servers:
|
|
323
|
+
as_url = auth_servers[0]
|
|
324
|
+
is_ext = _netloc(origin) != _netloc(as_url)
|
|
325
|
+
placement = "external" if is_ext else "co-located"
|
|
326
|
+
findings.append(RedTeamFinding(
|
|
327
|
+
attack_type="preauth",
|
|
328
|
+
severity="INFO",
|
|
329
|
+
title=(
|
|
330
|
+
f"MCP 2025 OAuth discovery: AS at {as_url} ({placement})"
|
|
331
|
+
),
|
|
332
|
+
tool_name="<server>",
|
|
333
|
+
parameter=None,
|
|
334
|
+
payload=f"GET {path}",
|
|
335
|
+
evidence=(
|
|
336
|
+
f"authorization_servers: {auth_servers}\n"
|
|
337
|
+
+ "\n".join(resource_fields[:3])
|
|
338
|
+
),
|
|
339
|
+
exploit_scenario=(
|
|
340
|
+
f"The MCP server advertises its authorization server at {as_url}. "
|
|
341
|
+
+ (
|
|
342
|
+
"The AS is hosted on a separate origin — OAuth attack tests "
|
|
343
|
+
"should target that AS directly. "
|
|
344
|
+
"Use `sentinel redteam mcp auth <mcp-url>` to follow the chain "
|
|
345
|
+
"and test the external AS."
|
|
346
|
+
if is_ext else
|
|
347
|
+
"The AS is co-located on the same host — OAuth tests against "
|
|
348
|
+
"this origin will reach the authorization server."
|
|
349
|
+
)
|
|
350
|
+
),
|
|
351
|
+
mitre_id="T1590",
|
|
352
|
+
owasp_id=None,
|
|
353
|
+
confidence="HIGH",
|
|
354
|
+
))
|
|
355
|
+
else:
|
|
356
|
+
findings.append(RedTeamFinding(
|
|
357
|
+
attack_type="preauth",
|
|
358
|
+
severity="INFO",
|
|
359
|
+
title=f"MCP protected-resource metadata discovered — {path}",
|
|
360
|
+
tool_name="<server>",
|
|
361
|
+
parameter=None,
|
|
362
|
+
payload=f"GET {path}",
|
|
363
|
+
evidence="\n".join(resource_fields[:6]),
|
|
364
|
+
exploit_scenario=(
|
|
365
|
+
"The MCP server exposes its OAuth protected-resource document. "
|
|
366
|
+
"This identifies the authorization server and permitted scopes."
|
|
367
|
+
),
|
|
368
|
+
mitre_id="T1590",
|
|
369
|
+
owasp_id=None,
|
|
370
|
+
confidence="HIGH",
|
|
371
|
+
))
|
|
372
|
+
continue # rest of the loop handles AS metadata paths
|
|
373
|
+
|
|
374
|
+
# ── oauth-authorization-server / openid-configuration ─────────────────
|
|
310
375
|
endpoints: list[str] = []
|
|
311
376
|
for key in ("authorization_endpoint", "token_endpoint", "registration_endpoint",
|
|
312
377
|
"introspection_endpoint", "revocation_endpoint"):
|
|
@@ -318,8 +383,10 @@ def _probe_oauth_metadata(
|
|
|
318
383
|
|
|
319
384
|
has_registration = "registration_endpoint" in meta
|
|
320
385
|
has_implicit = "implicit" in grant_types or "token" in grant_types
|
|
386
|
+
reg_url = meta.get("registration_endpoint", "")
|
|
321
387
|
|
|
322
|
-
if has_registration:
|
|
388
|
+
if has_registration and reg_url not in seen_reg_endpoints:
|
|
389
|
+
seen_reg_endpoints.add(reg_url)
|
|
323
390
|
findings.append(RedTeamFinding(
|
|
324
391
|
attack_type="preauth",
|
|
325
392
|
severity="HIGH",
|
|
@@ -328,7 +395,7 @@ def _probe_oauth_metadata(
|
|
|
328
395
|
parameter=None,
|
|
329
396
|
payload=f"GET {path}",
|
|
330
397
|
evidence=(
|
|
331
|
-
f"registration_endpoint: {
|
|
398
|
+
f"registration_endpoint: {reg_url}\n"
|
|
332
399
|
+ "\n".join(endpoints[:5])
|
|
333
400
|
),
|
|
334
401
|
exploit_scenario=(
|
|
@@ -346,6 +413,8 @@ def _probe_oauth_metadata(
|
|
|
346
413
|
"Disable public registration unless explicitly required."
|
|
347
414
|
),
|
|
348
415
|
))
|
|
416
|
+
elif has_registration and reg_url in seen_reg_endpoints:
|
|
417
|
+
pass # same registration_endpoint already reported via another path
|
|
349
418
|
elif has_implicit:
|
|
350
419
|
findings.append(RedTeamFinding(
|
|
351
420
|
attack_type="preauth",
|
|
@@ -369,7 +438,6 @@ def _probe_oauth_metadata(
|
|
|
369
438
|
),
|
|
370
439
|
))
|
|
371
440
|
else:
|
|
372
|
-
# Just an INFO finding for the metadata itself
|
|
373
441
|
findings.append(RedTeamFinding(
|
|
374
442
|
attack_type="preauth",
|
|
375
443
|
severity="INFO",
|
|
@@ -390,6 +458,11 @@ def _probe_oauth_metadata(
|
|
|
390
458
|
return probes
|
|
391
459
|
|
|
392
460
|
|
|
461
|
+
def _netloc(url: str) -> str:
|
|
462
|
+
"""Extract scheme+netloc from a URL for origin comparison."""
|
|
463
|
+
return urllib.parse.urlparse(url).netloc
|
|
464
|
+
|
|
465
|
+
|
|
393
466
|
def _probe_info_paths(
|
|
394
467
|
client,
|
|
395
468
|
base: str,
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentsentinel-cli"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.7"
|
|
8
8
|
description = "AI agent and MCP server security scanner — discovery, static analysis, supply chain audit, and multi-agent trust analysis"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentsentinel_cli-0.9.6 → agentsentinel_cli-0.9.7}/agentsentinel_cli/supply_chain_report.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|