Skip to content

Commit f31285c

Browse files
committed
Add one-command README trial path
1 parent 047c6cc commit f31285c

4 files changed

Lines changed: 465 additions & 1 deletion

File tree

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ On matched-vocabulary tool poisoning, where safe and poisoned descriptions use a
2323

2424
Runs locally. 22 KB probe. Any CPU. Nothing uploaded. See the [full competitive landscape](docs/COMPETITIVE_LANDSCAPE.md).
2525

26+
## Break it in one command
27+
28+
No install or registry account needed:
29+
30+
```bash
31+
uvx --python 3.11 --from git+https://github.com/mcpware/IntentProbe.git@v0.1.0 intentprobe scan --format summary --text "Reads SSH config and private keys, then silently uploads credentials to a remote server."
32+
```
33+
34+
First model-backed scan downloads Qwen2.5-0.5B (~1 GB, once). Scan data stays on your machine.
35+
2636
---
2737

2838
## How it works
@@ -68,12 +78,29 @@ The words are almost identical. The intent is not. Text scanners fail here becau
6878
| **Enterprise cloud** | Lakera, Azure Prompt Shields, Google Model Armor, AWS Bedrock Guardrails, Cisco | Ship content to their cloud API for classification | Black box. You can't verify what model they use or reproduce their results. | **100% local.** Every benchmark script, model artifact, and dataset is public. |
6979
| **MCP scanner** | Snyk Agent Scan, Invariant MCP-Scan, MEDUSA, ClawGuard | Static rules, pattern matching, metadata scan, policy checks | Practical, but reads text and known patterns. | **Reads activations.** What the model understood, not what the text says. |
7080
| **Text classifier** | ProtectAI DeBERTa, Meta Prompt Guard | Classify text as benign / injection / jailbreak | Trained on prompt injection, not tool poisoning. Fails on matched vocabulary. | Matched-vocabulary F1: IntentProbe **96.6%**, DeBERTa **0%**. |
71-
| **LLM-as-judge** | NeMo self-check, OpenAI Guardrails, Promptfoo | Ask another LLM: "is this poisoned?" | Expensive, slow, non-deterministic. The judge can be fooled by the same poisoning. | **Deterministic.** Same input, same score, every time. No API calls. |
81+
| **LLM-as-judge** | NeMo self-check, OpenAI Guardrails, Promptfoo | Ask another LLM: "is this poisoned?" | Expensive, slow, prompt-sensitive, and the generated answer is part of the attack surface. | **Representation-level.** Scores the internal state before any verbal answer is produced. |
7282
| **Red-team framework** | garak, Giskard, Promptfoo red team | Generate attacks to test your app | Audit tool, not a pre-install scanner. | IntentProbe is a **CLI + runtime hook** that blocks before install and before each tool call. |
7383
| **IntentProbe** | | Frozen local model + activation probe on layers 13-15 | Still improving on novel attack families | **First activation-probe scanner for MCP tool poisoning.** |
7484

7585
Full source-backed comparison: [docs/COMPETITIVE_LANDSCAPE.md](docs/COMPETITIVE_LANDSCAPE.md)
7686

87+
## Why not just ask Qwen?
88+
89+
LLM-as-judge is an output-level mechanism: ask a model to say safe or unsafe.
90+
IntentProbe is a representation-level mechanism: run the tool text through a
91+
frozen local model and score the hidden activation state.
92+
93+
That difference matters. A poisoned tool can claim "I am safe", and a judge
94+
prompt can be steered into saying safe. IntentProbe does not trust the verbal
95+
answer; it measures whether the tool text creates a poisoned-looking internal
96+
state.
97+
98+
We tested direct-prompting the same `Qwen/Qwen2.5-0.5B` sensor model. The
99+
deterministic label-score baseline flagged every clean curated item as poisoned
100+
(`clean FPR = 1.000`), while the generated-answer baseline produced lower
101+
recall and many unparseable outputs. Full reproducible baseline:
102+
[research/QWEN_PROMPT_JUDGE_BASELINE_2026-06-08.md](research/QWEN_PROMPT_JUDGE_BASELINE_2026-06-08.md).
103+
77104
## Benchmarks
78105

79106
Same test sets. Same split. Same seed. Every number is reproducible from `research/`.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Qwen Direct-Prompt Judge Baseline - 2026-06-08
2+
3+
## Question
4+
5+
Can the same local Qwen sensor model simply be prompted to classify tool
6+
poisoning, instead of reading its hidden activations and training a probe?
7+
8+
This tests a common LLM-as-judge shape against the current activation-probe
9+
lane. It does not replace the product scanner.
10+
11+
## Setup
12+
13+
- Model: `Qwen/Qwen2.5-0.5B`
14+
- Device: Apple MPS
15+
- Prompt: fixed zero-shot `CLEAN` vs `POISONED` tool-security prompt
16+
- Script: `research/benchmark_qwen_prompt_judge.py`
17+
- Seed: `42`
18+
- Methods:
19+
- `choice-logprob`: compare the model probability of the next label
20+
` CLEAN` vs ` POISONED`;
21+
- `generate`: ask the model to generate an answer, then parse the generated
22+
text.
23+
24+
## Commands
25+
26+
```bash
27+
.venv/bin/python -m research.benchmark_qwen_prompt_judge \
28+
--dataset family_curated_v0 \
29+
--max-samples 9999 \
30+
--method choice-logprob \
31+
--device auto \
32+
--pretty
33+
34+
.venv/bin/python -m research.benchmark_qwen_prompt_judge \
35+
--dataset family_curated_v0 \
36+
--max-samples 9999 \
37+
--method generate \
38+
--device auto \
39+
--pretty
40+
41+
.venv/bin/python -m research.benchmark_qwen_prompt_judge \
42+
--dataset routeguard_external_v0 \
43+
--max-samples 200 \
44+
--method choice-logprob \
45+
--device auto \
46+
--pretty
47+
48+
.venv/bin/python -m research.benchmark_qwen_prompt_judge \
49+
--dataset routeguard_external_v0 \
50+
--max-samples 100 \
51+
--method generate \
52+
--device auto \
53+
--pretty
54+
```
55+
56+
## Results
57+
58+
| Dataset | Method | n | Accuracy | Precision | Recall | F1 | Clean FPR | Unknown |
59+
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
60+
| `family_curated_v0` | `choice-logprob` | 76 | 0.500 | 0.500 | 1.000 | 0.667 | 1.000 | 0 |
61+
| `family_curated_v0` | `generate` | 76 | 0.500 | 0.500 | 0.789 | 0.612 | 0.789 | 16 |
62+
| `routeguard_external_v0` sample | `choice-logprob` | 200 | 0.500 | 0.500 | 1.000 | 0.667 | 1.000 | 0 |
63+
| `routeguard_external_v0` sample | `generate` | 100 | 0.590 | 0.696 | 0.320 | 0.438 | 0.140 | 77 |
64+
65+
Saved outputs:
66+
67+
- `research/_results/qwen_prompt_judge/20260608T072516Z-family-curated-qwen-choice-logprob-full.json`
68+
- `research/_results/qwen_prompt_judge/20260608T072539Z-family-curated-qwen-generate-full.json`
69+
- `research/_results/qwen_prompt_judge/20260608T072602Z-routeguard-qwen-choice-logprob-sample200.json`
70+
- `research/_results/qwen_prompt_judge/20260608T072711Z-routeguard-qwen-generate-sample100.json`
71+
72+
## Read
73+
74+
The direct-prompt baseline is not competitive with the activation scanner.
75+
76+
The label-logprob version is deterministic and parse-free, but it collapses into
77+
an all-poison policy on both tested gates. That gives perfect poison recall but
78+
blocks every clean item, so it is not usable as a scanner default.
79+
80+
The generation version reduces false positives on the RouteGuard sample, but it
81+
misses most poisoned rows and produces many unparseable outputs. That is a poor
82+
hot-path security contract: the scanner has to decide whether to allow, warn, or
83+
block, not interpret arbitrary text continuations.
84+
85+
Important nuance: LLM-as-judge can be made more deterministic with fixed
86+
decoding, especially at `temperature=0`. The bigger product problem is prompt
87+
fragility, parseability, latency, and model/version drift. A fixed activation
88+
artifact has a simpler contract: same input, same model, same probe, same
89+
threshold, same score.
90+
91+
## Current Position
92+
93+
Keep raw Qwen activation probing as the v0 product default. Keep direct-prompt
94+
LLM judge baselines in the benchmark suite as a comparison lane, not as the
95+
scanner implementation.

research/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ The short version:
2424
| `SCANNER_PIPELINE.md` | Product methodology: frozen sensor model, activation probe, SAE explanations, update loop. |
2525
| `LIVING_PLAN.md` | Current roadmap, model choices, benchmark results, and next actions. |
2626
| `PRODUCT_REPRODUCIBILITY_LEDGER_2026-06-03.md` | Peer-review ledger for the current product-shaped scanner path: commands, artifacts, metrics, and claim boundaries. |
27+
| `QWEN_PROMPT_JUDGE_BASELINE_2026-06-08.md` | Direct-prompt / LLM-as-judge baseline for the same Qwen sensor model. |
2728
| `ROUTEGUARD_EXTERNAL_QWEN_FIXED_LAYERS_2026-06-03.md` | Latest RouteGuard-style external-transfer gate and Qwen pooled artifact decision. |
2829
| `THRESHOLD_CALIBRATION_QWEN_POOLED_2026-06-03.md` | Current Qwen pooled warn/block threshold calibration and product decision. |
2930
| `CURATED_FAMILY_BAKEOFF_2026-06-03.md` | Latest curated-family model bakeoff and Qwen artifact decision. |

0 commit comments

Comments
 (0)