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.
- {agentproof_scan-0.1.3/agentproof_scan.egg-info → agentproof_scan-0.2.0}/PKG-INFO +182 -71
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/README.md +181 -70
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/__init__.py +1 -1
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/base.py +48 -2
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/simple_chatbot.py +15 -6
- agentproof_scan-0.2.0/agentproof_scan/adapters/simple_chatbot_multitype_canary.py +128 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/fingerprint.py +4 -1
- agentproof_scan-0.2.0/agentproof_scan/reasoning_adapters.py +112 -0
- agentproof_scan-0.2.0/agentproof_scan/reasoning_scan.py +188 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/scan.py +202 -56
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/victim_agent.py +18 -6
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0/agentproof_scan.egg-info}/PKG-INFO +182 -71
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan.egg-info/SOURCES.txt +3 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/pyproject.toml +1 -1
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/LICENSE +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/MANIFEST.in +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/__main__.py +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/__init__.py +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/external_starter.py +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/generic_http.py +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/llm_starter.py +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/simple_chatbot_canary.py +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/simple_chatbot_defended_canary.py +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/simple_chatbot_hardened_canary.py +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan/adapters/victim.py +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan.egg-info/dependency_links.txt +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan.egg-info/entry_points.txt +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan.egg-info/requires.txt +0 -0
- {agentproof_scan-0.1.3 → agentproof_scan-0.2.0}/agentproof_scan.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
|
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
|
|
71
|
-
Every command below carries a label saying whether *we actually ran it*:
|
|
73
|
+
**2. Install.**
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
+
Open a new file called `.env` in the folder you'll run from, and put one line in it:
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
> **
|
|
91
|
-
>
|
|
92
|
-
>
|
|
93
|
-
>
|
|
94
|
-
>
|
|
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
|
-
#
|
|
109
|
-
|
|
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 #
|
|
113
|
-
agentproof-scan --stability
|
|
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
|
-
> ⚠️ **
|
|
119
|
-
>
|
|
120
|
-
>
|
|
121
|
-
>
|
|
122
|
-
>
|
|
123
|
-
|
|
124
|
-
>
|
|
125
|
-
|
|
126
|
-
> **
|
|
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
|
|
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
|
|
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
|
-
**
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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.
|
|
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
|
-
**
|
|
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
|
-
**
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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 —
|
|
506
|
+
## Defense prompts — reference, not a fix
|
|
468
507
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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?
|
|
476
|
-
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
see above). Keeping
|
|
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
|
-
|
|
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
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
- **Secrets with no tell-tale prefix** —
|
|
503
|
-
|
|
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
|
|
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
|
|