mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-08-13 06:50:19 +02:00
feat: multi-provider model support, SARIF output, and exploit-mode fixes (#402)
* feat(worker): record token, cache, and turn usage per agent * feat: replace model tiers with a single SHANNON_AI_MODEL across five providers * feat(cli): rebuild the setup wizard for provider and model selection * docs: document single-model selection and supported providers * feat(worker): use chat completions for OpenAI behind a custom base URL * feat: add SHANNON_AI_OPENAI_FORMAT to pick the wire API for OpenAI gateways * refactor(cli): drop endpoint path hints from the gateway format picker * feat(worker): enable pi in-session provider retry with retry-after backoff * refactor(worker): hand provider error classification to pi and drop the Anthropic ladders * refactor: remove the subscription retry preset and pipeline config section * fix(worker): validate Bedrock credentials with the same live probe as other providers * feat(worker): render the report from structured findings instead of agent-written markdown * fix(worker): dispose the credential probe session on every path * fix(worker): refuse to replace the assembled report with an empty one * refactor(worker): catch post-processing throws across the whole finalization block * revert(worker): drop the report zero-findings guard * docs(worker): correct the retry split and Bedrock credential claims * docs: regenerate llms-full.txt from current sources * feat(cli): build and run the npx flow from a clone * refactor(cli): flatten the setup summary output * feat(cli): reject runs with more than one provider configured * fix(worker): say a rejected bash call never ran * chore(cli): drop grok-4.3 and gpt-5.6-luna from the setup suggestions * feat(worker): capture structured finding locations for SARIF output * fix(worker): enumerate queue confidence so the report inherits it verbatim * feat(worker): give the reporting phase a mode-specific output schema * feat(worker): emit a SARIF 2.1.0 log for exploitative runs * fix(worker): correct SARIF locations and defer fingerprinting to the upload action * fix(worker): drop the confidence suffix from the analysis-mode summary list * feat(worker): give exploit findings a dedicated code location field * feat(worker): carry structured code locations from the vuln queue to the report * fix(worker): join code locations from the vuln queue instead of re-asking agents * fix(worker): spell out the finding_id to category mapping in the tool schema * feat: drop Google/Gemini as a supported AI provider * fix(worker): stop asking the report agent for code locations * docs: correct the provider list and drop the removed rate-limit settings * docs: add provider cyber safeguards and suggested models per provider * docs: document the SARIF output and the report rating thresholds
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* set-report-meta CLI
|
||||
*
|
||||
* Writes top-level report metadata to report.json.
|
||||
* Called once by the report agent before recording individual findings.
|
||||
* Overwrites any existing report_meta — idempotent.
|
||||
*
|
||||
* Usage:
|
||||
* set-report-meta --target "https://example.com" --assessment-date "2026-05-07" \
|
||||
* --scope "injection, xss, auth, authz, ssrf" --executive-summary "..."
|
||||
*
|
||||
* Output (JSON to stdout):
|
||||
* { "status": "success" }
|
||||
* { "status": "error", "message": "...", "retryable": true }
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const REPORT_FILENAME = 'report.json';
|
||||
|
||||
interface ReportMeta {
|
||||
target: string;
|
||||
assessment_date: string;
|
||||
scope: string;
|
||||
executive_summary: string;
|
||||
}
|
||||
|
||||
interface ReportFile {
|
||||
report_meta?: ReportMeta;
|
||||
findings: Array<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
const HELP = `set-report-meta — write top-level report metadata to report.json
|
||||
|
||||
Usage:
|
||||
set-report-meta --target "https://example.com" --assessment-date "2026-05-07" \\
|
||||
--scope "injection, xss, auth" --executive-summary "..."
|
||||
|
||||
Required flags: --target, --assessment-date, --scope, --executive-summary
|
||||
|
||||
Output: JSON to stdout with status "success" or "error".`;
|
||||
|
||||
function getFlag(argv: string[], flag: string): string | undefined {
|
||||
for (let i = 2; i < argv.length; i++) {
|
||||
if (argv[i] === flag && argv[i + 1] && !argv[i + 1]!.startsWith('--')) {
|
||||
return argv[i + 1]!;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function readReportFile(filePath: string): ReportFile {
|
||||
if (!existsSync(filePath)) {
|
||||
return { findings: [] };
|
||||
}
|
||||
const raw = readFileSync(filePath, 'utf-8');
|
||||
return JSON.parse(raw) as ReportFile;
|
||||
}
|
||||
|
||||
function writeReportFile(filePath: string, data: ReportFile): void {
|
||||
const tmpPath = `${filePath}.tmp`;
|
||||
const payload = JSON.stringify(data, null, 2);
|
||||
try {
|
||||
writeFileSync(tmpPath, payload, 'utf-8');
|
||||
renameSync(tmpPath, filePath);
|
||||
} catch (err) {
|
||||
try {
|
||||
unlinkSync(tmpPath);
|
||||
} catch {
|
||||
/* best-effort */
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
if (process.argv[2] === '--help' || process.argv[2] === '-h') {
|
||||
console.log(HELP);
|
||||
return;
|
||||
}
|
||||
|
||||
const target = getFlag(process.argv, '--target');
|
||||
const assessmentDate = getFlag(process.argv, '--assessment-date');
|
||||
const scope = getFlag(process.argv, '--scope');
|
||||
const executiveSummary = getFlag(process.argv, '--executive-summary');
|
||||
|
||||
if (!target) {
|
||||
console.log(JSON.stringify({ status: 'error', message: 'Missing required --target flag', retryable: true }));
|
||||
process.exit(1);
|
||||
}
|
||||
if (!assessmentDate) {
|
||||
console.log(
|
||||
JSON.stringify({ status: 'error', message: 'Missing required --assessment-date flag', retryable: true }),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if (!scope) {
|
||||
console.log(JSON.stringify({ status: 'error', message: 'Missing required --scope flag', retryable: true }));
|
||||
process.exit(1);
|
||||
}
|
||||
if (!executiveSummary) {
|
||||
console.log(
|
||||
JSON.stringify({ status: 'error', message: 'Missing required --executive-summary flag', retryable: true }),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const subdir = process.env.SHANNON_DELIVERABLES_SUBDIR || '.shannon/deliverables';
|
||||
const deliverablesDir = resolve(process.cwd(), ...subdir.split('/'));
|
||||
mkdirSync(deliverablesDir, { recursive: true });
|
||||
const filePath = resolve(deliverablesDir, REPORT_FILENAME);
|
||||
const data = readReportFile(filePath);
|
||||
data.report_meta = {
|
||||
target,
|
||||
assessment_date: assessmentDate,
|
||||
scope,
|
||||
executive_summary: executiveSummary,
|
||||
};
|
||||
writeReportFile(filePath, data);
|
||||
|
||||
console.log(JSON.stringify({ status: 'success' }));
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.log(JSON.stringify({ status: 'error', message, retryable: true }));
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user