agentsentinel-cli 0.9.4__tar.gz → 0.9.6__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.4 → agentsentinel_cli-0.9.6}/PKG-INFO +1 -1
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/cli.py +99 -11
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/redteam/mcp_fuzz.py +23 -5
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/redteam/mcp_inject.py +29 -0
- agentsentinel_cli-0.9.6/agentsentinel_cli/redteam/mcp_oauth.py +523 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/redteam/mcp_poison.py +72 -0
- agentsentinel_cli-0.9.6/agentsentinel_cli/redteam/mcp_preauth.py +614 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/redteam/mcp_recon.py +15 -2
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/redteam/models.py +1 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/redteam/payloads.py +8 -3
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/redteam/report.py +90 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/pyproject.toml +1 -1
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/.gitignore +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/DOCUMENTATION.md +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/LICENSE +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/README.md +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/__init__.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/a2a_report.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/a2a_rules.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/a2a_scanner.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/discover.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/discover_report.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/fingerprint.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/frameworks.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/host_report.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/host_rules.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/host_scanner.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/inspect.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/inspect_report.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/mcp_client.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/mcp_report.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/mcp_rules.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/redteam/__init__.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/redteam/mcp_auth.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/redteam/transport.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/report.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/rules.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/scanner.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/secrets.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/secrets_report.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/secrets_rules.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/supply_chain_ai.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/supply_chain_report.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/agentsentinel_cli/supply_chain_rules.py +0 -0
- {agentsentinel_cli-0.9.4 → agentsentinel_cli-0.9.6}/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.6
|
|
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
|
|
@@ -1157,6 +1157,66 @@ def redteam_mcp_recon(
|
|
|
1157
1157
|
_check_exit(findings, fail_on)
|
|
1158
1158
|
|
|
1159
1159
|
|
|
1160
|
+
# ── sentinel redteam mcp preauth ─────────────────────────────────────────────
|
|
1161
|
+
|
|
1162
|
+
@redteam_mcp_group.command("preauth")
|
|
1163
|
+
@click.argument("target", required=False, metavar="URL")
|
|
1164
|
+
@click.option("--timeout", default=15.0, show_default=True, metavar="SECONDS")
|
|
1165
|
+
@click.option("--format", "fmt", type=click.Choice(["text", "json"]), default="text")
|
|
1166
|
+
@click.option("--output", "output_path", default=None, metavar="FILE")
|
|
1167
|
+
@click.option("--verbose", "-v", is_flag=True, default=False)
|
|
1168
|
+
@click.option("--fail-on", type=click.Choice(["CRITICAL", "HIGH", "MEDIUM", "LOW"]), default=None)
|
|
1169
|
+
def redteam_mcp_preauth(
|
|
1170
|
+
target: str | None, timeout: float, fmt: str,
|
|
1171
|
+
output_path: str | None, verbose: bool, fail_on: str | None,
|
|
1172
|
+
) -> None:
|
|
1173
|
+
"""HTTP-layer fingerprinting — runs with zero credentials.
|
|
1174
|
+
|
|
1175
|
+
Probes the server before any MCP initialize attempt: CORS config,
|
|
1176
|
+
OAuth metadata, version disclosure, unauthenticated paths, SSE stream.
|
|
1177
|
+
Produces findings even when the server blocks unauthenticated MCP access.
|
|
1178
|
+
|
|
1179
|
+
\b
|
|
1180
|
+
Examples:
|
|
1181
|
+
sentinel redteam mcp preauth http://localhost:3000
|
|
1182
|
+
sentinel redteam mcp preauth http://localhost:3000 --verbose
|
|
1183
|
+
"""
|
|
1184
|
+
import time
|
|
1185
|
+
from agentsentinel_cli.redteam.mcp_preauth import run_preauth
|
|
1186
|
+
from agentsentinel_cli.redteam.mcp_oauth import run_oauth
|
|
1187
|
+
from agentsentinel_cli.redteam.models import RedTeamResult
|
|
1188
|
+
from agentsentinel_cli.redteam.report import print_redteam_result, as_redteam_json
|
|
1189
|
+
|
|
1190
|
+
if not target:
|
|
1191
|
+
console.print("[red]Error:[/red] provide a URL.")
|
|
1192
|
+
console.print(" Example: [dim]sentinel redteam mcp preauth http://localhost:3000[/dim]")
|
|
1193
|
+
sys.exit(1)
|
|
1194
|
+
|
|
1195
|
+
target = _normalize_url(target)
|
|
1196
|
+
t0 = time.monotonic()
|
|
1197
|
+
|
|
1198
|
+
preauth_findings, preauth_count = run_preauth(url=target, timeout=timeout, verbose=verbose)
|
|
1199
|
+
oauth_findings, oauth_count = run_oauth(
|
|
1200
|
+
url=target, original_headers={}, timeout=timeout, verbose=verbose,
|
|
1201
|
+
)
|
|
1202
|
+
all_findings = preauth_findings + oauth_findings
|
|
1203
|
+
|
|
1204
|
+
result = RedTeamResult(
|
|
1205
|
+
target=target, server_name="unknown", server_version="unknown",
|
|
1206
|
+
transport="http", modules_run=["preauth", "oauth"], findings=all_findings,
|
|
1207
|
+
tool_count=0, attack_count=preauth_count + oauth_count,
|
|
1208
|
+
duration_s=time.monotonic() - t0,
|
|
1209
|
+
)
|
|
1210
|
+
|
|
1211
|
+
if fmt == "json":
|
|
1212
|
+
click.echo(as_redteam_json(result))
|
|
1213
|
+
else:
|
|
1214
|
+
print_redteam_result(result, verbose)
|
|
1215
|
+
|
|
1216
|
+
_save_output(output_path, result)
|
|
1217
|
+
_check_exit(all_findings, fail_on)
|
|
1218
|
+
|
|
1219
|
+
|
|
1160
1220
|
# ── sentinel redteam mcp auth ─────────────────────────────────────────────────
|
|
1161
1221
|
|
|
1162
1222
|
@redteam_mcp_group.command("auth")
|
|
@@ -1216,9 +1276,18 @@ def redteam_mcp_auth(
|
|
|
1216
1276
|
timeout=timeout, verbose=verbose,
|
|
1217
1277
|
)
|
|
1218
1278
|
|
|
1279
|
+
# OAuth 2.0 attack surface (HTTP-layer, no session needed)
|
|
1280
|
+
if target and not stdio_cmd:
|
|
1281
|
+
from agentsentinel_cli.redteam.mcp_oauth import run_oauth
|
|
1282
|
+
oauth_findings, oauth_count = run_oauth(
|
|
1283
|
+
url=target, original_headers=headers, timeout=timeout, verbose=verbose,
|
|
1284
|
+
)
|
|
1285
|
+
findings = findings + oauth_findings
|
|
1286
|
+
scenarios_tested += oauth_count
|
|
1287
|
+
|
|
1219
1288
|
result = RedTeamResult(
|
|
1220
1289
|
target=display, server_name=server_name, server_version=server_version,
|
|
1221
|
-
transport=transport, modules_run=["auth"], findings=findings,
|
|
1290
|
+
transport=transport, modules_run=["auth", "oauth"], findings=findings,
|
|
1222
1291
|
tool_count=tool_count, attack_count=scenarios_tested,
|
|
1223
1292
|
duration_s=time.monotonic() - t0,
|
|
1224
1293
|
)
|
|
@@ -1507,6 +1576,8 @@ def redteam_mcp_full(
|
|
|
1507
1576
|
"""
|
|
1508
1577
|
import time
|
|
1509
1578
|
from agentsentinel_cli.redteam.transport import RedTeamSession
|
|
1579
|
+
from agentsentinel_cli.redteam.mcp_preauth import run_preauth
|
|
1580
|
+
from agentsentinel_cli.redteam.mcp_oauth import run_oauth
|
|
1510
1581
|
from agentsentinel_cli.redteam.mcp_recon import run_recon
|
|
1511
1582
|
from agentsentinel_cli.redteam.mcp_auth import run_auth_bypass
|
|
1512
1583
|
from agentsentinel_cli.redteam.mcp_inject import run_inject
|
|
@@ -1529,7 +1600,21 @@ def redteam_mcp_full(
|
|
|
1529
1600
|
|
|
1530
1601
|
t0 = time.monotonic()
|
|
1531
1602
|
|
|
1532
|
-
# ── Phase 1–
|
|
1603
|
+
# ── Phase 1–2: HTTP-layer probes — no session or credentials required ────
|
|
1604
|
+
if target and not stdio_cmd:
|
|
1605
|
+
preauth_findings, preauth_count = run_preauth(
|
|
1606
|
+
url=target, timeout=timeout, verbose=verbose,
|
|
1607
|
+
)
|
|
1608
|
+
all_findings.extend(preauth_findings)
|
|
1609
|
+
total_attacks += preauth_count
|
|
1610
|
+
|
|
1611
|
+
oauth_findings, oauth_count = run_oauth(
|
|
1612
|
+
url=target, original_headers=headers, timeout=timeout, verbose=verbose,
|
|
1613
|
+
)
|
|
1614
|
+
all_findings.extend(oauth_findings)
|
|
1615
|
+
total_attacks += oauth_count
|
|
1616
|
+
|
|
1617
|
+
# ── Phase 3–7: run inside a single persistent MCP session ───────────────
|
|
1533
1618
|
try:
|
|
1534
1619
|
with RedTeamSession(url=target, stdio_cmd=stdio_cmd,
|
|
1535
1620
|
extra_headers=headers, timeout=timeout) as session:
|
|
@@ -1554,11 +1639,11 @@ def redteam_mcp_full(
|
|
|
1554
1639
|
transient=True,
|
|
1555
1640
|
) as progress:
|
|
1556
1641
|
|
|
1557
|
-
task = progress.add_task("Phase
|
|
1642
|
+
task = progress.add_task("Phase 3/7 — recon…", total=None)
|
|
1558
1643
|
recon_findings, _ = run_recon(session, verbose)
|
|
1559
1644
|
all_findings.extend(recon_findings)
|
|
1560
1645
|
|
|
1561
|
-
progress.update(task, description="Phase
|
|
1646
|
+
progress.update(task, description="Phase 4/7 — auth bypass…")
|
|
1562
1647
|
auth_findings, auth_scenarios = run_auth_bypass(
|
|
1563
1648
|
url=target, stdio_cmd=stdio_cmd,
|
|
1564
1649
|
original_headers=headers, timeout=timeout, verbose=verbose,
|
|
@@ -1566,10 +1651,9 @@ def redteam_mcp_full(
|
|
|
1566
1651
|
all_findings.extend(auth_findings)
|
|
1567
1652
|
total_attacks += auth_scenarios
|
|
1568
1653
|
|
|
1569
|
-
progress.update(task, description="Phase
|
|
1654
|
+
progress.update(task, description="Phase 5/7 — injection…")
|
|
1570
1655
|
inject_findings, inject_count = run_inject(
|
|
1571
1656
|
session,
|
|
1572
|
-
# LLM injection is handled by poison phase — no duplication
|
|
1573
1657
|
techniques=["traverse", "ssrf", "cmd", "sqli"],
|
|
1574
1658
|
intensity=intensity,
|
|
1575
1659
|
include_dangerous=include_dangerous,
|
|
@@ -1578,30 +1662,34 @@ def redteam_mcp_full(
|
|
|
1578
1662
|
all_findings.extend(inject_findings)
|
|
1579
1663
|
total_attacks += inject_count
|
|
1580
1664
|
|
|
1581
|
-
progress.update(task, description="Phase
|
|
1665
|
+
progress.update(task, description="Phase 6/7 — poisoning…")
|
|
1582
1666
|
poison_findings, poison_count = run_poison(session, verbose)
|
|
1583
1667
|
all_findings.extend(poison_findings)
|
|
1584
1668
|
total_attacks += poison_count
|
|
1585
1669
|
|
|
1586
|
-
progress.update(task, description="Phase
|
|
1670
|
+
progress.update(task, description="Phase 7/7 — fuzzing…")
|
|
1587
1671
|
fuzz_findings, fuzz_count = run_fuzz(session, verbose)
|
|
1588
1672
|
all_findings.extend(fuzz_findings)
|
|
1589
1673
|
total_attacks += fuzz_count
|
|
1590
1674
|
|
|
1591
1675
|
except McpAuthRequired as exc:
|
|
1592
1676
|
console.print(f"\n[bold yellow]Auth required[/bold yellow] (HTTP {exc.status_code})")
|
|
1677
|
+
console.print(" Preauth + OAuth findings above are still valid without a token.")
|
|
1593
1678
|
console.print(" Use: [bold]--auth-header 'Authorization: Bearer <token>'[/bold]")
|
|
1594
|
-
|
|
1679
|
+
if not all_findings:
|
|
1680
|
+
sys.exit(1)
|
|
1681
|
+
# Fall through — report whatever preauth/oauth found
|
|
1595
1682
|
except McpError as exc:
|
|
1596
1683
|
console.print(f"\n[red]Connection failed:[/red] {exc}")
|
|
1597
|
-
|
|
1684
|
+
if not all_findings:
|
|
1685
|
+
sys.exit(1)
|
|
1598
1686
|
|
|
1599
1687
|
result = RedTeamResult(
|
|
1600
1688
|
target=display,
|
|
1601
1689
|
server_name=server_name,
|
|
1602
1690
|
server_version=server_version,
|
|
1603
1691
|
transport=transport,
|
|
1604
|
-
modules_run=["recon", "auth", "inject", "poison", "fuzz"],
|
|
1692
|
+
modules_run=["preauth", "oauth", "recon", "auth", "inject", "poison", "fuzz"],
|
|
1605
1693
|
findings=all_findings,
|
|
1606
1694
|
tool_count=tool_count,
|
|
1607
1695
|
attack_count=total_attacks,
|
|
@@ -138,9 +138,13 @@ def _emit(
|
|
|
138
138
|
if is_reflection:
|
|
139
139
|
severity, title = "MEDIUM", "Input reflected in error response (no HTML encoding)"
|
|
140
140
|
scenario = (
|
|
141
|
-
f"Tool '{tool_name}' echoes user-controlled input without
|
|
142
|
-
"
|
|
143
|
-
"
|
|
141
|
+
f"Tool '{tool_name}' echoes user-controlled input without sanitization via parameter '{param}'. "
|
|
142
|
+
"Reflected content enters any connected agent's context window directly, making this an "
|
|
143
|
+
"injection vector for adversarial instructions embedded in the input."
|
|
144
|
+
)
|
|
145
|
+
remediation = (
|
|
146
|
+
"Sanitize user input before including it in error messages. "
|
|
147
|
+
"Strip or HTML-encode special characters in all error responses."
|
|
144
148
|
)
|
|
145
149
|
elif is_trace:
|
|
146
150
|
severity, title = "HIGH", "Unhandled exception — stack trace leaked"
|
|
@@ -149,21 +153,34 @@ def _emit(
|
|
|
149
153
|
"Stack traces reveal internal paths, library versions, and code structure "
|
|
150
154
|
"that attackers use to craft further exploits."
|
|
151
155
|
)
|
|
156
|
+
remediation = (
|
|
157
|
+
"Catch all exceptions server-side and return a generic error message. "
|
|
158
|
+
"Never expose stack traces, internal paths, or library names to clients."
|
|
159
|
+
)
|
|
152
160
|
elif is_template:
|
|
153
161
|
severity, title = "HIGH", "Template/expression injection confirmed"
|
|
154
162
|
scenario = (
|
|
155
|
-
f"Tool '{tool_name}' evaluated a template expression. "
|
|
163
|
+
f"Tool '{tool_name}' evaluated a template expression in user-controlled input. "
|
|
156
164
|
"Server-side template injection can escalate to arbitrary code execution."
|
|
157
165
|
)
|
|
166
|
+
remediation = (
|
|
167
|
+
"Disable server-side template evaluation of user-controlled strings. "
|
|
168
|
+
"Use a templating engine that escapes by default and never eval user input."
|
|
169
|
+
)
|
|
158
170
|
elif is_path:
|
|
159
171
|
severity, title = "MEDIUM", "Internal file path leaked in error response"
|
|
160
172
|
scenario = (
|
|
161
|
-
f"Tool '{tool_name}' exposes an internal filesystem path. "
|
|
173
|
+
f"Tool '{tool_name}' exposes an internal filesystem path in its error response. "
|
|
162
174
|
"Attackers use path disclosure to target specific files in traversal attacks."
|
|
163
175
|
)
|
|
176
|
+
remediation = (
|
|
177
|
+
"Sanitize error messages to exclude internal filesystem paths. "
|
|
178
|
+
"Return 'invalid input' without path context."
|
|
179
|
+
)
|
|
164
180
|
else:
|
|
165
181
|
severity, title = "LOW", "Unexpected information in fuzz response"
|
|
166
182
|
scenario = f"Tool '{tool_name}' returned unexpected content in response to malformed input."
|
|
183
|
+
remediation = "Review what the tool returns on unexpected input and tighten input validation."
|
|
167
184
|
|
|
168
185
|
param_str = f".{param}" if param else ""
|
|
169
186
|
findings.append(RedTeamFinding(
|
|
@@ -178,6 +195,7 @@ def _emit(
|
|
|
178
195
|
mitre_id="T1592.002",
|
|
179
196
|
owasp_id="ASI03",
|
|
180
197
|
confidence="HIGH",
|
|
198
|
+
remediation=remediation,
|
|
181
199
|
request_body={"tool": tool_name, "payload": repr(raw_payload)[:100]} if verbose else None,
|
|
182
200
|
response_body=result.raw_response[:500] if verbose else None,
|
|
183
201
|
))
|
|
@@ -26,6 +26,11 @@ _TECHNIQUE_META: dict[str, dict] = {
|
|
|
26
26
|
"severity": "CRITICAL",
|
|
27
27
|
"title": "Path traversal — arbitrary file read",
|
|
28
28
|
"scenario": "Attacker can read arbitrary files from the server filesystem via parameter '{param}'.",
|
|
29
|
+
"remediation": (
|
|
30
|
+
"Restrict file access to an explicit allowlist of base directories. "
|
|
31
|
+
"Reject paths containing `..` sequences and absolute paths outside the allowed root. "
|
|
32
|
+
"Use os.path.realpath() and verify the resolved path starts with the allowed base."
|
|
33
|
+
),
|
|
29
34
|
},
|
|
30
35
|
"ssrf": {
|
|
31
36
|
"mitre": "T1090.002",
|
|
@@ -33,6 +38,11 @@ _TECHNIQUE_META: dict[str, dict] = {
|
|
|
33
38
|
"severity": "CRITICAL",
|
|
34
39
|
"title": "SSRF — server-side request forgery",
|
|
35
40
|
"scenario": "Attacker can make the server issue requests to internal network addresses via '{param}'.",
|
|
41
|
+
"remediation": (
|
|
42
|
+
"Validate and allowlist permitted URL schemes and hosts. "
|
|
43
|
+
"Block RFC-1918 ranges (169.254.x.x, 10.x.x.x, 172.16-31.x.x, 192.168.x.x) and localhost. "
|
|
44
|
+
"Resolve DNS before allowlist checks to prevent TOCTOU bypasses."
|
|
45
|
+
),
|
|
36
46
|
},
|
|
37
47
|
"cmd": {
|
|
38
48
|
"mitre": "T1059",
|
|
@@ -40,6 +50,11 @@ _TECHNIQUE_META: dict[str, dict] = {
|
|
|
40
50
|
"severity": "CRITICAL",
|
|
41
51
|
"title": "Command injection — OS command execution",
|
|
42
52
|
"scenario": "Attacker can execute arbitrary OS commands on the host via parameter '{param}'.",
|
|
53
|
+
"remediation": (
|
|
54
|
+
"Never pass user-controlled input to a shell. "
|
|
55
|
+
"Use subprocess with an explicit argument list (no shell=True). "
|
|
56
|
+
"If shell execution is required, validate against a strict allowlist."
|
|
57
|
+
),
|
|
43
58
|
},
|
|
44
59
|
"sqli": {
|
|
45
60
|
"mitre": "T1190",
|
|
@@ -47,6 +62,11 @@ _TECHNIQUE_META: dict[str, dict] = {
|
|
|
47
62
|
"severity": "HIGH",
|
|
48
63
|
"title": "SQL injection — database query manipulation",
|
|
49
64
|
"scenario": "Attacker can manipulate backend SQL queries through parameter '{param}'.",
|
|
65
|
+
"remediation": (
|
|
66
|
+
"Use parameterized queries or a query builder. "
|
|
67
|
+
"Never interpolate user input into SQL strings. "
|
|
68
|
+
"Apply input length and character-set validation as a second layer."
|
|
69
|
+
),
|
|
50
70
|
},
|
|
51
71
|
"llm": {
|
|
52
72
|
"mitre": "AML.T0051.000",
|
|
@@ -57,6 +77,10 @@ _TECHNIQUE_META: dict[str, dict] = {
|
|
|
57
77
|
"Tool result containing adversarial instructions flows into any connected LLM context "
|
|
58
78
|
"via parameter '{param}'. Attacker-controlled input can override agent behaviour."
|
|
59
79
|
),
|
|
80
|
+
"remediation": (
|
|
81
|
+
"Do not reflect user-controlled parameter values verbatim in tool responses. "
|
|
82
|
+
"Validate inputs and reject or strip LLM instruction patterns before processing."
|
|
83
|
+
),
|
|
60
84
|
},
|
|
61
85
|
}
|
|
62
86
|
|
|
@@ -140,6 +164,10 @@ def run_inject(
|
|
|
140
164
|
mitre_id=meta["mitre"],
|
|
141
165
|
owasp_id=meta["owasp"],
|
|
142
166
|
confidence="MEDIUM",
|
|
167
|
+
remediation=(
|
|
168
|
+
"Do not echo raw user input in error messages. "
|
|
169
|
+
"Return a generic error that excludes the parameter value."
|
|
170
|
+
),
|
|
143
171
|
request_body={"tool": tool.name, "arguments": args} if verbose else None,
|
|
144
172
|
response_body=result.raw_response[:500] if verbose else None,
|
|
145
173
|
))
|
|
@@ -156,6 +184,7 @@ def run_inject(
|
|
|
156
184
|
mitre_id=meta["mitre"],
|
|
157
185
|
owasp_id=meta["owasp"],
|
|
158
186
|
confidence="HIGH",
|
|
187
|
+
remediation=meta.get("remediation"),
|
|
159
188
|
request_body={"tool": tool.name, "arguments": args} if verbose else None,
|
|
160
189
|
response_body=result.raw_response[:500] if verbose else None,
|
|
161
190
|
))
|