feat: backport config-driven run scoping and report filtering

Cherry-pick of upstream Shannon PR #326. Adds vuln_classes subset
selection, exploit toggle, code_path avoid enforcement via SDK deny
rules, deterministic findings rendering when exploit is disabled,
report filtering (min_severity, min_confidence, guidance), and
rules_of_engagement config field.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-05-20 00:45:35 +00:00
committed by Hugh Commit [agent]
parent 70af2b12db
commit 85bcb27860
30 changed files with 1116 additions and 170 deletions
+8
View File
@@ -79,6 +79,14 @@ const AuthzVulnerability = baseVulnerability.extend({
minimal_witness: z.string().optional(),
});
// === Inferred Entry Types (consumed by renderer) ===
export type InjectionFinding = z.infer<typeof InjectionVulnerability>;
export type XssFinding = z.infer<typeof XssVulnerability>;
export type AuthFinding = z.infer<typeof AuthVulnerability>;
export type SsrfFinding = z.infer<typeof SsrfVulnerability>;
export type AuthzFinding = z.infer<typeof AuthzVulnerability>;
// === Queue Wrapper Schemas ===
const InjectionQueueSchema = z.object({ vulnerabilities: z.array(InjectionVulnerability) });
+41
View File
@@ -0,0 +1,41 @@
// Copyright (C) 2025 Keygraph, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License version 3
// as published by the Free Software Foundation.
/**
* Writes ~/.claude/settings.json with permissions.deny rules derived from
* `code_path` avoid patterns. The SDK reads this via `settingSources: ['user']`;
* deny rules fire even in `bypassPermissions` mode.
*/
import os from 'node:os';
import { fs, path } from 'zx';
import type { DistributedConfig } from '../types/config.js';
const FILE_TOOLS = ['Read', 'Edit'] as const;
function denyEntriesFor(pattern: string): string[] {
const arg = `./${pattern.replace(/^[./]+/, '')}`;
return FILE_TOOLS.map((tool) => `${tool}(${arg})`);
}
export async function writeUserSettingsForCodePathAvoids(config: DistributedConfig | null): Promise<void> {
const avoidPatterns = (config?.avoid ?? []).filter((r) => r.type === 'code_path').map((r) => r.value);
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
if (avoidPatterns.length === 0) {
await fs.remove(settingsPath);
return;
}
const settings = {
permissions: {
deny: avoidPatterns.flatMap(denyEntriesFor),
},
};
await fs.ensureDir(path.dirname(settingsPath));
await fs.writeJson(settingsPath, settings, { spaces: 2 });
}