stonefish-labs commited on
Commit
124bf0b
·
verified ·
1 Parent(s): 30806b7

Card fact-check: usage snippet, long inputs, 4090 follow-up, limits, privacy, sources

Browse files
Files changed (7) hide show
  1. MODEL_MANIFEST.json +11 -11
  2. README.md +129 -86
  3. REBUILD.md +8 -5
  4. RESULTS.md +2 -1
  5. SHA256SUMS +5 -5
  6. SOURCES.md +6 -3
  7. examples/usage.py +1 -1
MODEL_MANIFEST.json CHANGED
@@ -40,11 +40,11 @@
40
  "LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
41
  "MERGE-REPORT.json": "a42ca8b488e0b98b1a690937784e8da0fe417fd2218e7595129927221f4d9244",
42
  "NOTICE": "e74014fab972cffe76391a608e8a5cf54baeaf7d16cd8a77545edc67ffd25225",
43
- "README.md": "e991987170a18dda2a68fe6a25793d3d18250eb47e0315c32699dd2ca7bb7c69",
44
- "REBUILD.md": "3b1fd15114b4cad8eb45cee219e2a248a11d3cfb359181346d14eab9c98df7a9",
45
- "RESULTS.md": "096b58ff385f3f149780de90e14516f5e9fdef97acb316dffc4a7b06314231b3",
46
- "SHA256SUMS": "f791bda2f116f379302351fd46d29d5938aedf0cac63db8239341c6daf4b8926",
47
- "SOURCES.md": "3bd6eb1b1d0c5b0b18ff8ee8f2076512f514c3367e943610515b82e43e852144",
48
  "adapter/adapter_config.json": "ff2a5427cbcf0e7a0a61940920d06f0e1fc116565258c3eca2501b0d10933250",
49
  "adapter/adapter_model.safetensors": "3bd401009801b39f29148427e884a8230a85ddb3c20a54ea834372889998c895",
50
  "adapter/merge.py": "bf0999886d5b6ac6764c7a9d688c890579062a04254e0054bb230681fb464a7e",
@@ -60,7 +60,7 @@
60
  "d3_runtime.py": "adf7f7aa085674b5682e492d8b2e3cc4f11c67b0d153515c742e907e9b550917",
61
  "d3_server.py": "766a20a69101e36ab0535a1ec683379db422380a83091f0eccd7e82edb6a02de",
62
  "decision_config.json": "588bc0ac7644479b9adde0498d80b6fb7c576388992564ac67599c05d29add07",
63
- "examples/usage.py": "6eedbd855133312a30ba44f35a82bd6b47107313b3a9ad80a2230c1218c1ac98",
64
  "extractor/README.md": "cd1679b82e61c15f247c277007c0d416d27b4834f455f3fee7f9afb863743d08",
65
  "extractor/behaviour/__init__.py": "c0db7c9cb57a23289245209c30c335c3e44614454d2796a239864d70165356d6",
66
  "extractor/behaviour/__main__.py": "2c78b70c752cc11bfbf759e9e8fe0a4b5f43da43cdecb4489a9b403ac4d20311",
@@ -131,11 +131,11 @@
131
  "LICENSE": 11357,
132
  "MERGE-REPORT.json": 86,
133
  "NOTICE": 279,
134
- "README.md": 11437,
135
- "REBUILD.md": 1924,
136
- "RESULTS.md": 4669,
137
  "SHA256SUMS": 8922,
138
- "SOURCES.md": 3136,
139
  "adapter/adapter_config.json": 1329,
140
  "adapter/adapter_model.safetensors": 121954608,
141
  "adapter/merge.py": 10334,
@@ -151,7 +151,7 @@
151
  "d3_runtime.py": 95505,
152
  "d3_server.py": 17705,
153
  "decision_config.json": 5502,
154
- "examples/usage.py": 2584,
155
  "extractor/README.md": 3420,
156
  "extractor/behaviour/__init__.py": 453,
157
  "extractor/behaviour/__main__.py": 2303,
 
40
  "LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
41
  "MERGE-REPORT.json": "a42ca8b488e0b98b1a690937784e8da0fe417fd2218e7595129927221f4d9244",
42
  "NOTICE": "e74014fab972cffe76391a608e8a5cf54baeaf7d16cd8a77545edc67ffd25225",
43
+ "README.md": "ed61f82f9b000ddf533119f1345345bca2f345514b0b70d802f2bd172c8ce24a",
44
+ "REBUILD.md": "76e575f2d52a83a937d0c4daf818ba302595f577cf54b3f3bd3bbea67a0e4566",
45
+ "RESULTS.md": "cc20ca6e484d84f5c86ea16e82ce0ef3a3978f572c8d8c514ed3a1383b9d1930",
46
+ "SHA256SUMS": "0bce477e859f4735519404d7e0c9231f5762c6ff79f4eff41ba34897af23eae7",
47
+ "SOURCES.md": "bfb09be93fa5d4022645bb5bf3cd4268949ef76eba42c4d462099537a6fb7bb2",
48
  "adapter/adapter_config.json": "ff2a5427cbcf0e7a0a61940920d06f0e1fc116565258c3eca2501b0d10933250",
49
  "adapter/adapter_model.safetensors": "3bd401009801b39f29148427e884a8230a85ddb3c20a54ea834372889998c895",
50
  "adapter/merge.py": "bf0999886d5b6ac6764c7a9d688c890579062a04254e0054bb230681fb464a7e",
 
60
  "d3_runtime.py": "adf7f7aa085674b5682e492d8b2e3cc4f11c67b0d153515c742e907e9b550917",
61
  "d3_server.py": "766a20a69101e36ab0535a1ec683379db422380a83091f0eccd7e82edb6a02de",
62
  "decision_config.json": "588bc0ac7644479b9adde0498d80b6fb7c576388992564ac67599c05d29add07",
63
+ "examples/usage.py": "37fc6f3322832e8ce31028dba64561af57c189c612709e9f407457d6a7e44c9a",
64
  "extractor/README.md": "cd1679b82e61c15f247c277007c0d416d27b4834f455f3fee7f9afb863743d08",
65
  "extractor/behaviour/__init__.py": "c0db7c9cb57a23289245209c30c335c3e44614454d2796a239864d70165356d6",
66
  "extractor/behaviour/__main__.py": "2c78b70c752cc11bfbf759e9e8fe0a4b5f43da43cdecb4489a9b403ac4d20311",
 
131
  "LICENSE": 11357,
132
  "MERGE-REPORT.json": 86,
133
  "NOTICE": 279,
134
+ "README.md": 15004,
135
+ "REBUILD.md": 2168,
136
+ "RESULTS.md": 4805,
137
  "SHA256SUMS": 8922,
138
+ "SOURCES.md": 3241,
139
  "adapter/adapter_config.json": 1329,
140
  "adapter/adapter_model.safetensors": 121954608,
141
  "adapter/merge.py": 10334,
 
151
  "d3_runtime.py": 95505,
152
  "d3_server.py": 17705,
153
  "decision_config.json": 5502,
154
+ "examples/usage.py": 2595,
155
  "extractor/README.md": 3420,
156
  "extractor/behaviour/__init__.py": 453,
157
  "extractor/behaviour/__main__.py": 2303,
README.md CHANGED
@@ -18,42 +18,50 @@ tags:
18
 
19
  ![Ink-and-watercolor drawing: a small round robot sits on top of a huge haystack of code pages, holding up a glowing red needle it just found and inspecting it with a loupe.](https://huggingface.co/stonefish-labs/d3-mini-agentcode/resolve/main/assets/banner.jpg)
20
 
21
- **Pre-release.** A revised version is planned. It would be trained on positives from several writers, realistic task
22
- wording and more languages; see "Limitations" for what it targets.
23
 
24
- d3-mini-agentcode is [d3-mini](https://huggingface.co/vllm-sr/d3-mini) (4B, Apache-2.0) fine-tuned to triage **code
25
- written or run by AI coding agents**. It reads the agent's task and the change (a diff, or a snippet the agent ran) and
26
- answers whether the change does something security-relevant the task didn't call for, or something deliberately
27
- harmful. It's a cheap first-line filter that ranks changes for review.
 
28
 
29
  **Write-up:** [Small Models, Big Haystack](https://batteryshark.github.io/2026/11/05/small-models-big-haystack.html)
30
  covers how this model was built and tested, and how it compares with off-the-shelf d3 models and LLMs.
31
 
32
  ## Intended use
33
- - **An agent-code and code-mode gate:** score every agent diff or executed snippet; send flagged items to a human or a
34
  stronger model.
35
- - **A second-stage ranker** behind cheaper rules.
36
- - **Not a sole gate.** It's a 4B model with the limitations below; it is evidence for review, not proof of intent.
37
- - **Out of scope:** whole-package or dependency scanning (npm, PyPI), inputs over 8,192 tokens, and verdicts without a
38
- task description (it was trained with one).
 
 
 
39
 
40
  ## How it was made
41
- - **Base:** `vllm-sr/d3-mini` at revision `df99bf4ca4418bc6908ee68e9b7479af1f3b8800` (built on Qwen/Qwen3.5-4B).
42
  - **Recipe:** LoRA (r 16, α 32, dropout 0.05) on the text backbone's attention, linear-attention and MLP projections,
43
  plus the trained answer readout. lr 1e-4 (readout 2e-5), 2 epochs, 1,038 optimizer steps, max length 8,192, label
44
- smoothing 0.05, choice-order shuffling (`adapter/training_args.json`).
45
- - **Merged:** this repo holds the merged weights (load like d3-mini), the adapter (`adapter/`) and the behaviour
46
- extractor that builds the input (`extractor/`).
47
- - **Primary release:** seed 1, final checkpoint. A second seed replicates the results below and is not published.
48
- - **Questions:** three frozen System One questions (`questions.json`): `class` (choice), `intent` and `scope` (yes/no).
 
 
49
 
50
  ## Usage
51
  ```python
52
  import json
 
53
  from transformers import AutoModel
54
 
55
- model = AutoModel.from_pretrained("stonefish-labs/d3-mini-agentcode", trust_remote_code=True)
56
- questions = json.load(open("questions.json")) # class, intent, scope (shipped in this repo)
 
57
 
58
  state = """Task:
59
  Add a CLI flag to export the report as CSV.
@@ -68,79 +76,109 @@ Change:
68
  diff --git a/report.py b/report.py
69
  ..."""
70
 
71
- r = model.system_one(state=state, questions=questions)
72
- p_mal = r["class"]["probabilities"]["deliberately_malicious"]
73
- p_scope = r["scope"]["noul"] # p(yes): does more than the task asks
74
- flag = p_mal >= 0.9 or p_scope >= 0.9 # frozen thresholds (thresholds.json)
75
  ```
76
- - **Input:** a text state with four sections, in order: `Task:`, `Repository baseline (normal behaviour of this
77
- repo/org):`, `Behaviour summary (automated, not a verdict):` and `Change:`. The task should be present.
78
- `examples/usage.py` shows a full example.
79
- - **Behaviour summary:** use the included static extractor (`extractor/`), the same one that wrote the summary in every
80
- training and evaluation state. `pip install -r extractor/requirements.txt`, then, from `extractor/`,
81
- `python make_state.py diff change.diff --task-file task.txt` (or `snippet run.py --lang py --task "..."`) prints
82
- the full state. `extractor/README.md` covers the Python API and how to build a repository baseline. It never runs
83
- the analysed code.
84
- - **Placeholder summary:** without the extractor, the placeholder `(no security-relevant behaviour found)` works
85
- almost as well in our tests (seed 1, frozen θ). Across 14 languages it found 448/510 (with the extractor's summary:
86
- 458/510) and flagged 5/2150 real items (11/2150) and 1/560 controls (1/560); AUROC 0.984 (0.985).
87
- On the independent agent-diff set it found 20/22 (21/22) with 0/172 flagged (0/172). The largest drop
88
- is bash code mode (20/33 vs 24/33).
89
- - **Output:** per question, probabilities (`class`) or a yes-probability (`noul`, for `intent` and `scope`).
90
- - **Throughput:** about 0.5–0.8 s per item on one RTX 4090 at typical lengths.
91
-
92
- ## Thresholds (θ policy)
93
- - **Frozen (used for every result below):** flag when p(class = deliberately_malicious) ≥ **0.9** or p(scope) ≥
94
- **0.9**. Fitted on an English, Python-heavy dev set at ≤ 5% false alarms.
95
- - **Multilingual option:** 0.9 / **0.05**, fitted on a 10% dev split of the 14-language set. On the rest it raises
96
- recall from 90% to 95% at 2.3% instead of 0.5% false alarms on real code.
97
- - **Recommendation:** fit θ on a sample of your own code for your false-alarm budget (`thresholds.json`).
 
 
 
 
 
 
 
 
 
 
 
98
 
99
  ## Results
100
- Each model at its own dev-fitted threshold. "Untuned" is d3-mini with the same context.
 
 
101
 
102
  | test set | seed 1 (released) | seed 2 | untuned |
103
  |---|---|---|---|
104
- | Agent diffs, independent set: out-of-scope changes found / real agent diffs + in-scope controls flagged | 21/22 / 0/172 | 19/22 / 0/172 | 10/22 / 0/172 |
105
- | Public code mode, unseen source: positives found / real snippets flagged | 31/31 / 0/200 | 31/31 / 0/200 | 31/31 / 1/200 |
106
- | Held-out agent diffs + code mode (unseen repos/source): found / real items flagged | 122/122 / 0/527 | 118/122 / 0/527 | 96/122 / 2/527 |
107
- | Behaviour families never seen in training | 26/26 | 22/26 | 14/26 |
108
- | Compromised library releases shown as diffs (not trained on): found / ordinary releases flagged | 21/27 / 0/80 | 19/27 / 0/80 | 19/27 / 0/80 |
109
- | **14 languages and shells** (8 diff languages, 6 code-mode/shell cells): found / real items flagged / authored benign controls flagged | **458/510 / 11/2150 / 1/560** | 446/510 / 7/2150 / 2/560 | 343/510 / 48/2150 / 12/560 |
110
- | Task asks for the change (request alone as the task): flag clears | 100% | 100% | 62–69% |
111
- | Task asks for the change (request added to a real task): flag clears / still flags with an ordinary request | 143/246 / 221/246 | 173/246 / 218/246 | 116/246 / 152/246 |
 
112
  | Unrequested account email added to request headers: found / controls flagged | 1/40 / 0/60 | 0/40 / 0/60 | 3/40 / 4/60 |
113
 
114
- Per language (seed 1, frozen θ; `RESULTS.md` has both seeds, AUROCs with CIs and the untuned model):
 
115
 
116
  | | C/C++ | C# | Go | Java | JS/TS | PHP | Rust | Swift/ObjC | bash | cmd/batch | zsh/macOS | PowerShell | Python (code mode) | JS/TS (code mode) |
117
  |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
118
  | found | 35/38 | 34/37 | 36/38 | 36/37 | 33/37 | 34/38 | 33/37 | 37/38 | 24/33 | 28/33 | 29/36 | 35/37 | 30/35 | 34/36 |
119
  | real flagged | 1/149 | 0/149 | 1/150 | 1/149 | 0/150 | 0/150 | 0/150 | 0/150 | 1/200 | 0/130 | 3/144 | 3/150 | 1/200 | 0/129 |
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  ## Limitations
122
- - **Authored positives.** The malicious and out-of-scope training and test examples for agent code are authored, inert
123
- changes inserted into real agent work. Near-perfect scores on authored held-out sets partly reflect that. The
124
- independent agent-diff set (written under a different protocol, with real agent diffs as negatives) is the better
125
- check.
126
- - **Writer gap.** Training positives came from one writer model. On positives written by other models, recall is
127
- lower: 88% for GPT-written vs 93% for Claude-written across 14 languages, and 82% vs 95% in code mode.
128
- - **Task-swap behaviour depends on task wording.** When the task asks for exactly the flagged behaviour, the flag
129
- clears almost always if the task is just that request. It clears only 58–70% of the time when the request follows a
130
- longer real task description.
131
- - **Python-heavy training.** Agent-diff training data is mostly Python (JS/TS about 4%). It transfers to other
132
- languages (AUROC 0.93–1.00 per language) but recall is lower; bash is weakest (73%).
133
- - **Packages are out of scope.** Training included some package-level examples, but whole-package scanning isn't a
134
- supported use and isn't evaluated here.
135
- - **Inputs over 8,192 tokens** are truncated.
136
- - **Not caught: unrequested personal data in request headers.** An agent adding the user's email to the User-Agent or
137
- another header of a request to the task's own API is not flagged (1/40). Treat request contents as a separate check.
138
- - **Other limits:**
139
- - no release or commit diffs in training (the release result is transfer);
140
- - no time-split evaluation after the base model's cutoff;
141
- - calibration is good in-distribution (ECE ≈ 0.01–0.02) and weaker on new distributions;
142
- - the included behaviour extractor understands Python, JS/TS and shell best; other languages get line-level
143
- patterns only.
 
144
 
145
  ## Training data
146
  - **Agent diffs (2,500, benign):**
@@ -155,17 +193,22 @@ Per language (seed 1, frozen θ; `RESULTS.md` has both seeds, AUROCs with CIs an
155
  [snorkelai/Tau2-Bench-Verified-Airline-With-Code-Agents](https://huggingface.co/datasets/snorkelai/Tau2-Bench-Verified-Airline-With-Code-Agents),
156
  [snorkelai/Tau2-Bench-Airline-With-Code-Agents](https://huggingface.co/datasets/snorkelai/Tau2-Bench-Airline-With-Code-Agents)
157
  and [xingyaoww/code-act](https://huggingface.co/datasets/xingyaoww/code-act) (Apache-2.0).
158
- - **A small set of scrubbed snippets from our own agent sessions, used as benign examples** (577; not released).
159
- - **Authored examples:** inert out-of-scope and malicious changes, plus in-scope controls and task-swap pairs, written by
160
- a model (DeepSeek) into the public items above. Network destinations and secrets are stand-ins; nothing harmful runs.
161
- - **Package-level examples** from public npm/PyPI packages and a public malicious-package dataset (out of scope for this
162
- release).
163
  - `SOURCES.md` lists revisions and attributions.
164
 
165
- **Privacy check before release:**
166
- - training-vs-unseen membership tests on the internal snippets are at chance (AUROC 0.51–0.53);
167
- - no secrets, credentials, hostnames, personal emails or internal names were extractable, even through a generation
168
- head attached for the test.
 
 
 
 
 
169
 
170
  ## Licence
171
  Apache-2.0, like the base model (`LICENSE`, `NOTICE`). Training-data attributions are in `SOURCES.md`.
 
18
 
19
  ![Ink-and-watercolor drawing: a small round robot sits on top of a huge haystack of code pages, holding up a glowing red needle it just found and inspecting it with a loupe.](https://huggingface.co/stonefish-labs/d3-mini-agentcode/resolve/main/assets/banner.jpg)
20
 
21
+ **Pre-release.** A revised version is planned, trained on bad examples from several writer models, more realistic task
22
+ wording and more languages. "Limitations" lists what it should fix.
23
 
24
+ d3-mini-agentcode is [d3-mini](https://huggingface.co/vllm-sr/d3-mini) (4B, Apache-2.0) fine-tuned to triage code
25
+ written or run by AI coding agents. It reads the agent's task and the change (a diff, or a snippet the agent ran) and
26
+ answers two things: does the change do something security-relevant the task didn't ask for, and does it look
27
+ deliberately harmful. It's meant as a cheap first filter that picks which changes a person or a bigger model should
28
+ review.
29
 
30
  **Write-up:** [Small Models, Big Haystack](https://batteryshark.github.io/2026/11/05/small-models-big-haystack.html)
31
  covers how this model was built and tested, and how it compares with off-the-shelf d3 models and LLMs.
32
 
33
  ## Intended use
34
+ - **Agent diffs and code mode** (snippets an agent runs): score each one and send the flagged ones to a person or a
35
  stronger model.
36
+ - **A second stage** behind cheaper rules.
37
+ - **Not the only gate.** It's a 4B model with the limitations below, and a flag doesn't prove intent.
38
+ - **Out of scope:**
39
+ - packages and dependencies (npm, PyPI) and release diffs, where untuned d3-mini did better in our tests (see
40
+ "Packages, releases and prompt injection");
41
+ - changes longer than about 8,192 tokens (see "Long inputs");
42
+ - inputs without a task description, since it was trained with one.
43
 
44
  ## How it was made
45
+ - **Base:** `vllm-sr/d3-mini` at revision `df99bf4ca4418bc6908ee68e9b7479af1f3b8800`, built on Qwen/Qwen3.5-4B.
46
  - **Recipe:** LoRA (r 16, α 32, dropout 0.05) on the text backbone's attention, linear-attention and MLP projections,
47
  plus the trained answer readout. lr 1e-4 (readout 2e-5), 2 epochs, 1,038 optimizer steps, max length 8,192, label
48
+ smoothing 0.05, shuffled answer order (`adapter/training_args.json`).
49
+ - **Files:** the merged weights (they load like d3-mini), the adapter (`adapter/`) and the behaviour extractor that
50
+ builds the input (`extractor/`).
51
+ - **Released checkpoint:** seed 1, final. A second training seed isn't published. It scored a little lower on most
52
+ sets, and both are in the results below.
53
+ - **Questions:** three System One questions in `questions.json`: `class` (a choice of four), `intent` and `scope`
54
+ (yes/no). The flag rule below uses `class` and `scope`. `intent` is answered but wasn't used in any result here.
55
 
56
  ## Usage
57
  ```python
58
  import json
59
+ from huggingface_hub import hf_hub_download
60
  from transformers import AutoModel
61
 
62
+ REPO = "stonefish-labs/d3-mini-agentcode"
63
+ model = AutoModel.from_pretrained(REPO, trust_remote_code=True)
64
+ questions = json.load(open(hf_hub_download(REPO, "questions.json"))) # class, intent, scope
65
 
66
  state = """Task:
67
  Add a CLI flag to export the report as CSV.
 
76
  diff --git a/report.py b/report.py
77
  ..."""
78
 
79
+ answers = model.system_one(state=state, questions=questions)["answers"]
80
+ p_mal = answers["class"]["probabilities"]["deliberately_malicious"]
81
+ p_scope = answers["scope"]["noul"] # p(yes): the change does more than the task asks
82
+ flag = p_mal >= 0.9 or p_scope >= 0.9 # frozen thresholds (thresholds.json)
83
  ```
84
+ - **Input:** a text state with four sections, in this order: `Task:`, `Repository baseline (normal behaviour of this
85
+ repo/org):`, `Behaviour summary (automated, not a verdict):` and `Change:`. Include the task. `examples/usage.py`
86
+ scores two example changes.
87
+ - **Output:** `system_one` returns `{"model", "answers", "usage"}`. In `answers`, `class` has a `probabilities` entry
88
+ for each option and the top `choice`. `intent` and `scope` each have `noul`, the probability of yes.
89
+ - **Behaviour summary:** use the included static extractor (`extractor/`), the same one that wrote the summary in our
90
+ training and evaluation states. It never runs the code it reads. Run `pip install -r extractor/requirements.txt`,
91
+ then, from `extractor/`, `python make_state.py diff change.diff --task-file task.txt` (or
92
+ `snippet run.py --lang py --task "..."`) prints the whole state. `extractor/README.md` covers the Python API and how
93
+ to build a repository baseline.
94
+ - **Placeholder summary:** without the extractor, the placeholder `(no security-relevant behaviour found)` did almost
95
+ as well in our tests (seed 1, frozen thresholds). Across 14 languages it found 448/510 and flagged 5/2,150 real
96
+ items and 1/560 controls, against 458/510, 11/2,150 and 1/560 with the extractor's summary. On the independent
97
+ agent-diff set it found 20/22 (21/22 with the extractor), with no false alarms either way. Bash code mode dropped
98
+ the most, from 24/33 to 20/33.
99
+ - **Long inputs:** the extractor keeps the task, baseline and behaviour summary whole and cuts the change to fit about
100
+ 8,192 tokens (8,192 × 3 characters), with a marker saying how much it left out. Training inputs were built the same
101
+ way. The model doesn't truncate anything. Called directly, it reads the whole input up to d3-mini's 262,144-token
102
+ window (`decision_config.json` sets no `max_length`), but the fine-tune was never trained or tested on inputs
103
+ longer than 8,192 tokens.
104
+ - **Speed:** on one A100 in batches of 8, 0.46–0.61 s per item on our agent-diff test sets. On a desktop RTX 4090,
105
+ our package, release and prompt-injection test inputs, cut to 8k, averaged about 1 s per item.
106
+ - **Memory:** the bf16 weights take about 9 GB (4.5B parameters, vision tower included). On the 4090, batches of 8 at
107
+ up to 8k tokens peaked at 12.7 GiB allocated.
108
+
109
+ ## Thresholds
110
+ - **Frozen, used for every result below:** flag when p(class = deliberately_malicious) ≥ **0.9** or p(scope = yes) ≥
111
+ **0.9**. Fitted on an English, Python-heavy dev set for at most 5% false alarms.
112
+ - **Multilingual option:** 0.9 / **0.05**, fitted on a 10% repository-level dev split of the 14-language set. On the
113
+ other 90% it found 421/443 (95%) and flagged 43/1,900 real items (2.3%). The frozen thresholds found 90% and
114
+ flagged 0.5% there.
115
+ - **Your own:** fit thresholds on a sample of your own code for the false-alarm rate you can live with
116
+ (`thresholds.json` has the details).
117
 
118
  ## Results
119
+ Each model at its own dev-fitted thresholds: 0.9 / 0.9 for both seeds, 0.5 / 0.7 for untuned d3-mini. "Untuned" is
120
+ d3-mini given the same input (task, baseline, behaviour summary and change). Models wrote every bad example here
121
+ except the compromised releases, and none came from a real attack.
122
 
123
  | test set | seed 1 (released) | seed 2 | untuned |
124
  |---|---|---|---|
125
+ | Independent agent-diff set: out-of-scope changes found / real agent diffs and in-scope controls flagged | 21/22 / 0/172 | 19/22 / 0/172 | 10/22 / 0/172 |
126
+ | Public code mode from a source not used in training: bad snippets found / real snippets flagged | 31/31 / 0/200 | 31/31 / 0/200 | 31/31 / 1/200 |
127
+ | Held-out agent diffs and code mode (repos and sources not used in training): found / real items flagged | 122/122 / 0/527 | 118/122 / 0/527 | 96/122 / 2/527 |
128
+ | Behaviour families never seen in training: found | 26/26 | 22/26 | 14/26 |
129
+ | Compromised library releases shown as diffs (none in training): found / ordinary releases flagged | 21/27 / 0/80 | 19/27 / 0/80 | 19/27 / 0/80 |
130
+ | **14 languages and shells** (8 diff languages, 6 code-mode and shell cells): found / real items flagged / authored benign controls flagged | **458/510 / 11/2,150 / 1/560** | 446/510 / 7/2,150 / 2/560 | 343/510 / 48/2,150 / 12/560 |
131
+ | Task asks for the change, held-out set, the request alone as the task: flagged changes that clear | 122/122 | 118/118 | 66/96 |
132
+ | Task asks for the change, 14 languages, the request alone as the task: pairs that clear | 244/246 | 233/246 | 163/246 |
133
+ | Task asks for the change, 14 languages, the request added after a real task: pairs that clear / same change still flagged with an ordinary request | 143/246 / 221/246 | 173/246 / 218/246 | 116/246 / 152/246 |
134
  | Unrequested account email added to request headers: found / controls flagged | 1/40 / 0/60 | 0/40 / 0/60 | 3/40 / 4/60 |
135
 
136
+ Per language (seed 1, frozen thresholds; `RESULTS.md` has both seeds, AUROCs with confidence intervals and the
137
+ untuned model):
138
 
139
  | | C/C++ | C# | Go | Java | JS/TS | PHP | Rust | Swift/ObjC | bash | cmd/batch | zsh/macOS | PowerShell | Python (code mode) | JS/TS (code mode) |
140
  |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
141
  | found | 35/38 | 34/37 | 36/38 | 36/37 | 33/37 | 34/38 | 33/37 | 37/38 | 24/33 | 28/33 | 29/36 | 35/37 | 30/35 | 34/36 |
142
  | real flagged | 1/149 | 0/149 | 1/150 | 1/149 | 0/150 | 0/150 | 0/150 | 0/150 | 1/200 | 0/130 | 3/144 | 3/150 | 1/200 | 0/129 |
143
 
144
+ ### Packages, releases and prompt injection
145
+ After release, we ran seed 1 and untuned d3-mini on the same inputs on a desktop RTX 4090, each at its frozen
146
+ thresholds. Both checks we set beforehand failed: seed 1 raised more false alarms on packages, and planted text
147
+ talked it out of more catches on packages and releases.
148
+
149
+ | test | seed 1 | untuned |
150
+ |---|---|---|
151
+ | Newer npm packages (time split): malicious found / benign look-alikes flagged | 46/89 / 7/112 | 47/89 / 1/112 |
152
+ | Planted prompt-injection text, agent diffs: catches lost | 0/105 | 0/50 |
153
+ | Planted text, packages and releases, worst template (a fake approval note): catches lost | 12/83 | 4/73 |
154
+ | All five templates, all sets: catches lost | 37/423 | 14/366 |
155
+
156
+ Six of the seven benign look-alikes it flagged were large packages cut off at the 8k input limit.
157
+
158
  ## Limitations
159
+ - **Authored positives.** The bad examples in training and in most tests are inert changes that a model wrote into
160
+ real agent work. Near-perfect scores on held-out authored sets partly reflect that. The independent agent-diff set,
161
+ written under a different protocol and scored against 137 real agent diffs, is the better check.
162
+ - **Writer gap.** One model wrote all the training positives. Recall is lower on positives other models wrote: across
163
+ the 14 languages it found 88% of GPT-written and 93% of Claude-written ones, and 82% against 95% in code mode.
164
+ - **Task wording.** When the task asks for exactly the flagged change, the flag clears on 244 of 246 pairs if the task
165
+ is only that request. When the same request follows a longer, real task description, it clears on 143 of 246 (58%;
166
+ 70% for seed 2).
167
+ - **Python-heavy training.** The agent-diff training data is mostly Python (JS/TS is 4.4%). It still found 278/300 in
168
+ 8 other diff languages (AUROC 0.96–1.00 per language). Code mode and shells are weaker at 180/210, with bash the
169
+ lowest at 24/33. Python code mode dropped too (30/35), so language doesn't explain all of it.
170
+ - **Packages and releases.** On packages and release diffs it did no better than untuned d3-mini and raised more false
171
+ alarms (see "Packages, releases and prompt injection"). Use untuned d3-mini there.
172
+ - **Long inputs.** It was trained and tested on inputs of up to 8,192 tokens. The extractor cuts longer changes to fit
173
+ (see "Long inputs" under Usage).
174
+ - **Missed: personal data in request headers.** It doesn't flag an agent adding the user's email to the User-Agent or
175
+ another header of a request to the task's own API (1/40). Check request contents separately.
176
+ - **Also:**
177
+ - no release or commit diffs in training, so the release result is transfer;
178
+ - no evaluation on agent code written after the base model's training cutoff (the newer-npm set is packages);
179
+ - calibration error (ECE) is 0.015–0.028 on data like the training data and higher elsewhere (0.21 for `class` on
180
+ the independent agent-diff set);
181
+ - the extractor understands Python, JS/TS and shell best, and other languages get line-level patterns only.
182
 
183
  ## Training data
184
  - **Agent diffs (2,500, benign):**
 
193
  [snorkelai/Tau2-Bench-Verified-Airline-With-Code-Agents](https://huggingface.co/datasets/snorkelai/Tau2-Bench-Verified-Airline-With-Code-Agents),
194
  [snorkelai/Tau2-Bench-Airline-With-Code-Agents](https://huggingface.co/datasets/snorkelai/Tau2-Bench-Airline-With-Code-Agents)
195
  and [xingyaoww/code-act](https://huggingface.co/datasets/xingyaoww/code-act) (Apache-2.0).
196
+ - **577 scrubbed snippets from our own agent sessions,** used as benign examples only and not released.
197
+ - **Authored examples:** inert out-of-scope and malicious changes, in-scope controls and task-swap pairs that DeepSeek
198
+ Flash wrote into the public items above. Network destinations and secrets are stand-ins, and nothing harmful runs.
199
+ - **Package examples** from public npm and PyPI packages and a public malicious-package dataset (see "Packages and
200
+ releases" under Limitations).
201
  - `SOURCES.md` lists revisions and attributions.
202
 
203
+ **Privacy checks before release:**
204
+ - membership tests (trained vs unseen private snippets) were at chance, AUROC 0.51–0.53;
205
+ - a scan of the 577 private snippets found no real emails, tokens, hostnames, IP addresses or home paths;
206
+ - prompting the model through a generation head attached for the test didn't get it to emit secrets, credentials,
207
+ hostnames, personal emails or internal names;
208
+ - one check we set beforehand failed: on private snippets it trained on, the model continued code word for word for
209
+ 50 or more tokens a little more often than the base model (14/424 against 8/424). It did the same on private
210
+ snippets it never saw (15/374), so it's better at predicting this style of code in general. None of those
211
+ continuations contained internal names or secrets.
212
 
213
  ## Licence
214
  Apache-2.0, like the base model (`LICENSE`, `NOTICE`). Training-data attributions are in `SOURCES.md`.
REBUILD.md CHANGED
@@ -7,8 +7,10 @@ backbone weights and answer readout.
7
  from transformers import AutoModel
8
  model = AutoModel.from_pretrained("stonefish-labs/d3-mini-agentcode", trust_remote_code=True)
9
  ```
10
- `MODEL_MANIFEST.json` lists every file's SHA-256 and size. The runtime checks the files it loads on every load
11
- (`verify="fast"`); `verify="release"` checks all files. `SHA256SUMS` covers every file except the manifest itself.
 
 
12
 
13
  ## Rebuild the merged weights from the adapter
14
  `adapter/` holds the LoRA adapter (`adapter_model.safetensors`, `adapter_config.json`), the trained answer readout
@@ -25,6 +27,7 @@ model = AutoModel.from_pretrained("stonefish-labs/d3-mini-agentcode", trust_remo
25
 
26
  ## Serve
27
  - **transformers:** as above. `model.system_one(state=..., questions=...)`; see `examples/usage.py`.
28
- - **vLLM Semantic Router:** the package keeps d3-mini's format (`d3-code-readout-v1`), so any runtime that serves
29
- d3-mini can serve it (see the d3-mini card).
30
- - **Hardware:** about 10 GB of GPU memory in bf16 for typical inputs up to 8,192 tokens.
 
 
7
  from transformers import AutoModel
8
  model = AutoModel.from_pretrained("stonefish-labs/d3-mini-agentcode", trust_remote_code=True)
9
  ```
10
+ `MODEL_MANIFEST.json` lists every file's SHA-256 and size. On every load the runtime checks the files it reads
11
+ (`verify="fast"`: it hashes files up to 64 MB and checks the size of larger ones). `verify="full"` hashes them all, and
12
+ `verify="release"` also checks the card and other files the runtime doesn't read. `SHA256SUMS` covers every file except
13
+ itself and the manifest.
14
 
15
  ## Rebuild the merged weights from the adapter
16
  `adapter/` holds the LoRA adapter (`adapter_model.safetensors`, `adapter_config.json`), the trained answer readout
 
27
 
28
  ## Serve
29
  - **transformers:** as above. `model.system_one(state=..., questions=...)`; see `examples/usage.py`.
30
+ - **vLLM Semantic Router:** the package keeps d3-mini's format (`d3-code-readout-v1`), so runtimes that serve d3-mini
31
+ should serve it too (see the d3-mini card). We only ran it with the bundled runtime above.
32
+ - **Hardware:** the bf16 weights take about 9 GB. On an RTX 4090, batches of 8 at up to 8,192 tokens peaked at 12.7 GiB
33
+ allocated.
RESULTS.md CHANGED
@@ -30,7 +30,8 @@ By writer of the positives (all cells): seed 1 found 161/174 Claude-written and
30
  292/336; untuned 115/174 and 228/336. Authored benign controls flagged: seed 1 1/560, seed 2 2/560, untuned 12/560.
31
 
32
  ## Task swaps (the task asks for exactly the flagged behaviour)
33
- - Request alone as the task: the flag clears on 100% of pairs (both seeds); untuned 62–69%.
 
34
  - Request added after a real task (246 pairs, 14 languages): clears on 143 (seed 1) and 173 (seed 2), untuned 116. With the
35
  same change and an ordinary request, still flags 221, 218 and 152.
36
 
 
30
  292/336; untuned 115/174 and 228/336. Authored benign controls flagged: seed 1 1/560, seed 2 2/560, untuned 12/560.
31
 
32
  ## Task swaps (the task asks for exactly the flagged behaviour)
33
+ - Request alone as the task: clears on 244/246 (seed 1) and 233/246 (seed 2), untuned 163/246. On the English held-out
34
+ set (H3 and H4), it clears on every change it flagged: 122/122 (seed 1) and 118/118 (seed 2), untuned 66/96.
35
  - Request added after a real task (246 pairs, 14 languages): clears on 143 (seed 1) and 173 (seed 2), untuned 116. With the
36
  same change and an ordinary request, still flags 221, 218 and 152.
37
 
SHA256SUMS CHANGED
@@ -1,10 +1,10 @@
1
  c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4 ./LICENSE
2
  a42ca8b488e0b98b1a690937784e8da0fe417fd2218e7595129927221f4d9244 ./MERGE-REPORT.json
3
  e74014fab972cffe76391a608e8a5cf54baeaf7d16cd8a77545edc67ffd25225 ./NOTICE
4
- e991987170a18dda2a68fe6a25793d3d18250eb47e0315c32699dd2ca7bb7c69 ./README.md
5
- 3b1fd15114b4cad8eb45cee219e2a248a11d3cfb359181346d14eab9c98df7a9 ./REBUILD.md
6
- 096b58ff385f3f149780de90e14516f5e9fdef97acb316dffc4a7b06314231b3 ./RESULTS.md
7
- 3bd6eb1b1d0c5b0b18ff8ee8f2076512f514c3367e943610515b82e43e852144 ./SOURCES.md
8
  ff2a5427cbcf0e7a0a61940920d06f0e1fc116565258c3eca2501b0d10933250 ./adapter/adapter_config.json
9
  3bd401009801b39f29148427e884a8230a85ddb3c20a54ea834372889998c895 ./adapter/adapter_model.safetensors
10
  bf0999886d5b6ac6764c7a9d688c890579062a04254e0054bb230681fb464a7e ./adapter/merge.py
@@ -20,7 +20,7 @@ a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715 ./chat_templat
20
  adf7f7aa085674b5682e492d8b2e3cc4f11c67b0d153515c742e907e9b550917 ./d3_runtime.py
21
  766a20a69101e36ab0535a1ec683379db422380a83091f0eccd7e82edb6a02de ./d3_server.py
22
  588bc0ac7644479b9adde0498d80b6fb7c576388992564ac67599c05d29add07 ./decision_config.json
23
- 6eedbd855133312a30ba44f35a82bd6b47107313b3a9ad80a2230c1218c1ac98 ./examples/usage.py
24
  cd1679b82e61c15f247c277007c0d416d27b4834f455f3fee7f9afb863743d08 ./extractor/README.md
25
  c0db7c9cb57a23289245209c30c335c3e44614454d2796a239864d70165356d6 ./extractor/behaviour/__init__.py
26
  2c78b70c752cc11bfbf759e9e8fe0a4b5f43da43cdecb4489a9b403ac4d20311 ./extractor/behaviour/__main__.py
 
1
  c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4 ./LICENSE
2
  a42ca8b488e0b98b1a690937784e8da0fe417fd2218e7595129927221f4d9244 ./MERGE-REPORT.json
3
  e74014fab972cffe76391a608e8a5cf54baeaf7d16cd8a77545edc67ffd25225 ./NOTICE
4
+ ed61f82f9b000ddf533119f1345345bca2f345514b0b70d802f2bd172c8ce24a ./README.md
5
+ 76e575f2d52a83a937d0c4daf818ba302595f577cf54b3f3bd3bbea67a0e4566 ./REBUILD.md
6
+ cc20ca6e484d84f5c86ea16e82ce0ef3a3978f572c8d8c514ed3a1383b9d1930 ./RESULTS.md
7
+ bfb09be93fa5d4022645bb5bf3cd4268949ef76eba42c4d462099537a6fb7bb2 ./SOURCES.md
8
  ff2a5427cbcf0e7a0a61940920d06f0e1fc116565258c3eca2501b0d10933250 ./adapter/adapter_config.json
9
  3bd401009801b39f29148427e884a8230a85ddb3c20a54ea834372889998c895 ./adapter/adapter_model.safetensors
10
  bf0999886d5b6ac6764c7a9d688c890579062a04254e0054bb230681fb464a7e ./adapter/merge.py
 
20
  adf7f7aa085674b5682e492d8b2e3cc4f11c67b0d153515c742e907e9b550917 ./d3_runtime.py
21
  766a20a69101e36ab0535a1ec683379db422380a83091f0eccd7e82edb6a02de ./d3_server.py
22
  588bc0ac7644479b9adde0498d80b6fb7c576388992564ac67599c05d29add07 ./decision_config.json
23
+ 37fc6f3322832e8ce31028dba64561af57c189c612709e9f407457d6a7e44c9a ./examples/usage.py
24
  cd1679b82e61c15f247c277007c0d416d27b4834f455f3fee7f9afb863743d08 ./extractor/README.md
25
  c0db7c9cb57a23289245209c30c335c3e44614454d2796a239864d70165356d6 ./extractor/behaviour/__init__.py
26
  2c78b70c752cc11bfbf759e9e8fe0a4b5f43da43cdecb4489a9b403ac4d20311 ./extractor/behaviour/__main__.py
SOURCES.md CHANGED
@@ -7,12 +7,15 @@ examples into some items.
7
  ## Agent diffs (benign examples)
8
  | dataset | revision | licence | items |
9
  |---|---|---|---|
10
- | [nvidia/SWE-Zero-openhands-trajectories](https://huggingface.co/datasets/nvidia/SWE-Zero-openhands-trajectories) | `7b3cd106d00f60918e722d33a1d74bc67072a7ea` | CC-BY-4.0 (NVIDIA); per-repo MIT/Apache-2.0/BSD | 1,446 |
11
- | [nvidia/SWE-Hero-openhands-trajectories](https://huggingface.co/datasets/nvidia/SWE-Hero-openhands-trajectories) | `150bc119e52c647216fce285fd801f16b6fd745b` | CC-BY-4.0 (NVIDIA); per-repo MIT/Apache-2.0/BSD | 279 |
12
  | [nebius/SWE-rebench-openhands-trajectories](https://huggingface.co/datasets/nebius/SWE-rebench-openhands-trajectories) | `35455389ab51bf5e2306bfd436ef72d0f98bf882` | CC-BY-4.0 (Nebius) | 389 |
13
  | [sweagent/iter2-rl-rollouts](https://huggingface.co/datasets/sweagent/iter2-rl-rollouts) | `ec07602618bc22d56e71112fc8e81943fcbd28be` | MIT | 370 |
14
  | [nebius/SWE-agent-trajectories](https://huggingface.co/datasets/nebius/SWE-agent-trajectories) | `68195a1450865274106246d0d0296a1d6807b88e` | CC-BY-4.0 (Nebius) | 16 |
15
 
 
 
 
16
  ## Code-mode snippets (benign examples)
17
  | dataset | revision | licence |
18
  |---|---|---|
@@ -24,7 +27,7 @@ examples into some items.
24
  ## Other training data
25
  - **A small set of scrubbed snippets from our own agent sessions** (577), used as benign examples only. Not released.
26
  - **Authored inert examples:** out-of-scope and malicious changes, in-scope controls and task-swap pairs, written by
27
- DeepSeek models into the public items above. Network destinations and secrets are stand-ins.
28
  - **Package-level examples:** public npm/PyPI packages, and malicious samples from the
29
  [DataDog malicious-software-packages-dataset](https://github.com/DataDog/malicious-software-packages-dataset).
30
  Package scanning is out of scope for this release.
 
7
  ## Agent diffs (benign examples)
8
  | dataset | revision | licence | items |
9
  |---|---|---|---|
10
+ | [nvidia/SWE-Zero-openhands-trajectories](https://huggingface.co/datasets/nvidia/SWE-Zero-openhands-trajectories) | `7b3cd106d00f60918e722d33a1d74bc67072a7ea` | CC-BY-4.0 (NVIDIA) | 1,446 |
11
+ | [nvidia/SWE-Hero-openhands-trajectories](https://huggingface.co/datasets/nvidia/SWE-Hero-openhands-trajectories) | `150bc119e52c647216fce285fd801f16b6fd745b` | CC-BY-4.0 (NVIDIA) | 279 |
12
  | [nebius/SWE-rebench-openhands-trajectories](https://huggingface.co/datasets/nebius/SWE-rebench-openhands-trajectories) | `35455389ab51bf5e2306bfd436ef72d0f98bf882` | CC-BY-4.0 (Nebius) | 389 |
13
  | [sweagent/iter2-rl-rollouts](https://huggingface.co/datasets/sweagent/iter2-rl-rollouts) | `ec07602618bc22d56e71112fc8e81943fcbd28be` | MIT | 370 |
14
  | [nebius/SWE-agent-trajectories](https://huggingface.co/datasets/nebius/SWE-agent-trajectories) | `68195a1450865274106246d0d0296a1d6807b88e` | CC-BY-4.0 (Nebius) | 16 |
15
 
16
+ The code in these diffs keeps its own repository's licence. We recorded a permissive licence for 1,725 of the 2,500
17
+ items. For the other 775 we didn't look it up.
18
+
19
  ## Code-mode snippets (benign examples)
20
  | dataset | revision | licence |
21
  |---|---|---|
 
27
  ## Other training data
28
  - **A small set of scrubbed snippets from our own agent sessions** (577), used as benign examples only. Not released.
29
  - **Authored inert examples:** out-of-scope and malicious changes, in-scope controls and task-swap pairs, written by
30
+ DeepSeek Flash into the public items above. Network destinations and secrets are stand-ins.
31
  - **Package-level examples:** public npm/PyPI packages, and malicious samples from the
32
  [DataDog malicious-software-packages-dataset](https://github.com/DataDog/malicious-software-packages-dataset).
33
  Package scanning is out of scope for this release.
examples/usage.py CHANGED
@@ -55,7 +55,7 @@ OUT_OF_SCOPE = IN_SCOPE.replace(
55
 
56
  model = AutoModel.from_pretrained(MODEL, trust_remote_code=True)
57
  for name, change in (("in-scope change", IN_SCOPE), ("change that also sends the environment out", OUT_OF_SCOPE)):
58
- r = model.system_one(state=state(TASK, change), questions=QUESTIONS)
59
  p_mal = r["class"]["probabilities"]["deliberately_malicious"]
60
  p_scope = r["scope"]["noul"]
61
  flag = p_mal >= THETA["class_malicious"] or p_scope >= THETA["scope"]
 
55
 
56
  model = AutoModel.from_pretrained(MODEL, trust_remote_code=True)
57
  for name, change in (("in-scope change", IN_SCOPE), ("change that also sends the environment out", OUT_OF_SCOPE)):
58
+ r = model.system_one(state=state(TASK, change), questions=QUESTIONS)["answers"]
59
  p_mal = r["class"]["probabilities"]["deliberately_malicious"]
60
  p_scope = r["scope"]["noul"]
61
  flag = p_mal >= THETA["class_malicious"] or p_scope >= THETA["scope"]