fix(llm-security): hook coverage — pathguard on Edit, trifecta window, pipe-to-shell, provider keys (#9,#10,#12,#13,#11-doc)
#9 pathguard registered matcher Write only, so an Edit/MultiEdit to an existing protected file (settings.json, .env, .ssh, the hooks themselves) bypassed it entirely; matcher now Edit|Write (the script already reads only tool_input.file_path). #10 the primary trifecta detector's 20-entry window counted marker lines, so accumulated markers scrolled a real leg out (false negative); the window now counts tool-call entries. #13 pre-edit-secrets caught bare provider keys only inside a quoted label assignment; added anchored patterns for Anthropic sk-ant, OpenAI sk-proj, fine-grained github_pat_, Google AIza, and JWT eyJ (minimum-length guarded against prose false positives). #12 the remote-pipe-to-shell block required a shell immediately after the first pipe, so xargs/sudo/tee/env interposition evaded it and the comment falsely claimed xargs was caught; broadened to reach a shell through intermediate segments while leaving shell-OR fallbacks unblocked. #11 (doc only): knowledge/owasp-skills-top10.md claimed pre-bash-destructive blocks persistence commands — it does not; corrected to mark persistence detection as unimplemented/future (the detector itself is deferred to v8). Suite 2004/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TcQyMTQfyrsAapaCMPxTtQ
This commit is contained in:
parent
21c6c2b534
commit
8a59d616fb
9 changed files with 337 additions and 6 deletions
|
|
@ -270,6 +270,35 @@ function readLastEntries(stateFile, n) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a window covering the last n TOOL-CALL entries plus any marker entries
|
||||
* interleaved within that span.
|
||||
*
|
||||
* v7.8.3 #10 fix: the primary trifecta detector previously used
|
||||
* readLastEntries(stateFile, WINDOW_SIZE), which counts raw lines with no type
|
||||
* filter. Marker entries (warning/volume_warning/escalation_warning/...)
|
||||
* appended to the same file diluted the 20-line window and scrolled real
|
||||
* trifecta legs out — a false negative. This helper counts actual tool-call
|
||||
* entries (no `type` field) and returns the slice from the n-th-last tool call
|
||||
* onward, keeping interleaved markers so dedup checks (hasRecentWarning) still
|
||||
* see them.
|
||||
* @param {string} stateFile
|
||||
* @param {number} n - number of tool-call entries the window must cover
|
||||
* @returns {object[]}
|
||||
*/
|
||||
function readToolCallWindow(stateFile, n) {
|
||||
const all = readLastEntries(stateFile, 10_000);
|
||||
let toolCount = 0;
|
||||
let start = 0;
|
||||
for (let i = all.length - 1; i >= 0; i--) {
|
||||
if (!all[i].type) {
|
||||
toolCount++;
|
||||
if (toolCount === n) { start = i; break; }
|
||||
}
|
||||
}
|
||||
return all.slice(start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up state files older than CLEANUP_MAX_AGE_MS.
|
||||
* Only called on first invocation per session (when state file doesn't exist yet).
|
||||
|
|
@ -860,7 +889,9 @@ const messages = [];
|
|||
|
||||
// --- Trifecta detection (skip for neutral-only and delegation-only calls) ---
|
||||
if (!(classes.length === 1 && (classes[0] === 'neutral' || classes[0] === 'delegation'))) {
|
||||
const window = readLastEntries(stateFile, WINDOW_SIZE);
|
||||
// v7.8.3 #10: count tool-call entries, not raw lines — marker entries must
|
||||
// not dilute the trifecta window (see readToolCallWindow).
|
||||
const window = readToolCallWindow(stateFile, WINDOW_SIZE);
|
||||
const { detected, evidence } = checkTrifecta(window);
|
||||
|
||||
if (detected && !hasRecentWarning(window)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue