
Anthropic has released the Claude Security plugin for Claude Code in beta. The plugin runs a multi-agent vulnerability scan of a repository from inside an existing Claude Code session, then turns the findings you select into patch files that you review and apply yourself. Anthropic emphasized the tool’s versatility upon announcement, highlighting its capability to either run a comprehensive scan across the full codebase or inspect changes from the terminal right before a commit.
What plugin adds
The plugin adds a single command, /claude-security, which opens a menu of three jobs, per the official documentation:
- Scan codebase — the whole repository or a scoped subset of it
- Scan changes — a branch’s diff, a pull request’s diff, or a single commit
- Suggest patches — turn a report’s findings into .patch files
Installation is two commands from the official Anthropic marketplace:
/plugin install claude-security@claude-plugins-official
/reload-pluginsIf the marketplace is not found, run /plugin marketplace add anthropics/claude-plugins-official first. The plugin source is public in the claude-plugins-official repository, currently at version 0.10.0.
How the scan pipeline is structured
The scan is implemented as a dynamic workflow — a JavaScript orchestration script that fans work out across subagents. The script declares six phases:
- Inventory: partition the repository into components. Every top-level directory must be either scanned or explicitly skipped with a reason.
- Threat model: one modeler per component, producing entry points, sinks, trust boundaries, and files a researcher must read in full.
- Research: one researcher per component × category cell.
- Sweep: gap-fill over what the matrix did not cover.
- Panel: three-lens adversarial verification, one voter per lens.
- Adversarial: max effort only: re-panel marginal keeps, then red-team every survivor.
Research runs against four fixed categories: injection-and-input, auth-and-access, memory-and-unsafe, and crypto-and-secrets. The memory-and-unsafe lens is dropped for components written entirely in memory-safe languages, so a pure Python or TypeScript component gets three lenses instead of four.
The operational scale of a run is dictated by four distinct effort tiers: low, medium, high, and max. Depending on the selected tier, specific thresholds are enforced: the maximum number of components is capped at 12 for low and medium tiers, expanding to 24 for high and max tiers; matrix cells are assigned 1 researcher, which increases to 2 at the high and max levels; and the number of gap-fill sweeps scales from 0 at low, to 1 at medium, up to 2 for high and max. When dealing with a limited scope or a small diff, the process condenses into a single-researcher configuration instead of deploying the entire matrix. This ensures the evaluation remains strictly proportionate to the target while maintaining the identical verification standard.
The system employs model-tiered agents: the orchestrator runs on Opus, while the repository cartographer and read-only code explorer run on Sonnet. Furthermore, the session model is inherited by researchers and verifiers, and scan agents are restricted exclusively to read-only tools.
How a finding earns its place in the report
This is the part worth understanding closely. A candidate finding does not go into the report because a researcher found it. It goes in only after surviving a panel.
Each candidate is handed to three independent verifiers, one per lens: REACHABILITY, IMPACT, and DEFENSES. Each returns a structured verdict of TRUE_POSITIVE or FALSE_POSITIVE with one or two lines naming the decisive file:line. The keep quorum is 2 of 3. If fewer than three voters return, the candidate is not keepable at all.
The panel result also caps the finding’s stated confidence. A unanimous 3/3 panel allows a confidence ceiling of high; a 2/3 quorum caps it at medium. A finding cannot claim more confidence than its verification earned.
Critically, the tally is computed in Python by the report renderer, not asserted by the model that produced the findings. The revision stamp’s verification.status is set to verified only when the vote record proves the panel ran for every finding in the report; otherwise it is unverified with a stated reason. That makes the report’s own account of its rigor something you can check rather than something you take on trust.
function esc(s){ return String(s).replace(/&/g,’&’).replace(//g,’>’); }
function renderStage(i){
var p = PHASES[i];
stageOut.innerHTML =
‘$ ‘ + esc(p.cmd) + ‘\n\n’ +
‘‘ + esc(p.body) + ‘‘;
resize();
}
renderStage(0);
/* ———- 2. panel ———- */
var LENSES = [
{ k:’REACHABILITY’, d:’Can untrusted input actually reach this sink?’ },
{ k:’IMPACT’, d:’If it is reached, what does an attacker gain?’ },
{ k:’DEFENSES’, d:’Does something upstream already stop it?’ }
];
var votes = [true, true, false];
var votersEl = document.getElementById(‘voters’);
var panelOut = document.getElementById(‘panel-out’);
LENSES.forEach(function(l, i){
var row = document.createElement(‘div’);
row.className=”vote”;
row.innerHTML = ‘
[v’ + (i+1) + ‘] ‘ + l.k + ‘‘ + l.d + ‘
‘;
var btn = document.createElement(‘button’);
btn.type=”button”;
btn.className=”toggle”;
btn.addEventListener(‘click’, function(){ votes[i] = !votes[i]; paintVotes(); renderPanel(); });
row.appendChild(btn);
votersEl.appendChild(row);
});
function paintVotes(){
var btns = votersEl.querySelectorAll(‘.toggle’);
for (var i = 0; i < btns.length; i++){
btns[i].textContent = votes[i] ? ‘TRUE_POSITIVE’ : ‘FALSE_POSITIVE’;
btns[i].className=”toggle ” + (votes[i] ? ‘tp’ : ‘fp’);
btns[i].setAttribute(‘aria-label’, LENSES[i].k + ‘: ‘ + btns[i].textContent + ‘. Click to flip.’);
}
}
function pad(s, n){ s = String(s); while (s.length < n) s += ‘.’; return s; }
function renderPanel(){
var t = votes.filter(Boolean).length;
var f = 3 – t;
var kept = t >= 2;
var ceiling = t >= 3 ? ‘HIGH’ : ‘MEDIUM’;
var lines=””;
LENSES.forEach(function(l, i){
lines += ‘ [v’ + (i+1) + ‘] ‘ + pad(l.k + ‘ ‘, 18) + ‘ ‘ +
(votes[i] ? ‘TRUE_POSITIVE‘ : ‘FALSE_POSITIVE‘) + ‘\n’;
});
var verdict = kept
? ‘KEEP written to CLAUDE-SECURITY-RESULTS.md as F1′
: ‘DISCARD never shown; it is not in the report at all’;
var conf = kept
? ‘ ‘ + pad(‘confidence ceiling ‘, 22) + ‘ ‘ + ceiling + ‘‘ +
(t >= 3 ? ‘ (unanimous)‘ : ‘ (quorum, not unanimous)‘) + ‘\n’
: ”;
panelOut.innerHTML =
‘$ panel –finding F1 –voters 3 –quorum 2\n\n’ +
lines + ‘\n’ +
‘ ‘ + pad(‘tally ‘, 22) + ‘ ‘ + t + ‘ true / ‘ + f + ‘ false\n’ +
‘ ‘ + pad(‘verdict ‘, 22) + ‘ ‘ + verdict + ‘\n’ +
conf;
resize();
}
paintVotes(); renderPanel();
/* ———- 3. effort planner ———- */
var tier=”medium”, memsafe = false;
var compEl = document.getElementById(‘comp’);
var compV = document.getElementById(‘comp-v’);
var effortOut = document.getElementById(‘effort-out’);
var memBtn = document.getElementById(‘memsafe’);
[].slice.call(root.querySelectorAll(‘[data-tier]’)).forEach(function(b){
b.addEventListener(‘click’, function(){
tier = b.getAttribute(‘data-tier’);
[].slice.call(root.querySelectorAll(‘[data-tier]’)).forEach(function(x){
x.setAttribute(‘aria-pressed’, x === b ? ‘true’ : ‘false’);
});
renderEffort();
});
});
memBtn.addEventListener(‘click’, function(){
memsafe = !memsafe;
memBtn.setAttribute(‘aria-pressed’, memsafe ? ‘true’ : ‘false’);
renderEffort();
});
compEl.addEventListener(‘input’, function(){ compV.textContent = compEl.value; renderEffort(); });
function renderEffort(){
var big = (tier === ‘high’ || tier === ‘max’);
var cap = big ? 24 : 12;
var found = parseInt(compEl.value, 10);
var used = Math.min(found, cap);
var cats = memsafe ? 3 : 4;
var per = big ? 2 : 1;
var sweeps = tier === ‘low’ ? 0 : (big ? 2 : 1);
var low = (tier === ‘low’);
var researchers = used * cats * per;
var dropped = found > cap
? ‘\n inventory cap: keeping ‘ + cap + ‘ of ‘ + found + ‘ components; the rest are merged or dropped’
: ”;
var researchLine = low
? ‘ ‘ + pad(‘shape ‘, 24) + ‘ single-researcher (no component matrix)‘
: ‘ ‘ + pad(‘category matrix ‘, 24) + ‘ ‘ + cats + ‘ categories × ‘ + per + ‘ researcher’ + (per > 1 ? ‘s’ : ”) + ‘ per cell\n’ +
‘ ‘ + pad(‘researchers dispatched ‘, 24) + ‘ ‘ + researchers + ‘‘;
effortOut.innerHTML =
‘$ /claude-security scan codebase –effort ‘ + tier + ‘\n\n’ +
‘ ‘ + pad(‘component cap ‘, 24) + ‘ ‘ + cap + ‘ (12 at low/medium, 24 at high/max)\n’ +
‘ ‘ + pad(‘components scanned ‘, 24) + ‘ ‘ + used + ‘ of ‘ + found + dropped + ‘\n’ +
researchLine + ‘\n’ +
‘ ‘ + pad(‘gap-fill sweeps ‘, 24) + ‘ ‘ + sweeps + ‘\n’ +
‘ ‘ + pad(‘verification ‘, 24) + ‘ 3-voter panel per surviving candidate\n’ +
‘ ‘ + pad(‘adversarial pass ‘, 24) + ‘ ‘ + (tier === ‘max’
? ‘on (re-panel marginal keeps, red-team survivors)‘
: ‘off — max effort only‘) + ‘\n\n’ +
‘ Every tier clears the same verification bar. A thorough scan covers\n’ +
‘ more ground; it does not lower the standard a finding must meet.‘;
resize();
}
renderEffort();
/* ———- 4. report tree ———- */
document.getElementById(‘report-out’).innerHTML =
‘$ ls -R CLAUDE-SECURITY-20260722-1412/\n\n’ +
‘ CLAUDE-SECURITY-RESULTS.md the readable report\n’ +
‘ CLAUDE-SECURITY-RESULTS.jsonl one JSON object per finding\n’ +
‘ CLAUDE-SECURITY-REVISION-a1b2c3d4e5f6.json\n’ +
‘ commit, effort, severity counts,\n’ +
‘ and how thoroughly it was verified\n’ +
‘ patches/\n’ +
‘ F1.patch F1.md one patch + note per finding\n’ +
‘ .gitignore so a stray git add cannot commit it\n\n’ +
‘ The revision filename carries -dirty when uncommitted changes were\n’ +
‘ part of the scanned tree, so a report is always tied to the code it\n’ +
‘ describes.‘;
/* ———- auto-resize for WordPress iframe ———- */
function resize(){
try {
var h = root.offsetHeight + 40;
if (window.parent && window.parent !== window) {
window.parent.postMessage({ mtpEmbedHeight: h, id: ‘mtp-cs-term’ }, ‘*’);
}
} catch (e) {}
}
window.addEventListener(‘load’, resize);
window.addEventListener(‘resize’, resize);
setTimeout(resize, 120);
setTimeout(resize, 600);
})();






