feat(post-mcp-verify): E14 part 2 — cumulative-drift MEDIUM advisory [skip-docs]

Wave C step C2: surface the cumulative-drift signal from
checkDescriptionDrift() (added in C1) as a separate MEDIUM advisory
with finding category mcp-cumulative-drift. Independent of the existing
per-update drift advisory — a slow-burn rug-pull that keeps each update
below the 10% per-update threshold but cumulatively drifts >=25% from
the sticky baseline now triggers the new advisory without ever crossing
the per-update bar.

The advisory references /security mcp-baseline-reset (added in C3) so
the user knows how to acknowledge a legitimate MCP server upgrade.

CLAUDE.md updates:
- post-mcp-verify hooks-table row mentions per-update + cumulative drift
- mcp-description-cache lib bullet documents baseline schema, history,
  cumulative threshold policy key, and LLM_SECURITY_MCP_CACHE_FILE
  override.

Tests: 2 new hook tests using LLM_SECURITY_MCP_CACHE_FILE for cache
isolation. Existing 68 still pass; total 70.

Plugin README and root marketplace README updates land in C3 alongside
the new /security mcp-baseline-reset slash command (combined Wave-C
doc update per plan §"Wave C — Touch" list).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-04-30 16:40:52 +02:00
commit 427b68eca9
3 changed files with 109 additions and 4 deletions

View file

@ -421,6 +421,11 @@ if (isHtmlSource && outputText.length >= MIN_INJECTION_SCAN_LENGTH) {
// =========================================================================
// MCP description drift detection (OWASP MCP05 — Rug Pull)
// Checks if the MCP tool's description has changed since first seen.
// Two signals:
// - per-update drift (>10% Levenshtein vs previous)
// - cumulative drift (>=25% Levenshtein vs sticky baseline) — catches
// slow-burn rug-pulls where each update stays under the per-update
// threshold but cumulatively diverges from the original. v7.3.0 / E14.
// Only relevant for MCP tools that provide a description in tool_input.
// =========================================================================
const isMcpTool = toolName?.startsWith('mcp__');
@ -438,6 +443,19 @@ if (isMcpTool && !isTrustedMcp) {
` A changed tool description may indicate the MCP server has been compromised.`
);
}
// Cumulative-drift advisory (mcp-cumulative-drift, MEDIUM). Independent
// of per-update drift — a slow-burn rug-pull triggers this without
// ever crossing the per-update threshold.
if (driftResult.cumulative && driftResult.cumulative.drifted) {
const baselineDesc = driftResult.cumulative.baseline || '';
advisories.push(
`MCP tool cumulative description drift — MEDIUM (mcp-cumulative-drift, OWASP MCP05).\n` +
` ${driftResult.cumulative.detail}\n` +
` Baseline: "${baselineDesc.slice(0, 120)}${baselineDesc.length > 120 ? '...' : ''}"\n` +
` Current: "${description.slice(0, 120)}${description.length > 120 ? '...' : ''}"\n` +
` Reset the baseline after a legitimate MCP server upgrade with: /security mcp-baseline-reset`
);
}
} catch { /* drift check is advisory, never block */ }
}
}