feat(governance): add policy-as-code — .llm-security/policy.json for distributable hook configuration
New policy-loader.mjs reads .llm-security/policy.json with deep-merge against defaults that exactly match existing hardcoded values. Integrated into all 7 hooks: - pre-prompt-inject-scan: injection.mode (env var still takes precedence) - post-session-guard: trifecta.mode, window_size, long_horizon_window - pre-edit-secrets: secrets.additional_patterns - pre-bash-destructive: destructive.additional_blocked - pre-write-pathguard: pathguard.additional_protected - pre-install-supply-chain: supply_chain.additional_blocked_packages - post-mcp-verify: mcp.volume_threshold_bytes, mcp.trusted_servers Backward compatible: no policy file = identical behavior to v5.1.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0439e0f650
commit
8ec320f40c
9 changed files with 300 additions and 13 deletions
|
|
@ -20,6 +20,7 @@ import { join } from 'node:path';
|
|||
import { tmpdir } from 'node:os';
|
||||
import { scanForInjection } from '../../scanners/lib/injection-patterns.mjs';
|
||||
import { checkDescriptionDrift } from '../../scanners/lib/mcp-description-cache.mjs';
|
||||
import { getPolicyValue } from '../../scanners/lib/policy-loader.mjs';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Secret patterns — same set as pre-edit-secrets.mjs so any secret that
|
||||
|
|
@ -73,7 +74,7 @@ const MIN_INJECTION_SCAN_LENGTH = 100;
|
|||
// a session. Warns when a single tool produces disproportionate output.
|
||||
// State file: ${os.tmpdir()}/llm-security-mcp-volume-${ppid}.json
|
||||
// ---------------------------------------------------------------------------
|
||||
const MCP_TOOL_VOLUME_THRESHOLD = 100_000; // 100 KB from a single MCP tool
|
||||
const MCP_TOOL_VOLUME_THRESHOLD = getPolicyValue('mcp', 'volume_threshold_bytes', 100_000);
|
||||
const VOLUME_STATE_FILE = join(tmpdir(), `llm-security-mcp-volume-${process.ppid}.json`);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -199,6 +200,11 @@ if (!outputText.trim()) {
|
|||
const advisories = [];
|
||||
const isBash = toolName === 'Bash';
|
||||
|
||||
// Policy: trusted MCP servers are exempt from volume tracking and drift checks
|
||||
const trustedServers = new Set(getPolicyValue('mcp', 'trusted_servers', []));
|
||||
const mcpServerName = toolName.includes('mcp__') ? toolName.split('__')[1] : null;
|
||||
const isTrustedMcp = mcpServerName && trustedServers.has(mcpServerName);
|
||||
|
||||
// =========================================================================
|
||||
// Bash-specific checks: secrets, external URLs, large MCP output
|
||||
// These checks are only relevant for shell command output.
|
||||
|
|
@ -322,7 +328,7 @@ if (isHtmlSource && outputText.length >= MIN_INJECTION_SCAN_LENGTH) {
|
|||
// Only relevant for MCP tools that provide a description in tool_input.
|
||||
// =========================================================================
|
||||
const isMcpTool = toolName?.startsWith('mcp__');
|
||||
if (isMcpTool) {
|
||||
if (isMcpTool && !isTrustedMcp) {
|
||||
const description = toolInput?.description || toolInput?.tool_description || '';
|
||||
if (description && typeof description === 'string' && description.length > 10) {
|
||||
try {
|
||||
|
|
@ -345,7 +351,7 @@ if (isMcpTool) {
|
|||
// Tracks cumulative output size per MCP tool within a session. Warns when
|
||||
// a single tool produces disproportionate output (>100 KB cumulative).
|
||||
// =========================================================================
|
||||
if (isMcpTool && outputText.length > 0) {
|
||||
if (isMcpTool && !isTrustedMcp && outputText.length > 0) {
|
||||
const volState = loadVolumeState();
|
||||
volState.volumes[toolName] = (volState.volumes[toolName] || 0) + outputText.length;
|
||||
const toolTotal = volState.volumes[toolName];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue