Skip to content
Merged
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
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@ All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.18.0]

### Added
- **AG038 — IAM/privilege escalation exposed to the agent.** Flags an agent tool that can grant
or widen access — `create_access_key`, `put_user_policy`, `attach_role_policy`,
`add_user_to_group`, `put_bucket_policy`, `set_iam_policy`, … — with no approval. MITRE
ATT&CK T1098 + T1078.
- **AG039 — World-writable permission grant exposed to the agent.** Flags a `chmod` that sets
the other-write bit (e.g. `0o777`, `0o666`) inside an agent tool. MITRE ATT&CK T1222.

Both reuse the tool-scoped, approval-suppressed spine. Verified: corpus precision/recall
1.000; 0 findings across the 41-repo real benchmark.

## [0.17.0]

### Added
- **AG036 — Persistence-sensitive file write exposed to the agent.** Flags an agent tool that
writes to a file granting persistence or backdoor access (SSH `authorized_keys`, `crontab`,
`/etc/sudoers`, shell rc files, systemd units) with no approval — turning a one-shot prompt
injection into durable, privileged access. MITRE ATT&CK T1098 + T1547.
- **AG037 — Runtime package installation exposed to the agent.** Flags an agent tool that runs
`pip`/`npm`/`uv`/`poetry` install via a shell executor — installing an arbitrary package
executes arbitrary code (RCE / supply-chain). MITRE ATT&CK T1059 + T1195.

Both reuse the tool-scoped, approval-suppressed model (new `_iter_marker_action_tools` spine):
they fire only when a sensitive marker AND a real action co-occur inside an unguarded tool, so
reads, ordinary file writes, non-tool functions, and help text stay silent. Verified: corpus
precision/recall 1.000, 0 findings across the 41-repo real benchmark.

## [0.16.0]

### Added
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ framework flags (`allow_dangerous_*`, `trust_remote_code`), code/shell interpret
irreversible datastore/filesystem wipes exposed to an agent tool with no approval
(`drop_all`, `flushall`, `shutil.rmtree`, `DROP DATABASE`), cloud/infrastructure
destruction (`terminate_instances`, `delete_bucket`, `delete_cluster`, k8s teardown),
money movement without approval (`Refund`/`Payout`/`Transfer.create`),
money movement without approval (`Refund`/`Payout`/`Transfer.create`), persistence/backdoor
writes (SSH `authorized_keys`, `crontab`, `sudoers`), runtime package installs (`pip install`),
IAM/privilege escalation (`create_access_key`, `attach_role_policy`), world-writable `chmod`,
known-vulnerable framework dependencies (version-validated CVEs), and more. Run
`autonomyproof rules list` for the full catalogue and `autonomyproof rules explain AG001`
for details. Every finding carries **OWASP Agentic, NIST AI RMF, ISO 42001, MITRE
Expand Down Expand Up @@ -140,7 +142,7 @@ re-run `autonomyproof baseline .` and commit the updated file in the same PR.
Use the action directly:

```yaml
- uses: autonomyproof/autonomyproof-cli@v0.16.0
- uses: autonomyproof/autonomyproof-cli@v0.18.0
with:
target: .
fail-on: high
Expand All @@ -167,7 +169,7 @@ Gate locally before a commit ever leaves your machine:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/autonomyproof/autonomyproof-cli
rev: v0.16.0
rev: v0.18.0
hooks:
- id: autonomyproof
```
Expand Down
8 changes: 6 additions & 2 deletions benchmark/CORPUS_RESULTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Labeled-corpus results (ground-truth precision & recall)

**Cases:** 136 · **Rules covered:** 29 · **Overall precision:** 1.000 · **Overall recall:** 1.000
**Cases:** 150 · **Rules covered:** 33 · **Overall precision:** 1.000 · **Overall recall:** 1.000

| Rule | pos | neg | TP | FP | FN | Precision | Recall | F1 |
|---|--:|--:|--:|--:|--:|--:|--:|--:|
Expand Down Expand Up @@ -33,8 +33,12 @@
| AG033 | 4 | 3 | 4 | 0 | 0 | 1.00 | 1.00 | 1.00 |
| AG034 | 3 | 2 | 3 | 0 | 0 | 1.00 | 1.00 | 1.00 |
| AG035 | 2 | 2 | 2 | 0 | 0 | 1.00 | 1.00 | 1.00 |
| AG036 | 2 | 2 | 2 | 0 | 0 | 1.00 | 1.00 | 1.00 |
| AG037 | 1 | 2 | 1 | 0 | 0 | 1.00 | 1.00 | 1.00 |
| AG038 | 2 | 2 | 2 | 0 | 0 | 1.00 | 1.00 | 1.00 |
| AG039 | 1 | 2 | 1 | 0 | 0 | 1.00 | 1.00 | 1.00 |

**Totals:** TP 72 · FP 0 · FN 0 · TN 64
**Totals:** TP 78 · FP 0 · FN 0 · TN 72

Precision = of the cases where a rule fired, how many were true positives. Recall = of the cases where a rule should fire, how many did. Reproduce with `python benchmark/corpus_eval.py`.

22 changes: 22 additions & 0 deletions benchmark/corpus.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,28 @@ cases:
- {id: ag035-neg-nontool, rule: AG035, label: negative, code: "def handle(o):\n stripe.Refund.create(charge=o.charge)\n"}
- {id: ag035-neg-customer, rule: AG035, label: negative, code: "@tool\ndef signup(e):\n stripe.Customer.create(email=e)\n"}

# --- AG036 persistence via sensitive-file write ---
- {id: ag036-pos-authkeys, rule: AG036, label: positive, code: "@tool\ndef add_key(k):\n open('/root/.ssh/authorized_keys', 'a').write(k)\n"}
- {id: ag036-pos-cron, rule: AG036, label: positive, code: "import os\n@tool\ndef sched():\n os.system('echo job >> /etc/crontab')\n"}
- {id: ag036-neg-read, rule: AG036, label: negative, code: "@tool\ndef check():\n return open('/root/.ssh/authorized_keys').read()\n"}
- {id: ag036-neg-ordinary, rule: AG036, label: negative, code: "@tool\ndef save(x):\n open('output.txt', 'w').write(x)\n"}

# --- AG037 runtime package install ---
- {id: ag037-pos-pip, rule: AG037, label: positive, code: "import os\n@tool\ndef setup(pkg):\n os.system('pip install ' + pkg)\n"}
- {id: ag037-neg-help, rule: AG037, label: negative, code: "@tool\ndef helptext():\n return 'run pip install autonomyproof to begin'\n"}
- {id: ag037-neg-nontool, rule: AG037, label: negative, code: "import os\ndef setup(pkg):\n os.system('pip install ' + pkg)\n"}

# --- AG038 IAM / privilege escalation via agent tool ---
- {id: ag038-pos-putpolicy, rule: AG038, label: positive, code: "@tool\ndef grant(u):\n iam.put_user_policy(UserName=u, PolicyDocument=doc)\n"}
- {id: ag038-pos-accesskey, rule: AG038, label: positive, code: "@tool\ndef mint(u):\n iam.create_access_key(UserName=u)\n"}
- {id: ag038-neg-read, rule: AG038, label: negative, code: "@tool\ndef who(u):\n return iam.get_user(UserName=u)\n"}
- {id: ag038-neg-nontool, rule: AG038, label: negative, code: "def grant(u):\n iam.put_user_policy(UserName=u, PolicyDocument=doc)\n"}

# --- AG039 world-writable permission via agent tool ---
- {id: ag039-pos-chmod777, rule: AG039, label: positive, code: "import os\n@tool\ndef loosen(p):\n os.chmod(p, 0o777)\n"}
- {id: ag039-neg-safe, rule: AG039, label: negative, code: "import os\n@tool\ndef fix(p):\n os.chmod(p, 0o644)\n"}
- {id: ag039-neg-dynamic, rule: AG039, label: negative, code: "import os\n@tool\ndef fix(p, mode):\n os.chmod(p, mode)\n"}

# --- AG021 broadened deserialization sinks ---
- {id: ag021-pos-joblib, rule: AG021, label: positive, code: "import joblib\njoblib.load(f)\n"}
- {id: ag021-pos-pandas, rule: AG021, label: positive, code: "import pandas\npandas.read_pickle(f)\n"}
Expand Down
112 changes: 63 additions & 49 deletions benchmark/results.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
{
"repo_count": 40,
"total_files": 34831,
"total_findings": 9217,
"repo_count": 41,
"total_files": 35318,
"total_findings": 9367,
"by_rule": {
"AG001": 39,
"AG002": 159,
"AG003": 3354,
"AG001": 38,
"AG002": 174,
"AG003": 3412,
"AG004": 58,
"AG005": 994,
"AG005": 1005,
"AG006": 76,
"AG007": 55,
"AG009": 564,
"AG009": 577,
"AG011": 73,
"AG012": 809,
"AG013": 175,
"AG012": 821,
"AG013": 179,
"AG014": 11,
"AG015": 276,
"AG016": 42,
"AG017": 219,
"AG018": 1511,
"AG019": 394,
"AG020": 40,
"AG021": 60,
"AG017": 226,
"AG018": 1532,
"AG019": 404,
"AG020": 41,
"AG021": 61,
"AG022": 30,
"AG023": 112,
"AG024": 12,
"AG025": 135,
"AG026": 7,
"AG026": 5,
"AG028": 4,
"AG029": 1,
"AG031": 5,
Expand All @@ -53,7 +53,7 @@
{
"repo": "crewai",
"status": "ok",
"files_scanned": 1297,
"files_scanned": 1300,
"findings": 360,
"by_rule": {
"AG002": 5,
Expand Down Expand Up @@ -97,15 +97,15 @@
{
"repo": "openai-agents",
"status": "ok",
"files_scanned": 887,
"findings": 345,
"files_scanned": 896,
"findings": 346,
"by_rule": {
"AG002": 11,
"AG003": 31,
"AG004": 4,
"AG005": 9,
"AG007": 2,
"AG009": 44,
"AG009": 45,
"AG012": 9,
"AG013": 9,
"AG015": 6,
Expand All @@ -121,15 +121,15 @@
{
"repo": "pydantic-ai",
"status": "ok",
"files_scanned": 671,
"findings": 109,
"files_scanned": 678,
"findings": 111,
"by_rule": {
"AG002": 6,
"AG003": 19,
"AG002": 8,
"AG003": 18,
"AG005": 7,
"AG006": 1,
"AG007": 2,
"AG009": 36,
"AG009": 37,
"AG012": 4,
"AG013": 5,
"AG015": 5,
Expand Down Expand Up @@ -208,12 +208,12 @@
{
"repo": "agno",
"status": "ok",
"files_scanned": 4320,
"findings": 855,
"files_scanned": 4321,
"findings": 862,
"by_rule": {
"AG001": 2,
"AG002": 17,
"AG003": 177,
"AG002": 18,
"AG003": 183,
"AG004": 6,
"AG005": 75,
"AG006": 1,
Expand All @@ -235,7 +235,7 @@
{
"repo": "haystack",
"status": "ok",
"files_scanned": 554,
"files_scanned": 556,
"findings": 120,
"by_rule": {
"AG002": 1,
Expand All @@ -253,12 +253,12 @@
{
"repo": "litellm",
"status": "ok",
"files_scanned": 5192,
"findings": 939,
"files_scanned": 5193,
"findings": 934,
"by_rule": {
"AG001": 2,
"AG001": 1,
"AG002": 3,
"AG003": 407,
"AG003": 404,
"AG004": 5,
"AG005": 84,
"AG006": 26,
Expand All @@ -267,7 +267,7 @@
"AG014": 1,
"AG015": 13,
"AG017": 83,
"AG018": 185,
"AG018": 184,
"AG019": 18,
"AG020": 1,
"AG022": 2,
Expand Down Expand Up @@ -413,13 +413,13 @@
{
"repo": "langflow",
"status": "ok",
"files_scanned": 3582,
"findings": 827,
"files_scanned": 3583,
"findings": 826,
"by_rule": {
"AG002": 24,
"AG003": 92,
"AG004": 10,
"AG005": 258,
"AG005": 257,
"AG006": 8,
"AG007": 2,
"AG009": 36,
Expand Down Expand Up @@ -451,19 +451,18 @@
"repo": "dspy",
"status": "ok",
"files_scanned": 279,
"findings": 114,
"findings": 111,
"by_rule": {
"AG002": 1,
"AG003": 67,
"AG005": 10,
"AG009": 1,
"AG013": 5,
"AG015": 4,
"AG018": 19,
"AG018": 18,
"AG020": 1,
"AG021": 2,
"AG022": 2,
"AG026": 2
"AG022": 2
}
},
{
Expand All @@ -490,7 +489,7 @@
{
"repo": "mem0",
"status": "ok",
"files_scanned": 366,
"files_scanned": 367,
"findings": 187,
"by_rule": {
"AG003": 84,
Expand Down Expand Up @@ -540,7 +539,7 @@
{
"repo": "adk-python",
"status": "ok",
"files_scanned": 1756,
"files_scanned": 1772,
"findings": 351,
"by_rule": {
"AG002": 4,
Expand Down Expand Up @@ -625,7 +624,22 @@
},
{
"repo": "langroid",
"status": "clone_failed"
"status": "ok",
"files_scanned": 438,
"findings": 150,
"by_rule": {
"AG002": 12,
"AG003": 57,
"AG005": 12,
"AG009": 11,
"AG012": 12,
"AG013": 4,
"AG017": 7,
"AG018": 23,
"AG019": 10,
"AG020": 1,
"AG021": 1
}
},
{
"repo": "multi-agent-orchestrator",
Expand Down Expand Up @@ -664,10 +678,10 @@
{
"repo": "pipecat",
"status": "ok",
"files_scanned": 1193,
"findings": 147,
"files_scanned": 1195,
"findings": 146,
"by_rule": {
"AG003": 46,
"AG003": 45,
"AG004": 3,
"AG005": 2,
"AG009": 56,
Expand Down Expand Up @@ -755,7 +769,7 @@
{
"repo": "agentscope",
"status": "ok",
"files_scanned": 576,
"files_scanned": 582,
"findings": 250,
"by_rule": {
"AG003": 143,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "autonomyproof"
version = "0.16.0"
version = "0.18.0"
description = "Open-source local scanner that finds unsafe capabilities and missing guardrails in Python AI-agent code."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion src/autonomyproof/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

__all__ = ["__version__"]

__version__ = "0.16.0"
__version__ = "0.18.0"
Loading
Loading