mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-09-24 18:50:49 +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:
@@ -17,7 +17,7 @@
|
||||
*
|
||||
* Features:
|
||||
* - Queryable state via getProgress
|
||||
* - Automatic retry with backoff for transient/billing errors
|
||||
* - Automatic retry with backoff for transient errors
|
||||
* - Non-retryable classification for permanent errors
|
||||
* - Audit correlation via workflowId
|
||||
* - Graceful failure handling: pipelines continue if one fails
|
||||
@@ -64,21 +64,21 @@ function computeExpectedAgents(vulnClasses: readonly VulnClass[], exploit: boole
|
||||
return expected;
|
||||
}
|
||||
|
||||
// Retry configuration for production (long intervals for billing recovery)
|
||||
// Retry configuration for production (long intervals so a rate-limit window can clear)
|
||||
const PRODUCTION_RETRY = {
|
||||
initialInterval: '5 minutes',
|
||||
maximumInterval: '30 minutes',
|
||||
backoffCoefficient: 2,
|
||||
maximumAttempts: 50,
|
||||
// Belt-and-braces: activities already throw non-retryable ApplicationFailures for
|
||||
// these. Only types that are always permanent belong here — GitError and
|
||||
// AgentExecutionError carry a per-error verdict and must not be listed.
|
||||
nonRetryableErrorTypes: [
|
||||
'AuthenticationError',
|
||||
'PermissionError',
|
||||
'InvalidRequestError',
|
||||
'RequestTooLargeError',
|
||||
'ConfigurationError',
|
||||
'InvalidTargetError',
|
||||
'ExecutionLimitError',
|
||||
'AuthLoginFailedError',
|
||||
'PermanentError',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -105,22 +105,6 @@ const testActs = proxyActivities<typeof activities>({
|
||||
retry: TESTING_RETRY,
|
||||
});
|
||||
|
||||
// Retry configuration for subscription plans (5h+ rolling rate limit windows)
|
||||
const SUBSCRIPTION_RETRY = {
|
||||
initialInterval: '5 minutes',
|
||||
maximumInterval: '6 hours',
|
||||
backoffCoefficient: 2,
|
||||
maximumAttempts: 100,
|
||||
nonRetryableErrorTypes: PRODUCTION_RETRY.nonRetryableErrorTypes,
|
||||
};
|
||||
|
||||
// Activity proxy for subscription plan recovery (extended timeouts)
|
||||
const subscriptionActs = proxyActivities<typeof activities>({
|
||||
startToCloseTimeout: '8 hours',
|
||||
heartbeatTimeout: '2 hours',
|
||||
retry: SUBSCRIPTION_RETRY,
|
||||
});
|
||||
|
||||
// Retry configuration for preflight validation (short timeout, few retries)
|
||||
const PREFLIGHT_RETRY = {
|
||||
initialInterval: '10 seconds',
|
||||
@@ -167,6 +151,9 @@ function computeSummary(state: PipelineState): PipelineSummary {
|
||||
};
|
||||
}
|
||||
|
||||
/** One pipeline per vulnerability class, all five in flight together. */
|
||||
const MAX_CONCURRENT_PIPELINES = 5;
|
||||
|
||||
const MAX_PIPELINE_ERROR_MESSAGE_LENGTH = 2000;
|
||||
|
||||
function truncatePipelineErrorMessage(message: string): string {
|
||||
@@ -200,14 +187,7 @@ export async function pentestPipeline(input: PipelineInput): Promise<PipelineSta
|
||||
|
||||
const { workflowId } = workflowInfo();
|
||||
|
||||
// Select activity proxy based on mode: testing (fast), subscription (extended), or default
|
||||
function selectActivityProxy(pipelineInput: PipelineInput) {
|
||||
if (pipelineInput.pipelineTestingMode) return testActs;
|
||||
if (pipelineInput.pipelineConfig?.retry_preset === 'subscription') return subscriptionActs;
|
||||
return acts;
|
||||
}
|
||||
|
||||
const a = selectActivityProxy(input);
|
||||
const a = input.pipelineTestingMode ? testActs : acts;
|
||||
|
||||
const state: PipelineState = {
|
||||
status: 'running',
|
||||
@@ -611,8 +591,6 @@ export async function pentestPipeline(input: PipelineInput): Promise<PipelineSta
|
||||
}
|
||||
}
|
||||
|
||||
const maxConcurrent = input.pipelineConfig?.max_concurrent_pipelines ?? 5;
|
||||
|
||||
const pipelineConfigs = buildPipelineConfigs();
|
||||
const pipelineThunks: Array<() => Promise<VulnExploitPipelineResult>> = [];
|
||||
let alreadyCompletedPipelineCount = 0;
|
||||
@@ -632,9 +610,15 @@ export async function pentestPipeline(input: PipelineInput): Promise<PipelineSta
|
||||
}
|
||||
}
|
||||
|
||||
const pipelineResults = await runWithConcurrencyLimit(pipelineThunks, maxConcurrent);
|
||||
const pipelineResults = await runWithConcurrencyLimit(pipelineThunks, MAX_CONCURRENT_PIPELINES);
|
||||
aggregatePipelineResults(pipelineResults, alreadyCompletedPipelineCount);
|
||||
|
||||
// Surface the not-assessed classes to the report stage so a failed class renders as
|
||||
// "analysis did not complete" rather than the absence assertion "no findings".
|
||||
if (state.failedPipelines.length > 0) {
|
||||
activityInput.failedClasses = state.failedPipelines.map((f) => f.vulnType);
|
||||
}
|
||||
|
||||
state.currentPhase = 'exploitation';
|
||||
state.currentAgent = null;
|
||||
await a.logPhaseTransition(activityInput, 'vulnerability-exploitation', 'complete');
|
||||
@@ -649,7 +633,7 @@ export async function pentestPipeline(input: PipelineInput): Promise<PipelineSta
|
||||
await a.assembleReportActivity(activityInput, exploit);
|
||||
|
||||
// Then run the report agent to add executive summary and clean up
|
||||
state.agentMetrics.report = await a.runReportAgent(activityInput);
|
||||
state.agentMetrics.report = await a.runReportAgent(activityInput, exploit);
|
||||
state.completedAgents.push('report');
|
||||
if (input.checkpointsEnabled) {
|
||||
await a.saveCheckpoint(activityInput, 'report', 'reporting', state);
|
||||
|
||||
Reference in New Issue
Block a user