Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ python3 skills/agent-security/scripts/config_risk_summary.py \
< examples/high-risk-agent-config.json
```

Emit a Markdown summary for PR comments, issue updates, or human-readable reports:

```bash
python3 skills/agent-security/scripts/config_risk_summary.py \
--format markdown \
< examples/high-risk-agent-config.json
```

Score prompt-injection exposure from a config/status JSON object:

```bash
Expand Down
73 changes: 72 additions & 1 deletion skills/agent-security/scripts/config_risk_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,79 @@ def truthy(value: Any) -> bool:
return value is True or (isinstance(value, str) and value.lower() in {"true", "yes", "enabled", "on"})


def markdown_cell(value: Any, *, code: bool = False) -> str:
if value is None:
return ""
if isinstance(value, (list, tuple)):
text = ", ".join(str(item) for item in value)
else:
text = str(value)
text = text.replace("\\", "\\\\").replace("|", "\\|").replace("\n", "<br>")
if code and text:
return f"`{text}`"
return text


def render_markdown(summary: dict[str, Any]) -> str:
lines = ["# Agent Security Config Risk Summary", ""]
if summary["ok"]:
lines.append("**Overall:** no high/critical findings")
else:
lines.append("**Overall:** high risk findings present")
lines.append(f"**Risk count:** {summary['risk_count']}")
lines.append("")
lines.append("## Severity counts")
lines.append("")
if summary["counts"]:
for severity in ("error", "critical", "high", "warn", "info"):
count = summary["counts"].get(severity)
if count:
lines.append(f"- **{severity}:** {count}")
else:
lines.append("- No findings")
lines.append("")
lines.append("## Findings")
lines.append("")
findings = summary["findings"]
if not findings:
lines.append("No findings.")
lines.append("")
return "\n".join(lines)

lines.append("| Severity | Rule | Risk | Field | Recommendation |")
lines.append("| --- | --- | --- | --- | --- |")
for finding in findings:
field = finding.get("field") or finding.get("fields") or ""
recommendation = finding.get("recommendation") or finding.get("reason") or ""
details = []
for key in ("agent", "index", "risk_class", "value", "expected"):
if key in finding:
details.append(f"{key}={finding[key]}")
if details:
recommendation = " — ".join(part for part in [str(recommendation), "; ".join(details)] if part)
lines.append(
"| "
+ " | ".join(
[
markdown_cell(finding.get("severity")),
markdown_cell(finding.get("rule_id")),
markdown_cell(finding.get("risk")),
markdown_cell(field, code=bool(field)),
markdown_cell(recommendation),
]
)
+ " |"
)
lines.append("")
return "\n".join(lines)


def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--strict", action="store_true", help="exit nonzero on error/high/critical findings")
parser.add_argument("--fail-on", choices=["error", "critical", "high", "warn", "info"], default=None)
parser.add_argument("--compact", action="store_true", help="emit compact JSON")
parser.add_argument("--format", choices=["json", "markdown"], default="json", help="output format (default: json)")
args = parser.parse_args()

cfg, initial_findings = load_json()
Expand Down Expand Up @@ -221,7 +289,10 @@ def add(severity: str, risk: str, **extra: Any) -> None:
}
for f in findings:
summary["counts"][f["severity"]] = summary["counts"].get(f["severity"], 0) + 1
print(json.dumps(summary, separators=(",", ":") if args.compact else None, indent=None if args.compact else 2, sort_keys=True))
if args.format == "markdown":
print(render_markdown(summary))
else:
print(json.dumps(summary, separators=(",", ":") if args.compact else None, indent=None if args.compact else 2, sort_keys=True))

fail_on = args.fail_on or ("high" if args.strict else None)
if fail_on:
Expand Down
20 changes: 20 additions & 0 deletions tests/test_config_risk_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,23 @@ def test_key_findings_include_stable_rule_ids():
assert findings_by_risk["persistence_available_in_untrusted_content_context"]["rule_id"] == "ASG-003"
assert findings_by_risk["elevated_enabled_without_allowlist"]["rule_id"] == "ASG-004"
assert findings_by_risk["exec_security_full"]["rule_id"] == "ASG-008"


def test_markdown_format_renders_summary_and_findings_table():
payload = {
"browser": {"enabled": True, "ssrfPolicy": {"dangerouslyAllowPrivateNetwork": True}},
"bindings": [{"agentId": "shared", "match": {"channel": "discord", "peer": {"kind": "channel"}}}],
}
proc = run_script(payload, "--format", "markdown")
assert proc.returncode == 0
assert proc.stdout.startswith("# Agent Security Config Risk Summary\n")
assert "**Overall:** high risk findings present" in proc.stdout
assert "| Severity | Rule | Risk | Field | Recommendation |" in proc.stdout
assert "| high | ASG-002 | browser_private_network_allowed | `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork` | |" in proc.stdout
assert "| critical | ASG-006 | shared_channel_with_private_network_browser | | |" in proc.stdout


def test_markdown_format_escapes_table_pipes():
proc = run_script({"agents": {"list": [{"id": "agent|one", "tools": {"elevated": {"enabled": True}}}]}}, "--format", "markdown")
assert proc.returncode == 0
assert "agent\\|one" in proc.stdout
Loading