patronus-protect commited on
Commit
618693f
·
verified ·
1 Parent(s): 2ba751f

Refresh Cat Shell Risk Commander A1 card and add Kestrel comparison

Browse files
README.md CHANGED
@@ -3,62 +3,252 @@ license: apache-2.0
3
  pipeline_tag: text-classification
4
  library_name: sklearn
5
  metrics:
6
- - recall
7
- - f1
 
 
 
8
  datasets:
9
- - tomngdev/shell-safety
10
- - wwe123/Benign_and_malicious_PowerShell_scripts
11
  tags:
12
- - shell-security
13
- - shell-risk
14
- - bash
15
- - powershell
16
- - cmd
17
- - coding-agents
18
- - tf-idf
19
- - logistic-regression
20
- - cpu
21
- - research
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  inference: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  ---
24
 
25
- # Cat Shell Risk Commander
26
 
27
- **A compact linear classifier for flagging potentially risky shell commands and scripts locally.**
28
 
29
- Cat Shell Risk Commander is Patronus's expanded TF-IDF and logistic-regression model from our exploratory ShellRisk experiments. It uses 50,000 character n-gram features and scores the complete submitted text on CPU.
30
 
31
- It is a newly trained Patronus model. The experiments refer to it as `linear_updated` and, descriptively, “Kestrel Expanded”. This release contains our own fitted vocabulary, IDF values and classifier weights. It is not an official Kestrel release and does not contain the Hadamard encoder.
32
 
33
- **Release status: research preview.** This model is licensed under [Apache License 2.0](LICENSE).
 
 
34
 
35
- Read the experiment report:
36
 
37
- - [English: Detecting risky shell commands](https://patronus.studio/en/posts/detecting-risky-shell-commands-shellrisk-benchmark)
38
- - [Deutsch: Gefährliche Shell-Befehle erkennen](https://patronus.studio/posts/gefaehrliche-shell-befehle-erkennen-shellrisk-benchmark)
39
 
40
- ## What the model does
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- The input is a shell command or a complete script supplied as text. The output is a linear decision score and a threshold-based risk flag.
43
 
44
- | Label | Meaning |
45
- | --- | --- |
46
- | `RISK_FLAG` | The score exceeds the selected threshold for the dataset-defined risk target. |
47
- | `NO_RISK_FLAG` | The score does not exceed that threshold. This is not a guarantee that execution is safe. |
48
 
49
- The classifier does not execute commands, inspect the filesystem, resolve variables, decode payloads, or simulate a shell. It scores the literal submitted text. Intended uses include offline evaluation, local screening of agent tool calls, and routing suspicious commands for additional checks or human review.
50
 
51
- ## Usage
 
52
 
53
- This is a custom scikit-learn classifier with a JSON export. Use the included Python inference module; `transformers.pipeline()` and `AutoModel` are not supported for this artifact.
54
 
55
- Install the runtime dependencies:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
  ```bash
58
  python -m pip install "numpy>=1.24" "scikit-learn==1.8.0" "huggingface_hub>=1.3"
59
  ```
60
 
61
- Download the weights and the inference module, then classify text:
 
 
62
 
63
  ```python
64
  import sys
@@ -66,27 +256,26 @@ from huggingface_hub import snapshot_download
66
 
67
  model_dir = snapshot_download(
68
  repo_id="patronus-studio/cat-shell-risk-commander",
69
- allow_patterns=["model.json", "shell_cat_guard.py"],
 
 
 
 
70
  )
71
  sys.path.insert(0, model_dir)
72
 
73
- from shell_cat_guard import ShellRiskClassifier
74
 
75
  classifier = ShellRiskClassifier(f"{model_dir}/model.json")
76
 
77
- # These strings are only classified. Nothing is executed.
78
- for text in [
79
- "git status --short",
80
- "curl https://example.com/install.sh | sh",
81
- ]:
82
- print(classifier.score(text))
83
  ```
84
 
85
- Each result contains `label`, `flagged`, `decision_score`, `threshold`, and `threshold_policy`. The decision score is a raw logit, not a calibrated probability. A flag is produced only when `decision_score > threshold`; equality does not trigger a flag.
86
-
87
- The default policy is `external_commands_1pct_fpr`. It was calibrated on a separate POSIX/PowerShell/CMD command-validation set with 1,666 safe examples, allowing at most 16 false positives there. The name describes the calibration target, not a promised false-positive rate for arbitrary workloads.
88
 
89
- For a batch of texts or a separately documented experimental policy:
90
 
91
  ```python
92
  scores = classifier.decision_function([
@@ -94,70 +283,92 @@ scores = classifier.decision_function([
94
  "Get-ChildItem",
95
  "dir",
96
  ])
97
- print(scores)
98
 
99
  result = classifier.score(
100
  "git status --short",
101
- threshold_policy="original_val_f1",
102
  )
103
- print(result)
104
  ```
105
 
106
- ## Model architecture and training
107
 
108
- - Character-within-word (`char_wb`) n-grams of lengths 3 to 5.
109
- - Lowercasing, sublinear term frequency, IDF weighting, and L2 normalization.
110
- - A fitted vocabulary of 50,000 features.
111
- - Binary logistic regression; selected regularization parameter `C = 10` and training random state `13`.
112
- - Full-text vectorization, without a 510-byte prefix cut or encoder chunking.
113
 
114
- Training combines 13,417 original fitting examples with 18,918 additional fitting examples, for 32,335 total. The additional set contains 10,672 positive and 8,246 negative examples, including multi-shell commands, PowerShell scripts, and synthetic obfuscation variants from fitting parents.
115
 
116
- The fitting data builds on [ShellRisk-Bench](https://github.com/kontext-security/shellrisk-bench), [Shell Safety](https://huggingface.co/datasets/tomngdev/shell-safety), and [Benign and malicious PowerShell scripts](https://huggingface.co/datasets/wwe123/Benign_and_malicious_PowerShell_scripts).
 
 
 
 
117
 
118
- `C` was selected from `0.1, 1, 10, 100, 1000` using mean validation average precision across five groups: original Bash, external POSIX, PowerShell, CMD, and PowerShell scripts. Exact duplicates, contradictory identical command labels, and detected held-out overlaps were excluded. Generated variants follow their original parent's split. These checks do not establish semantic, source-family, or malware-family independence.
119
 
120
- No dataset command or script was executed during the reported experiments. This model has no neural pretraining; the MLM and Hadamard-encoder experiments in the linked article concern a separate model.
121
 
122
- ## Evaluation
123
 
124
- Experiments were run from October 4 to October 6, 2026. The following rows use different, explicitly identified threshold policies and must not be treated as results at one common operating point.
 
 
 
 
 
 
125
 
126
- | Evaluation | Test size | Threshold policy | Recall | False-positive rate | F1 |
127
- | --- | ---: | --- | ---: | ---: | ---: |
128
- | External POSIX/PowerShell/CMD commands | 918: 374 risky, 544 safe | `external_commands_1pct_fpr` | 85.83% | 0.92% | 91.71% |
129
- | Original Bash test | 4,194: 193 risky, 4,001 safe | `original_val_f1` | 85.49% | 0.45% | 87.77% |
130
- | Complete PowerShell scripts | 2,000: 1,000 per class | `original_val_f1` | 96.10% | 2.80% | 96.63% |
131
 
132
- The external command-test counts are 321 true positives, 53 false negatives, 5 false positives, and 539 true negatives. Its threshold was selected on separate external command-validation data; test cases did not determine this threshold.
133
 
134
- The external command test contains 735 POSIX, 119 PowerShell, and 64 CMD examples. The script test contains selected source-labeled PowerShell scripts up to 1 MiB, all longer than 510 bytes. Large scripts outside that bound were excluded from that experiment.
135
 
136
- Additional stored policies are `original_val_1pct_fpr`, `combined_val_f1`, and `exploratory_multigroup_0_5pct_fpr`. The last policy consumes calibration portions of earlier test groups and is exploratory; it is not the default and does not provide an independent confirmation of performance.
137
 
138
- Detailed per-group metrics, threshold values and training/audit counts are included in `evaluation.json`. The linked article also reports synthetic obfuscation and comment-padding stress tests.
139
 
140
- ## Limitations
141
 
142
- - The target is the source-labeled risk status of text. Labels are not independently verified malicious behavior or execution outcomes, and different source datasets use different labeling criteria.
143
- - A legitimate installer may look risky; a harmful command may appear harmless when its payload or environment is absent from the input.
144
- - Character n-grams do not provide shell parsing or document-wide semantic reasoning.
145
- - Harmless comment padding can weaken detection. In the reported padding test, this linear model's POSIX recall fell from 91.10% to 50.00% behind 4 KiB of repeated comments and to 41.78% behind 16 KiB. Those results use the original-validation F1 policy and this specific synthetic transformation.
146
- - PowerShell and CMD command-test samples are small. Results use one training seed and source-conditioned splits, not a prospective production evaluation.
147
- - Processing the entire text does not guarantee robust detection of hidden or late risks. Runtime and memory use grow with input size; no deployment latency claim is made here.
148
 
149
- Use the classifier alongside execution permissions, sandboxing, explicit policy checks and review where needed. Evaluate threshold behavior on your own representative validation data before enabling automatic blocking.
150
 
151
  ## Repository files
152
 
153
  | File | Contents |
154
- | --- | --- |
155
- | `LICENSE` | Apache License 2.0. |
156
- | `model.json` | Vocabulary, IDF values, linear weights, intercept, inference configuration and threshold policies. |
157
- | `shell_cat_guard.py` | CPU inference implementation and result schema. |
158
- | `requirements.txt` | Runtime dependencies; the reference implementation was checked with scikit-learn 1.8.0. |
159
- | `evaluation.json` | Stored experiment metrics and data-audit summaries. |
160
- | `validation.json` | Export verification results against saved test scores. |
161
- | `manifest.json` | File hashes and source-model provenance. |
162
-
163
- The JSON export preserves the fitted inference parameters. It omits training-only estimator state and does not require loading a pickle or enabling Transformers remote code.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  pipeline_tag: text-classification
4
  library_name: sklearn
5
  metrics:
6
+ - recall
7
+ - precision
8
+ - f1
9
+ - roc_auc
10
+ - average_precision
11
  datasets:
12
+ - tomngdev/shell-safety
13
+ - nebius/SWE-agent-trajectories
14
  tags:
15
+ - shell-security
16
+ - shell-risk
17
+ - bash
18
+ - powershell
19
+ - cmd
20
+ - coding-agents
21
+ - tf-idf
22
+ - logistic-regression
23
+ - cpu
24
+ - research
25
+ - cat-shell-risk-commander
26
+ - shell-command-classification
27
+ - shell-command-security
28
+ - shell-risk-classification
29
+ - command-risk-detection
30
+ - agent-security
31
+ - ai-agent-security
32
+ - ai-security
33
+ - tool-call-security
34
+ - tool-security
35
+ - posix
36
+ - windows
37
+ - linux
38
+ - powershell-security
39
+ - tfidf
40
+ - shell-token-features
41
+ - binary-classification
42
+ - local-inference
43
+ - on-device
44
+ - cpu-inference
45
+ - scikit-learn
46
+ - json-model
47
+ - a1
48
  inference: false
49
+ thumbnail: https://huggingface.co/patronus-studio/cat-shell-risk-commander/resolve/main/assets/cat-shell-risk-commander-banner.png
50
+ model-index:
51
+ - name: Cat Shell Risk Commander A1
52
+ results:
53
+ - task:
54
+ type: text-classification
55
+ name: Shell command risk classification
56
+ dataset:
57
+ name: Cat Shell A1 stored test (5,948 cases)
58
+ type: cat-shell-a1-stored-test
59
+ split: test
60
+ metrics:
61
+ - type: recall
62
+ name: Recall (external_commands_1pct_fpr)
63
+ value: 0.9259
64
+ - type: f1
65
+ name: F1 (external_commands_1pct_fpr; derived from confusion counts)
66
+ value: 0.942029
67
+ - type: roc_auc
68
+ name: AUROC
69
+ value: 0.9966
70
+ - type: false_positive_rate
71
+ name: False-positive rate (external_commands_1pct_fpr)
72
+ value: 0.0086
73
+ - task:
74
+ type: text-classification
75
+ name: Shell command risk classification
76
+ dataset:
77
+ name: Cat Shell A1 stored test (5,948 cases)
78
+ type: cat-shell-a1-stored-test
79
+ split: test
80
+ metrics:
81
+ - type: recall
82
+ name: Recall (block_0_1pct_fpr)
83
+ value: 0.8129
84
+ - type: f1
85
+ name: F1 (block_0_1pct_fpr; derived from confusion counts)
86
+ value: 0.888889
87
+ - type: roc_auc
88
+ name: AUROC
89
+ value: 0.9966
90
+ - type: false_positive_rate
91
+ name: False-positive rate (block_0_1pct_fpr)
92
+ value: 0.0035
93
  ---
94
 
95
+ ![Cat Shell Risk Commander. Local risk screening for shell commands and scripts.](assets/cat-shell-risk-commander-banner.png)
96
 
97
+ <p align="center"><a href="#quickstart">Quickstart</a> · <a href="#evaluation">Evaluation</a> · <a href="#kestrel-comparison">Kestrel comparison</a> · <a href="#model-and-thresholds">Model &amp; thresholds</a> · <a href="https://patronus.studio/product/ai-models">AI models ↗</a></p>
98
 
99
+ **Screen shell commands before an agent executes them.**
100
 
101
+ Cat Shell Risk Commander scores shell commands and scripts locally on CPU. Use its risk flag to route agent tool calls for additional checks, review or policy enforcement. It reads the submitted text without executing it.
102
 
103
+ | 92.59% recall | 0.86% false positives | 4.09 MB |
104
+ | :---: | :---: | :---: |
105
+ | Across the stored 5,948-case test | At the default threshold policy | JSON model artifact |
106
 
107
+ **A1 research preview.** The figures above describe the current `format_version: 2` export and its stored evaluation. The model combines character n-grams with shell-token features. It is released under Apache 2.0.
108
 
109
+ ## What the model checks
 
110
 
111
+ ```text
112
+ Agent tool call · Shell command · Script text
113
+ ↓
114
+ Cat Shell Risk Commander
115
+ ↓
116
+ Raw score + threshold policy
117
+ ↓
118
+ Your execution or review policy
119
+ ```
120
+
121
+ | Output | Meaning |
122
+ | :--- | :--- |
123
+ | `RISK_FLAG` | The decision score exceeds the selected risk threshold |
124
+ | `NO_RISK_FLAG` | The decision score does not exceed that threshold |
125
+
126
+ A score describes the submitted text. It does not establish what a command will do in a particular environment. Your application decides whether to block, review or continue; a `NO_RISK_FLAG` is not an execution-safety guarantee.
127
+
128
+ ## Model at a glance
129
+
130
+ | Property | Current A1 release |
131
+ | :--- | :--- |
132
+ | Architecture | TF-IDF features with a binary logistic-regression classifier |
133
+ | Character features | 50,000 `char_wb` n-grams, lengths 3 to 5 |
134
+ | Shell-token features | 20,096 features from normalized tokens, token pairs and structural markers |
135
+ | Total features | **70,096**, with one linear coefficient per feature plus an intercept |
136
+ | Artifact | **4,088,897 bytes** (4.09 MB), `model.json`, JSON format version 2 |
137
+ | Input | The complete submitted text; no fixed Transformer token window |
138
+ | Output | Raw decision score, label, flag, threshold and threshold-policy name |
139
+ | Inference | Local CPU, NumPy and scikit-learn; no GPU or neural checkpoint |
140
+ | Default policy | `external_commands_1pct_fpr`, threshold `0.65763` |
141
+ | Runtime loader | `shell_cat_guard_a1.py` and `shell_tokens.py` |
142
+ | License | Apache 2.0 |
143
+
144
+ This artifact is a custom JSON model. Use the included A1 inference module; Transformers `pipeline()` and `AutoModel` do not load it.
145
+
146
+ ## Evaluation
147
+
148
+ ### Compare operating points on the same test
149
+
150
+ The stored A1 evaluation contains **5,948 cases: 1,053 risky and 4,895 safe**. Both rows below use the same model and test set. Threshold policy names identify calibration settings; their names are not promised false-positive rates for every test or production workload.
151
+
152
+ | Threshold policy | Threshold | Recall ↑ | FPR ↓ | Precision ↑ | F1 ↑ |
153
+ | :--- | ---: | ---: | ---: | ---: | ---: |
154
+ | `external_commands_1pct_fpr` | 0.65763 | 92.59% | 0.86% | 95.87% | 94.20% |
155
+ | `block_0_1pct_fpr` | 2.242267 | 81.29% | 0.35% | 98.05% | 88.89% |
156
+
157
+ The default policy detects **975 of 1,053 risky cases**, with **42 false positives among 4,895 safe cases**. Average precision is **0.9858** and AUROC is **0.9966** on this stored test. Precision and F1 above are calculated from the published confusion counts.
158
+
159
+ [Full A1 evaluation](https://huggingface.co/patronus-studio/cat-shell-risk-commander/blob/main/evaluation.json) · [Export validation](https://huggingface.co/patronus-studio/cat-shell-risk-commander/blob/main/validation.json)
160
+
161
+ ### Kestrel comparison
162
+
163
+ **Cross-shell transfer: POSIX, PowerShell and CMD.** The [Patronus ShellRisk experiment](https://patronus.studio/posts/gefaehrliche-shell-befehle-erkennen-shellrisk-benchmark) evaluates 918 held-out commands: 374 risky and 544 safe cases, comprising 735 POSIX, 119 PowerShell and 64 CMD commands.
164
+
165
+ For the calibrated rows, each model's threshold is selected on the **same separate external command-validation set**, allowing at most 16 false positives among 1,666 safe examples. The test cases do not select the thresholds.
166
+
167
+ | Model and operating point | Detected / risky | False positives / safe |
168
+ | :--- | ---: | ---: |
169
+ | **Patronus Linear, same calibration** | **321/374 · 85.8%** | **5/544 · 0.9%** |
170
+ | Patronus Encoder, same calibration | 315/374 · 84.2% | 5/544 · 0.9% |
171
+ | [Kestrel](https://huggingface.co/kontext-security/Kestrel/tree/main), 1% validation FPR | 8/374 · 2.1% | 6/544 · 1.1% |
172
+ | Kestrel, native threshold | 209/374 · 55.9% | 226/544 · 41.5% |
173
+
174
+ At the shared validation-FPR target, the Patronus Linear model detects **85.8%** of risky commands versus **2.1%** for Kestrel, while producing fewer false positives on this external test.
175
+
176
+ **Model versions:** this published experiment covers the previous 50,000-feature Patronus Linear model and a separate Patronus Encoder. It predates the current A1 export; A1's own stored results are reported separately in this card.
177
+
178
+ <details>
179
+ <summary>Comparison methodology and full results</summary>
180
+
181
+ The external experiment uses the official Kestrel weights with the documented Python TF-IDF inference: character-within-word n-grams of lengths 3 to 5, lowercasing, sublinear term frequency, IDF weighting and L2 normalization.
182
+
183
+ Training data differ. Kestrel was released for a different risk-classification task, while the Patronus models receive examples from the expanded sources during fitting. The experiment demonstrates a source-transfer gap and the benefit of adapting to that target distribution, rather than an architecture-only comparison or universal superiority.
184
 
185
+ [Full published comparison (Deutsch)](https://patronus.studio/posts/gefaehrliche-shell-befehle-erkennen-shellrisk-benchmark) · [English report](https://patronus.studio/en/posts/detecting-risky-shell-commands-shellrisk-benchmark)
186
 
187
+ The original same-source Bash test answers a different question. [Additional A1/Bash reference data](comparison/kestrel-a1-comparison.json) and the [reproduction script](comparison/compare_shellrisk.py) remain available separately; those values are not the external-transfer results in the table above.
 
 
 
188
 
189
+ </details>
190
 
191
+ <details>
192
+ <summary>Shell-specific results at the default policy</summary>
193
 
194
+ These are source-specific subsets, not four independent production evaluations. PowerShell and CMD command-test samples are small; zero observed false positives does not establish a zero underlying false-positive rate.
195
 
196
+ | Test subset | Cases | Risky cases | Recall ↑ | FPR ↓ |
197
+ | :--- | ---: | ---: | ---: | ---: |
198
+ | Original Bash | 4194 | 193 | 84.97% | 0.25% |
199
+ | External POSIX | 735 | 292 | 90.75% | 2.26% |
200
+ | External PowerShell | 119 | 58 | 98.28% | 0.00% |
201
+ | External CMD | 64 | 24 | 95.83% | 0.00% |
202
+
203
+ </details>
204
+
205
+ <details>
206
+ <summary>All stored test families and false-positive uncertainty</summary>
207
+
208
+ Default policy: `external_commands_1pct_fpr`. FPR upper bounds are copied from the published `fpr_wilson_upper` field. A metric is n/a when the subset has no examples of the required class. Several families have very small sample sizes.
209
+
210
+ | Family | Cases | Risky cases | Recall ↑ | FPR ↓ | Reported Wilson FPR upper bound |
211
+ | :--- | ---: | ---: | ---: | ---: | ---: |
212
+ | `shellsafety_powershell` | 119 | 58 | 98.28% | 0.00% | 5.92% |
213
+ | `shellsafety_posix` | 735 | 292 | 90.75% | 2.26% | 4.10% |
214
+ | `shellsafety_cmd` | 64 | 24 | 95.83% | 0.00% | 8.76% |
215
+ | `original_bash` | 4194 | 193 | 84.97% | 0.25% | 0.46% |
216
+ | `exfil_pipe` | 160 | 160 | 95.63% | n/a | n/a |
217
+ | `credential_store` | 80 | 80 | 97.50% | n/a | n/a |
218
+ | `reverse_shell` | 7 | 7 | 85.71% | n/a | n/a |
219
+ | `remote_download_exec` | 147 | 147 | 98.64% | n/a | n/a |
220
+ | `privilege_escalation` | 8 | 8 | 100.00% | n/a | n/a |
221
+ | `defense_evasion` | 26 | 26 | 96.15% | n/a | n/a |
222
+ | `disk_destruction` | 28 | 28 | 92.86% | n/a | n/a |
223
+ | `resource_dos` | 4 | 4 | 75.00% | n/a | n/a |
224
+ | `payload_decode_exec` | 6 | 6 | 100.00% | n/a | n/a |
225
+ | `agent_log` | 307 | 0 | n/a | 4.56% | 7.51% |
226
+ | `issue_1` | 10 | 10 | 60.00% | n/a | n/a |
227
+ | `issue_2` | 10 | 10 | 100.00% | n/a | n/a |
228
+ | `issue_3` | 2 | 0 | n/a | 0.00% | 65.76% |
229
+ | `pipe_filter` | 10 | 0 | n/a | 10.00% | 40.42% |
230
+ | `near_miss` | 18 | 0 | n/a | 16.67% | 39.22% |
231
+ | `routine_dev` | 13 | 0 | n/a | 30.77% | 57.63% |
232
+
233
+ The stored JSON also contains per-family results for `block_0_1pct_fpr`, confusion counts, ranking metrics and issue-specific checks. This card reports that source without asserting additional split-independence guarantees.
234
+
235
+ </details>
236
+
237
+ ### JSON export parity
238
+
239
+ The repository's validation report compares the exported model with the fitted in-memory model on **5,948 examples**. It records **zero changed decisions** at either threshold policy and a maximum absolute score error of approximately **8.53 × 10⁻⁶**. This validates export parity on those examples; it is separate from detection quality.
240
+
241
+ ## Quickstart
242
+
243
+ ### Install
244
 
245
  ```bash
246
  python -m pip install "numpy>=1.24" "scikit-learn==1.8.0" "huggingface_hub>=1.3"
247
  ```
248
 
249
+ ### Score command text locally
250
+
251
+ Download the A1 model together with its matching loader and tokenizer:
252
 
253
  ```python
254
  import sys
 
256
 
257
  model_dir = snapshot_download(
258
  repo_id="patronus-studio/cat-shell-risk-commander",
259
+ allow_patterns=[
260
+ "model.json",
261
+ "shell_cat_guard_a1.py",
262
+ "shell_tokens.py",
263
+ ],
264
  )
265
  sys.path.insert(0, model_dir)
266
 
267
+ from shell_cat_guard_a1 import ShellRiskClassifier
268
 
269
  classifier = ShellRiskClassifier(f"{model_dir}/model.json")
270
 
271
+ # Only the text is classified; these commands are not executed.
272
+ result = classifier.score("git status --short")
273
+ print(result)
 
 
 
274
  ```
275
 
276
+ Each result includes `label`, `flagged`, `decision_score`, `threshold` and `threshold_policy`. The decision score is a raw linear logit, not a calibrated probability. A flag is raised when **`decision_score > threshold`**; equality does not raise a flag.
 
 
277
 
278
+ ### Batches and threshold policies
279
 
280
  ```python
281
  scores = classifier.decision_function([
 
283
  "Get-ChildItem",
284
  "dir",
285
  ])
 
286
 
287
  result = classifier.score(
288
  "git status --short",
289
+ threshold_policy="block_0_1pct_fpr",
290
  )
 
291
  ```
292
 
293
+ `decision_function` expects a list of strings and returns raw scores. Use `score` for one text and a threshold-based result. Select a policy using representative validation traffic from your application.
294
 
295
+ ## Model and thresholds
 
 
 
 
296
 
297
+ This release has one A1 model with two stored operating points, rather than Transformer precision or ONNX variants.
298
 
299
+ | Artifact / policy | What to choose it for |
300
+ | :--- | :--- |
301
+ | `model.json` + A1 loader | The current two-block classifier; 70,096 features, about 4.09 MB |
302
+ | `external_commands_1pct_fpr` | Default policy; higher recall in the stored test |
303
+ | `block_0_1pct_fpr` | Stricter score threshold; fewer false positives with lower recall in the stored test |
304
 
305
+ The legacy `shell_cat_guard.py` expects JSON format version 1. The current `model.json` is format version 2 and requires **`shell_cat_guard_a1.py` plus `shell_tokens.py`**.
306
 
307
+ ## Training data and provenance
308
 
309
+ The A1 manifest records **23,177 fitting examples**, including **6,253 positive examples**. The remaining 16,924 are negative examples.
310
 
311
+ | Recorded source | Role in the A1 manifest |
312
+ | :--- | :--- |
313
+ | [Shell Safety](https://huggingface.co/datasets/tomngdev/shell-safety) | Positive and negative commands |
314
+ | Shell Attack Evolution (`zyw-286/shell-attack-evolution-dataset` in the manifest) | Honeypot positives |
315
+ | [Atomic Red Team](https://github.com/redcanaryco/atomic-red-team) | Positive attack commands |
316
+ | [SWE-agent trajectories](https://huggingface.co/datasets/nebius/SWE-agent-trajectories) | Benign agent-command negatives; command strings only |
317
+ | [ShellRisk-Bench](https://github.com/kontext-security/shellrisk-bench) | Original Bash risk target |
318
 
319
+ The Shell Attack Evolution identifier is retained as recorded provenance; no accessible public Hub dataset was found at that identifier during card preparation.
 
 
 
 
320
 
321
+ The fitted model combines 50,000 character-within-word n-gram features with 20,096 shell-token features. The shell-token block includes normalized tokens, token bigrams and markers for command heads, pipe targets and redirection targets. Feature extraction processes text; it does not invoke a shell.
322
 
323
+ The export identifies its source experiment as `shellrisk-retrain-2026-10-23 (arm A1)`. The earlier article below describes the original ShellRisk experiments and the previous linear model. Its older model sizes, training counts and benchmark values should not be read as A1 results.
324
 
325
+ [Earlier experiment report (English)](https://patronus.studio/en/posts/detecting-risky-shell-commands-shellrisk-benchmark) · [Deutsch](https://patronus.studio/posts/gefaehrliche-shell-befehle-erkennen-shellrisk-benchmark)
326
 
327
+ Cat Shell Risk Commander is a Patronus model. It is not an official Kestrel release and does not contain the separate Hadamard encoder discussed in the earlier report.
328
 
329
+ ## Deployment considerations
330
 
331
+ - The classifier predicts the source-defined risk label of submitted text. Dataset labels are not independently verified execution outcomes.
332
+ - It does not execute commands, inspect your filesystem, resolve variables, decode payloads or simulate a shell. Effects that depend on missing payloads or environment context can be missed.
333
+ - Literal feature patterns are not full shell parsing or document-wide semantic reasoning. Obfuscation, unseen patterns and comment padding can affect classification; the previous model's stress-test results do not establish A1 robustness.
334
+ - Scoring the complete text does not imply a fixed latency or memory footprint. Input size affects cost, and no Cat Shell deployment latency or RAM benchmark is published in this card.
335
+ - Source-specific tests, small subgroups and calibration-policy names do not establish performance on new production traffic. Validate thresholds on your workload.
 
336
 
337
+ Use the score alongside execution permissions, sandboxing, explicit policy checks and review where required.
338
 
339
  ## Repository files
340
 
341
  | File | Contents |
342
+ | :--- | :--- |
343
+ | `model.json` | Current A1 vocabulary blocks, IDF values, linear coefficients, intercept and threshold policies |
344
+ | `shell_cat_guard_a1.py` | Matching format-version-2 CPU loader and result schema |
345
+ | `shell_tokens.py` | Shell-token feature extraction used by A1 |
346
+ | `shell_cat_guard.py` | Legacy format-version-1 loader |
347
+ | `requirements.txt` | Reference Python dependencies |
348
+ | `evaluation.json` | Current stored A1 results, confusion counts and per-family metrics |
349
+ | `validation.json` | Export parity report |
350
+ | `manifest.json` | Artifact hashes, source experiment and training-source records |
351
+ | `LICENSE` | Apache License 2.0 |
352
+
353
+ The JSON format contains fitted inference parameters and does not require loading a pickle. The reference Python loader reconstructs the TF-IDF vectorizers and applies the linear decision rule.
354
+
355
+ ## Citation and license
356
+
357
+ Released under **Apache License 2.0**. Training-source licenses and provenance are recorded in [manifest.json](https://huggingface.co/patronus-studio/cat-shell-risk-commander/blob/main/manifest.json).
358
+
359
+ ```bibtex
360
+ @misc{catshellriskcommander2026,
361
+ title = {Cat Shell Risk Commander: Local Shell Risk Classification},
362
+ author = {Patronus Protect},
363
+ year = {2026},
364
+ howpublished = {\url{https://huggingface.co/patronus-studio/cat-shell-risk-commander}}
365
+ }
366
+ ```
367
+
368
+ ## Patronus Protect
369
+
370
+ **Local protection for prompts, tools and documents.**
371
+
372
+ Explore Patronus Protect, or use our open models in your own application.
373
+
374
+ [Try Patronus Protect for free →](https://patronus.studio) · [Explore our AI models →](https://patronus.studio/product/ai-models) · [Wolf Defender Jev →](https://huggingface.co/patronus-studio/wolf-defender-jev)
assets/cat-shell-risk-commander-banner.png ADDED
comparison/compare_shellrisk.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compare Cat Shell A1 with published Kestrel verdicts; command text is never executed."""
2
+ import argparse
3
+ import hashlib
4
+ import json
5
+ from pathlib import Path
6
+ import sys
7
+
8
+ import pyarrow.parquet as pq
9
+
10
+
11
+ def metrics(labels, predictions):
12
+ tp = int(sum(y and p for y, p in zip(labels, predictions)))
13
+ fp = int(sum(not y and p for y, p in zip(labels, predictions)))
14
+ fn = int(sum(y and not p for y, p in zip(labels, predictions)))
15
+ tn = int(sum(not y and not p for y, p in zip(labels, predictions)))
16
+ return dict(n=len(labels), tp=tp, fp=fp, fn=fn, tn=tn,
17
+ precision=tp/(tp+fp), recall=tp/(tp+fn),
18
+ fpr=fp/(fp+tn), f1=2*tp/(2*tp+fp+fn))
19
+
20
+
21
+ def main():
22
+ parser = argparse.ArgumentParser(description=__doc__)
23
+ parser.add_argument('--a1-source', type=Path, required=True)
24
+ parser.add_argument('--gold', type=Path, required=True)
25
+ parser.add_argument('--kestrel-verdicts', type=Path, required=True)
26
+ parser.add_argument('--output', type=Path, required=True)
27
+ args = parser.parse_args()
28
+ sys.path.insert(0, str(args.a1_source.resolve()))
29
+ from shell_cat_guard_a1 import ShellRiskClassifier
30
+
31
+ rows = pq.read_table(args.gold).to_pylist()
32
+ published = {r['id']: r['prediction'] == 'risky' for r in
33
+ map(json.loads, args.kestrel_verdicts.read_text().splitlines())}
34
+ assert len(rows) == len(published) == 4194
35
+ assert set(published) == {r['id'] for r in rows}
36
+ model = ShellRiskClassifier(args.a1_source / 'model.json')
37
+ scores = []
38
+ for start in range(0, len(rows), 128):
39
+ scores.extend(model.decision_function([r['command'] for r in rows[start:start+128]]))
40
+ labels = [r['label'] == 'risky' for r in rows]
41
+ a1 = metrics(labels, [s > model.thresholds[model.default_policy] for s in scores])
42
+ kestrel = metrics(labels, [published[r['id']] for r in rows])
43
+ assert (kestrel['tp'], kestrel['fp']) == (178, 10)
44
+ stored = json.loads((args.a1_source/'evaluation.json').read_text())['original_bash_test'][model.default_policy]
45
+ assert all(a1[k] == stored[k] for k in ['n', 'tp', 'fp', 'fn', 'tn'])
46
+ report = {
47
+ 'date': '2026-10-24',
48
+ 'dataset': 'kontext-security/ShellRisk-Bench',
49
+ 'split': 'test', 'cases': 4194, 'risky': 193, 'safe': 4001,
50
+ 'kestrel': {'source': 'Published per-example Kestrel verdicts, native release behaviour', 'metrics': kestrel},
51
+ 'cat_shell_a1': {'source': 'Replayed current format-version-2 export with shell_cat_guard_a1.py',
52
+ 'policy': model.default_policy,
53
+ 'threshold': model.thresholds[model.default_policy], 'metrics': a1},
54
+ 'test_data_sha256': hashlib.sha256(args.gold.read_bytes()).hexdigest(),
55
+ 'published_kestrel_verdicts_sha256': hashlib.sha256(args.kestrel_verdicts.read_bytes()).hexdigest(),
56
+ 'cat_model_sha256': hashlib.sha256((args.a1_source/'model.json').read_bytes()).hexdigest(),
57
+ 'notes': ['Both rows use the same fixed 4194-case Bash test. Kestrel values come from published verdicts, not a reimplementation.',
58
+ 'Different published/default threshold policies; not a common recalibration or identical-training-data architecture comparison.',
59
+ 'Same-source Bash benchmark; not an external-shell transfer or production evaluation.',
60
+ 'A1 confusion counts exactly match the current stored evaluation.']}
61
+ args.output.write_text(json.dumps(report, indent=2)+'\n')
62
+ print(json.dumps(report, indent=2))
63
+
64
+
65
+ if __name__ == '__main__':
66
+ main()
comparison/kestrel-a1-comparison.json ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "date": "2026-10-24",
3
+ "dataset": "kontext-security/ShellRisk-Bench",
4
+ "split": "test",
5
+ "cases": 4194,
6
+ "risky": 193,
7
+ "safe": 4001,
8
+ "kestrel": {
9
+ "source": "Published per-example Kestrel verdicts, native release behaviour",
10
+ "metrics": {
11
+ "n": 4194,
12
+ "tp": 178,
13
+ "fp": 10,
14
+ "fn": 15,
15
+ "tn": 3991,
16
+ "precision": 0.9468085106382979,
17
+ "recall": 0.9222797927461139,
18
+ "fpr": 0.0024993751562109472,
19
+ "f1": 0.9343832020997376
20
+ },
21
+ "model_revision": "6d6661cac29f8944328f90c8ed3240566cf9f6a1",
22
+ "artifact_sha256": "1df8b3e5f2bfc4e1fe95230ee9b3d37f63aaa461a8551982fcc0dd7103c8221b"
23
+ },
24
+ "cat_shell_a1": {
25
+ "source": "Replayed current format-version-2 export with shell_cat_guard_a1.py",
26
+ "policy": "external_commands_1pct_fpr",
27
+ "threshold": 0.65763,
28
+ "metrics": {
29
+ "n": 4194,
30
+ "tp": 164,
31
+ "fp": 10,
32
+ "fn": 29,
33
+ "tn": 3991,
34
+ "precision": 0.9425287356321839,
35
+ "recall": 0.8497409326424871,
36
+ "fpr": 0.0024993751562109472,
37
+ "f1": 0.8937329700272479
38
+ },
39
+ "model_revision": "2ba751f998dac81b00644fec3b7ca4bf57af08f4"
40
+ },
41
+ "test_data_sha256": "e9df3b50a716ad077eeab6fcc27be57834bc2ff3f28ebeaf48dfd6640e9064c8",
42
+ "published_kestrel_verdicts_sha256": "2d0a3fb080837d1d346da4c422faae4254454af25fb426c3ccba0beb92fb162a",
43
+ "cat_model_sha256": "73f4baaca07a97b5bdef8fd112084a0dfba8fead5ca7f3a0d41660b0ee673556",
44
+ "notes": [
45
+ "Both rows use the same fixed 4194-case Bash test. Kestrel values come from published verdicts, not a reimplementation.",
46
+ "Different published/default threshold policies; not a common recalibration or identical-training-data architecture comparison.",
47
+ "Same-source Bash benchmark; not an external-shell transfer or production evaluation.",
48
+ "A1 confusion counts exactly match the current stored evaluation."
49
+ ],
50
+ "dataset_revision": "437467862139b4e9cdd5322024ef3434a67c7ec8"
51
+ }