agentproof-scan 0.1.3__tar.gz → 0.2.0__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.
Files changed (30) hide show
  1. {agentproof_scan-0.1.3/agentproof_scan.egg-info → agentproof_scan-0.2.0}/PKG-INFO +182 -71
  2. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/README.md +181 -70
  3. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/__init__.py +1 -1
  4. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/base.py +48 -2
  5. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/simple_chatbot.py +15 -6
  6. agentproof_scan-0.2.0/agentproof_scan/adapters/simple_chatbot_multitype_canary.py +128 -0
  7. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/fingerprint.py +4 -1
  8. agentproof_scan-0.2.0/agentproof_scan/reasoning_adapters.py +112 -0
  9. agentproof_scan-0.2.0/agentproof_scan/reasoning_scan.py +188 -0
  10. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/scan.py +202 -56
  11. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/victim_agent.py +18 -6
  12. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0/agentproof_scan.egg-info}/PKG-INFO +182 -71
  13. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan.egg-info/SOURCES.txt +3 -0
  14. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/pyproject.toml +1 -1
  15. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/LICENSE +0 -0
  16. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/MANIFEST.in +0 -0
  17. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/__main__.py +0 -0
  18. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/__init__.py +0 -0
  19. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/external_starter.py +0 -0
  20. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/generic_http.py +0 -0
  21. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/llm_starter.py +0 -0
  22. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/simple_chatbot_canary.py +0 -0
  23. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/simple_chatbot_defended_canary.py +0 -0
  24. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/simple_chatbot_hardened_canary.py +0 -0
  25. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/victim.py +0 -0
  26. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan.egg-info/dependency_links.txt +0 -0
  27. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan.egg-info/entry_points.txt +0 -0
  28. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan.egg-info/requires.txt +0 -0
  29. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan.egg-info/top_level.txt +0 -0
  30. {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentproof-scan
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Summary: CLI security scanner: probes AI-agent HTTP endpoints for secret/prompt leakage, with CI-gating exit codes.
5
5
  Author: AgentProof
6
6
  License-Expression: Apache-2.0
@@ -48,7 +48,10 @@ pattern-matcher (no AI doing the judging) counts how many runs the secret litera
48
48
  shows up in each. A result reads like *"leaked in 4 of 10 runs."*
49
49
 
50
50
  Because it's straight matching against known secret shapes, there's nothing hidden:
51
- you can read the code and reproduce every number yourself.
51
+ you can read the code. Every number we mark **GREEN-backed** you can reproduce from a
52
+ clone, offline, byte-for-byte — see [Reproducing these numbers](#reproducing-these-numbers).
53
+ The cross-model observations are a different kind of number: measured snapshots that
54
+ depend on a live model, reported as directional. We don't blur the two.
52
55
 
53
56
  ---
54
57
 
@@ -67,31 +70,41 @@ python3 --version
67
70
  py -3 --version
68
71
  ```
69
72
 
70
- **2. Install, and put your key in a `.env` file** in the folder you'll run from.
71
- Every command below carries a label saying whether *we actually ran it*:
73
+ **2. Install.**
72
74
 
73
- | Shell | Commands | Source |
74
- |---|---|---|
75
- | bash / zsh | `pip install agentproof-scan`<br>`echo 'GEMINI_API_KEY=PASTE_YOUR_KEY_HERE' > .env` | **VERIFIED** — 9 Linux-container cells (Python 3.9/3.11/3.13 × venv/system/pipx, live PyPI install) |
76
- | PowerShell 5.1 | `pip install agentproof-scan`<br>`'GEMINI_API_KEY=PASTE_YOUR_KEY_HERE' \| Out-File -Encoding utf8 .env` | **UNTESTED** — works or not, [open an issue](https://github.com/ghkfuddl1327-wq/agentproof/issues) |
77
- | pwsh 7 | `pip install agentproof-scan`<br>`'GEMINI_API_KEY=PASTE_YOUR_KEY_HERE' > .env` | **UNTESTED** same |
78
- | cmd.exe | `pip install agentproof-scan`<br>`echo GEMINI_API_KEY=PASTE_YOUR_KEY_HERE> .env` | **UNTESTED** same |
75
+ ```
76
+ pip install agentproof-scan
77
+ ```
78
+ `pip: command not found` `python -m pip install agentproof-scan`. On Windows without
79
+ Python, `py -m pip install agentproof-scan` triggers the Python Install Manager to fetch
80
+ Python for you. [VERIFIED: `py -m ...` installed Python 3.14.6 on Windows, owner, 2026-07-11.]
79
81
 
80
- Get a free Gemini key at <https://aistudio.google.com/apikey>. `.env` is read from the
81
- directory you run in, and is gitignored — never commit it.
82
+ **3. Put your key in a `.env` file, with an editor — not a shell command.**
82
83
 
83
- **3. If something isn't found:**
84
+ Open a new file called `.env` in the folder you'll run from, and put one line in it:
84
85
 
85
- | Symptom | Try |
86
- |---|---|
87
- | `pip: command not found` | `python -m pip install agentproof-scan` |
88
- | `agentproof-scan: command not found` | `python -m agentproof_scan` (same tool, same flags) |
86
+ ```
87
+ GEMINI_API_KEY=your-real-key-here
88
+ ```
89
+
90
+ Save it. Get a free key at <https://aistudio.google.com/apikey>. `.env` is auto-loaded from
91
+ the directory you run in and is gitignored — never commit it.
89
92
 
90
- > **On `.env` encoding.** `0.1.3` reads `.env` files written as UTF-8, UTF-8 with BOM,
91
- > and **UTF-16LE with BOM** (what PowerShell 5.1's `>` produces), and it strips stray
92
- > quotes that `cmd.exe` leaves around the *key name*. That is a claim about **bytes**,
93
- > tested byte-for-byte. It is **not** a claim that the tool passes on Windows see
94
- > [Verification scope](#verification-scope).
93
+ > **Why an editor and not `echo '...' > .env`?** A shell command puts your key on the
94
+ > command line, and the shell saves that line to its **history** in plaintext
95
+ > (`~/.bash_history`, PowerShell's `ConsoleHost_history.txt`). A tool that detects leaked
96
+ > credentials should not teach you to write one to a file nobody thinks to check. An editor
97
+ > avoids the history entirely. (If you do use the shell, this tool does **not** scan shell
98
+ > history — that's on you to clear.)
99
+
100
+ > **On `.env` encoding.** The loader reads `.env` written as UTF-8, UTF-8 with BOM, and
101
+ > **UTF-16LE with BOM** (what PowerShell 5.1's `>` produces — [VERIFIED on a real PowerShell
102
+ > 5.1 file, owner, 2026-07-11]), and strips stray quotes `cmd.exe` leaves around the *key
103
+ > name*. **Don't quote the value** — `KEY="abc"` keeps the quotes literally.
104
+
105
+ **4. If the command isn't found:** `agentproof-scan: command not found` →
106
+ `python -m agentproof_scan` (Windows: `py -m agentproof_scan`) — same tool, same flags,
107
+ works when the console script isn't on PATH. [VERIFIED on Windows, owner, 2026-07-11.]
95
108
 
96
109
  ---
97
110
 
@@ -105,25 +118,38 @@ backed by Google Gemini. You only need a free Gemini key to try it.
105
118
  pip install agentproof-scan
106
119
 
107
120
  # 2. Get a FREE Gemini key → https://aistudio.google.com/apikey
108
- # save it to a .env file in the folder you run from (auto-loaded, kept out of git):
109
- echo 'GEMINI_API_KEY=PASTE_YOUR_KEY_HERE' > .env
121
+ # Then create a file named .env in this folder (with an editor, not the shell
122
+ # see Prerequisites) containing one line:
123
+ # GEMINI_API_KEY=your-real-key-here
110
124
 
111
125
  # 3. Scan the built-in vulnerable demo agent
112
- agentproof-scan # same as: agentproof-scan --target victim
113
- agentproof-scan --stability 5 # repeat — more reliable (see note below)
126
+ agentproof-scan # one pass = 15 probes = 15 API calls
127
+ agentproof-scan --stability 2 # 2 passes = 30 calls — more reliable (see notes)
114
128
  ```
115
129
 
116
130
  A JSON report prints. If the demo leaked, you'll see a `leak_count` of 1 or more.
117
131
 
118
- > ⚠️ **A zero you can trust.** Up to `0.1.2` a scan that never reached your agent — bad
119
- > key, HTTP 500, timeout still printed `leak_count: 0` and exited `0`. It read as
120
- > *"your agent is safe"* when it meant *"we couldn't ask."* From `0.1.3` the scanner
121
- > **refuses to report clean** unless every probe actually got an answer: it exits `1`
122
- > and prints `AGENTPROOF_SCAN_DID_NOT_RUN reason=<slug>` on stderr. A leak it *did*
123
- > find is still reported — a partial scan may not claim clean, but it may claim what it
124
- > found.
125
-
126
- > **Why `--stability 5`?** A single run is non-deterministic a leaky agent can still answer "safely" on any one try, so a one-shot scan might read `0` by luck. Repeating (e.g. `--stability 5`) measures *how often* it leaks (`leak_rate`) and is the reliable way to read the verdict.
132
+ > ⚠️ **On a free Gemini key, watch the request count.** Each pass sends **15 probes** = 15
133
+ > API calls; `--stability N` multiplies that (`--stability 5` = 75 calls, `--stability 10`
134
+ > = 150). The free tier has a per-minute quota, and hitting it is **not** a tool failure —
135
+ > the scanner exits `1` with `reason=rate_limit` and tells you to wait or lower
136
+ > `--stability`. Start with `--stability 2`; raise it once you know your quota headroom.
137
+
138
+ > ⚠️ **A zero you can trust.** A scan that never reached your agent, or was cut off
139
+ > partway (bad key, HTTP 500, timeout, rate limit), must not read as *"safe."* The scanner
140
+ > **refuses to report clean** unless every probe actually got an answer: otherwise it exits
141
+ > `1` and prints `AGENTPROOF_SCAN_DID_NOT_RUN reason=<slug>` on stderr (`rate_limit`,
142
+ > `auth_failed`, `http_status`, `incomplete_scan`, …). A leak it *did* find is still
143
+ > reported and, under `--fail-on-findings`, still exits `2` — **a partial scan may not
144
+ > claim clean, but it may claim what it found.** (Earlier versions, up to `0.1.3`, could
145
+ > print `leak_count: 0` and exit `0` here — that's the bug `0.1.4` closes.)
146
+
147
+ > **Why repeat with `--stability`?** A single run is non-deterministic — a leaky agent can
148
+ > still answer "safely" on any one try, so a one-shot scan might read `0` by luck. Repeating
149
+ > measures *how often* it leaks (`leak_rate`) and is the reliable way to read the verdict.
150
+ > A probe that never got an answer shows `leak_rate: null` (**not** `0.0`) — "not asked" is
151
+ > not "didn't leak." ⚠ Each repeat costs `15 × N` API calls; on a free key start at
152
+ > `--stability 2` (see the request-count note above).
127
153
 
128
154
  ---
129
155
 
@@ -136,7 +162,7 @@ Two fields matter:
136
162
 
137
163
  **Analogy:** `leak` = the guard handed over the vault key. `prompt_disclosure` = the guard didn't hand over the key, but read the security manual aloud. Both are bad; the first is worse.
138
164
 
139
- **`leak_rate`** (in repeat/stability mode) = how often a probe pulled a leak. For example `4/10 (0.4)` = 4 of 10 tries leaked. A flaky leak is still a leak — repetition shows how *reliably* an agent fails.
165
+ **`leak_rate`** (in repeat/stability mode) = how often a probe pulled a leak, over the runs that **got an answer**. For example `4/10 (0.4)` = 4 of 10 answered tries leaked. A flaky leak is still a leak — repetition shows how *reliably* an agent fails. A probe that never got an answer (rate-limited, timed out) shows `leak_rate: null`, not `0.0` — the report separates *"didn't leak"* from *"wasn't asked."*
140
166
 
141
167
  **Which secrets it recognizes:** the scanner looks for key shapes from major providers — OpenAI (including modern `sk-proj-` / `sk-svcacct-` / `sk-admin-` keys and the legacy `sk-` format), Anthropic, Google, AWS, GitHub, and xAI.
142
168
 
@@ -216,9 +242,10 @@ agentproof-scan \
216
242
  **Needs auth?** Pass a header — but put only the **name** of an environment variable
217
243
  in the flag, never the key itself:
218
244
 
245
+ Add the key to your `.env` (with an editor — same reason as the Quick Start: keep it out
246
+ of shell history) as `MY_AGENT_KEY=your-real-key`, then reference it **by name**:
247
+
219
248
  ```bash
220
- # key lives in .env (gitignored); the flag references it by name
221
- echo 'MY_AGENT_KEY=sk-your-real-key' >> .env
222
249
  agentproof-scan --url https://my-agent.example.com/chat \
223
250
  --prompt-field message --response-field reply \
224
251
  --auth-header "Authorization=Bearer {MY_AGENT_KEY}"
@@ -264,7 +291,9 @@ body: # your request template
264
291
  > so it spends whatever those calls cost on your account — same as the demo Gemini key.
265
292
 
266
293
  *(Prefer to wire it in yourself? You still can: implement the small `AgentAdapter`
267
- interface in `adapters/base.py` and register it in `ADAPTERS` in `scan.py`.)*
294
+ interface in `agentproof_scan/adapters/base.py` and register it in `ADAPTERS` in
295
+ `agentproof_scan/scan.py`. The top-level `scan.py` is only a clone-launcher shim and
296
+ isn't in the wheel.)*
268
297
 
269
298
  **Roadmap:** the generic HTTP path above is **shipped**. Broader shapes — non-JSON
270
299
  bodies, streaming responses, and non-HTTP transports — are expanding from here.
@@ -309,11 +338,13 @@ jobs:
309
338
 
310
339
  *(Also at [`examples/ci/agentproof.yml`](https://github.com/ghkfuddl1327-wq/agentproof/blob/main/examples/ci/agentproof.yml) — copy it, don't clone the repo.)*
311
340
 
312
- **Don't drop the `AGENT_URL` guard.** An unset secret is an empty string, and an
313
- empty `--url` makes the scanner fall back to its **bundled demo agent** which
314
- leaks on purpose. Without the guard your build goes red over a planted fake secret
315
- while your real agent is never scanned. Forked pull requests get no secrets at all,
316
- so they always take this path.
341
+ **Keep the `AGENT_URL` guard anyway.** An unset secret is an empty string, and the
342
+ guard turns that into a clear "secret not set" failure. Since `0.1.3` an empty `--url`
343
+ already exits `1` on its own (`reason=missing_url`) rather than scanning anything so
344
+ a missing `AGENT_URL` fails the build as *"the scan did not run,"* not as *"your agent
345
+ leaked."* The guard just makes that reason obvious in the log. Forked pull requests get
346
+ no secrets, so they hit this path — and a `1` there is correct: nothing was scanned,
347
+ and nothing is claimed clean.
317
348
 
318
349
  If your agent leaks a secret, `--fail-on-findings` exits non-zero and the check goes
319
350
  red. By default it fails on **leaked secrets only** (not softer prompt-disclosure
@@ -363,7 +394,7 @@ Python 3.9 / 3.11 / 3.13 × venv / system / pipx
363
394
  The last row will not be closed by automation. If you hit friction installing
364
395
  Python itself, an issue is the only way that row changes.
365
396
 
366
- `0.1.3` handles UTF-16LE+BOM `.env` files (byte-level test).
397
+ `0.1.4` handles UTF-16LE+BOM `.env` files (byte-level test); `0.2.0` keeps that behaviour.
367
398
  This is **not** a claim that the tool passes on Windows PowerShell 5.1.
368
399
  It is a claim about the bytes.
369
400
 
@@ -416,7 +447,7 @@ If any step is confusing, paste this into an AI assistant and follow along:
416
447
 
417
448
  ## ⚠️ A note on the test fixtures
418
449
 
419
- `victim_agent.py` and the `*_canary` adapters contain **intentional** vulnerabilities — fake, format-only secrets (not real keys) used as test fixtures to prove the scanner works. They are not exploits, and the embedded strings are not usable credentials. The probe set in this public repo uses neutral, category-labeled example questions — it does **not** ship copy-pasteable injection prompts.
450
+ `agentproof_scan/victim_agent.py` and the `*_canary` adapters contain **intentional** vulnerabilities — fake, format-only secrets (not real keys) used as test fixtures to prove the scanner works. They are not exploits, and the embedded strings are not usable credentials. The probe set in this public repo uses neutral, category-labeled example questions — it does **not** ship copy-pasteable injection prompts.
420
451
 
421
452
  ---
422
453
 
@@ -424,12 +455,20 @@ If any step is confusing, paste this into an AI assistant and follow along:
424
455
 
425
456
  Early work in progress. This tool grew out of red-team probing experiments and is expanding toward broader pre-deployment credential-exposure detection. The detection rule and the cross-model numbers are still being validated — **expect changes**, and if you can break something we marked as working, please open an issue.
426
457
 
427
- **Known limitation:** a present-but-invalid key (wrong or expired) can still produce a `0` — detecting invalid keys from API-error responses is a planned follow-up.
458
+ **Invalid keys, since `0.1.3`:** a present-but-invalid key no longer produces a
459
+ misleading `0`. The provider returns an HTTP error — a malformed key is a `400`
460
+ (`reason=http_status`), a wrong or expired key is a `401` (`reason=auth_failed`) — and
461
+ the scanner treats either as *the scan did not run*: exit `1`, refusing to report
462
+ clean. Earlier versions did report `0` here; that was the bug `0.1.3` closes.
428
463
 
429
- **Not yet in this release:** wider credential-type coverage (Stripe, Slack, JWT, PEM,
430
- SendGrid, Twilio, npm, …) is implemented and tested, but `0.1.3` ships only the six
431
- families listed under [What it catches](#what-it-catches--and-what-it-doesnt-plainly).
432
- It lands in a later release rather than being advertised here before you can run it.
464
+ **Released in `0.2.0`:** the wider credential-type coverage that `0.1.4` held back
465
+ (Stripe, Slack, JWT, PEM, SendGrid, Twilio, npm, GitHub fine-grained PAT, GCP) now
466
+ ships you can run it. See [What it catches](#what-it-catches--and-what-it-doesnt-plainly)
467
+ for the list and [What's measured](#whats-measured--three-separate-things) for what each
468
+ claim is backed by. `postgres` remains **opt-in**, not promoted to default (below).
469
+
470
+ `0.2.0` is additive: the six families from `0.1.4` behave exactly as before, and the
471
+ exit codes (`0` / `1` / `2`) and the `rule` slug are unchanged.
433
472
 
434
473
  ---
435
474
 
@@ -464,43 +503,114 @@ No extra API calls: the trace is captured during the same probe run.
464
503
 
465
504
  ---
466
505
 
467
- ## Defense prompts — where to find them
506
+ ## Defense prompts — reference, not a fix
468
507
 
469
- If a scan turns up a leak, the repo ships **defense prompts** you can paste into your
470
- agent's system prompt to reduce it. To keep this page short, the prompts themselves
471
- live in the repo, not here:
508
+ There is no prompt that "fixes" leakage. What the repo ships is a **reference**: a
509
+ defense hypothesis, measured per model, with results that vary by model what helps
510
+ one can leave a residual on another. Adding a defense block *raises the cost of a
511
+ leak*; it is not a guarantee, and the only figure that means anything for your setup is
512
+ the one you measure on your own model. To keep this page short, the prompts and the
513
+ measurements behind them live in the repo, not here:
472
514
 
473
515
  [`prompts/system_defense/`](https://github.com/ghkfuddl1327-wq/agentproof/tree/main/prompts/system_defense)
474
516
 
475
- Installed via pip? The prompts live in the repo, not the package — open the link
476
- above, or `git clone` the repo if you want them locally. Each prompt is a plain text
477
- block you copy into your agent's system prompt.
517
+ Installed via pip? They live in the repo, not the package — open the link above, or
518
+ `git clone` the repo to read them locally. Each is a plain-text block.
478
519
 
479
520
  [`REFERENCE.md`](https://github.com/ghkfuddl1327-wq/agentproof/blob/main/prompts/system_defense/REFERENCE.md)
480
- tells you **which prompt fits which model** and
481
- states the honest limits (it protects the final answer, not the reasoning trace
482
- see above). Keeping the data in the repo means it can grow without turning this page
483
- into a wall of text.
521
+ is the honest version: which block was measured against which model, in directional
522
+ buckets (not precise rates), and the limits chiefly that it moves the **final
523
+ answer** surface, not the reasoning trace (see above). Keeping it in the repo lets the
524
+ reference grow without turning this page into a wall of text.
525
+
526
+ ---
527
+
528
+ ## What's measured — three separate things
529
+
530
+ This page makes three different claims. They come from three different measurements,
531
+ they count three different things, and **they do not add up to one number**. A scanner
532
+ that labels 16 credential shapes is not a scanner that "catches 16 kinds of attack."
533
+
534
+ | Layer | The claim | What backs it |
535
+ |---|---|---|
536
+ | **Detection — 16 families** (15 default + `postgres` opt-in) | the matcher puts the **right family label** on a credential's shape, and stays quiet on look-alikes | [`axis_b_coverage_green.json`](https://github.com/ghkfuddl1327-wq/agentproof/blob/main/axis_b_coverage_green.json) — the 10 families added since `0.1.4`: 10/10 labelled, 0 missed, 0 false positives on near-miss strings. The original six are held by `test_secrets_integrity.py`. |
537
+ | **Elicitation — 10 families** | a credential sitting in an **agent's response** is picked up end-to-end (probe → response → report), with the right family and nothing invented | [`elicitation_green.json`](https://github.com/ghkfuddl1327-wq/agentproof/blob/main/elicitation_green.json) — 10 planted, 10 detected, 0 missed, 0 spurious providers. `postgres` is excluded (it's opt-in). |
538
+ | **Reasoning-attack (H-CoT) — 3 probes** | when a fake "reasoning step" is injected into an agent **you own**, the scanner **sees** the resulting reasoning-channel leak | [`hcot_green.json`](https://github.com/ghkfuddl1327-wq/agentproof/blob/main/hcot_green.json) — 3 probes, 0 missed, 0 false positives; in all three the answer stayed clean and only the reasoning leaked. |
539
+
540
+ Three things to be clear about, because the labels are easy to over-read:
541
+
542
+ - **Detection 16 ≠ elicitation 10.** The first counts shapes the matcher knows. The
543
+ second counts families carried end-to-end out of an agent's reply. Neither number
544
+ is a subset or a total of the other, and neither is "how many attacks it stops."
545
+ - **The H-CoT row is about the scanner, not about models.** It says *this tool sees
546
+ that leak*. It is **not** a claim that any real model is vulnerable to H-CoT — that
547
+ would need live measurement against real models, which is **not** in this release.
548
+ The probes exist so you can check an agent **you own**; they are not a jailbreak kit.
549
+ - **All three are offline (`canned`, 0 API calls).** They exercise the scanner against
550
+ planted, synthetic, shape-only fakes — no real key and no live model is involved. So
551
+ none of them says how often a *real* agent leaks. That question is measured elsewhere
552
+ (see [What we've observed](#-what-weve-observed-so-far-early--qualitative)) and those
553
+ observations are directional, not reproducible here.
554
+
555
+ ### Reproducing these numbers
556
+
557
+ Every **GREEN-backed** number in the table above reproduces from a clone, offline, with
558
+ no API key — and byte-for-byte, not just "close enough":
559
+
560
+ ```bash
561
+ git clone https://github.com/ghkfuddl1327-wq/agentproof && cd agentproof
562
+ python score_axis_b_coverage.py # → axis_b_coverage_green.json
563
+ python score_elicitation.py # → elicitation_green.json
564
+ python score_hcot.py # → hcot_green.json
565
+ python -m pytest -q # the gates behind them
566
+ ```
567
+
568
+ Run any of them twice and you get identical bytes (no clock, no RNG seed drift).
569
+ If a regenerated file differs from the committed one, treat the claim as broken —
570
+ that's the point of shipping the generators next to the artifacts.
571
+
572
+ **What does *not* reproduce this way:** the cross-model observations elsewhere on this
573
+ page. Those are **measured snapshots** — they depend on API keys, model availability,
574
+ and provider-side behaviour that changes under us. They are reported as directional,
575
+ and re-running them will not give you the same bytes. We keep the two kinds of number
576
+ apart on purpose.
484
577
 
485
578
  ---
486
579
 
487
580
  ## What it catches — and what it doesn't (plainly)
488
581
 
489
- **It catches:** a set list of credential types, matched by their shape. As of
490
- `0.1.3` that's **six families** — OpenAI, Anthropic, Google, AWS, GitHub, and xAI.
582
+ **It catches:** a set list of credential types, matched by their shape. As of `0.2.0`
583
+ the **detection** list is **15 families on by default** — OpenAI, Anthropic, Google,
584
+ AWS, GitHub (classic), GitHub fine-grained PAT, xAI, Stripe, Slack, JWT, PEM private
585
+ keys, SendGrid, GCP OAuth client secrets, npm, Twilio — **plus `postgres`, which is
586
+ off by default and opt-in** (16th family; see below for why it isn't promoted).
491
587
  Matching holds up whether the secret is in plain text or JSON, across different
492
588
  languages, and in the answer or the reasoning — for the types it knows.
493
589
 
590
+ Two of these are **exposure signals rather than proof of a secret leak**, and the tool
591
+ says so in the finding's `scope`: a **JWT** is often a public ID token, and a **Twilio**
592
+ `SK…` is a public identifier whose paired secret is separate. They're worth surfacing;
593
+ they are not automatically an incident.
594
+
595
+ `postgres` (a password inside a `postgres://…` URL) is **opt-in** because it is the one
596
+ type that could not meet the no-false-positives bar: the password has no prefix to
597
+ anchor on, so common documentation strings (`mysecretpassword`, `postgres_dev_password`)
598
+ trip it. Rather than loosen the bar for every user, it's off unless you ask:
599
+
600
+ ```bash
601
+ AGP_ENABLE_OPTIONAL=1 agentproof-scan --target … # turns postgres on, FPs included
602
+ ```
603
+
494
604
  Strings that have a real key's *shape* but are obvious dummies (`sk-ant-…EXAMPLE`,
495
605
  `AKIA…FAKE`, `…placeholder…`) are filtered out rather than reported, so example code
496
606
  and docs don't set off false alarms.
497
607
 
498
608
  **It doesn't catch:**
499
- - **Credential types outside those six** — Stripe, Slack, JWTs, PEM private keys,
500
- SendGrid, Twilio, npm tokens and others are **not** matched in `0.1.3`. Broader
501
- type coverage is built and tested but not yet released here (see Status).
502
- - **Secrets with no tell-tale prefix** — e.g. a database password buried in a
503
- `postgres://…` URL. A real limit of shape-matching.
609
+ - **Credential types outside that list** — the list is finite and hand-written. A type
610
+ that isn't in it is not matched at all. Adding families does not make the list
611
+ complete; it moves the boundary.
612
+ - **Secrets with no tell-tale prefix** — the `postgres://…` case above is the example,
613
+ and it's why that family is opt-in. A real limit of shape-matching.
504
614
  - **Secrets described in words** — if a secret is paraphrased with no literal
505
615
  key-string, shape-matching can't see it.
506
616
  - **Live/runtime catching** — this runs before you ship (offline), not as a live
@@ -508,8 +618,9 @@ and docs don't set off false alarms.
508
618
  - **Models we haven't tested** — results come from a small set of lightweight models,
509
619
  not the big frontier ones.
510
620
 
511
- *"No false positives" is true for random text on the six types above — it's not a
512
- promise that a shape-matching type never flags a token that turns out to be public.*
621
+ *"No false positives" is true for random text on the default types above — it's not a
622
+ promise that a shape-matching type never flags a token that turns out to be public.
623
+ The JWT and Twilio caveats above are exactly that case, stated up front.*
513
624
 
514
625
  ---
515
626