mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-10-06 00:06:53 +02:00
feat(cli): name cyber-access and app-login steps in the start loader
This commit is contained in:
1 parent
ed304bb812
commit
3283b4ac1d
2 files changed
+66
-10
No files matched your search
@@ -31,7 +31,12 @@ import { clearPendingWorkflowIdentity, writePendingWorkflowIdentity } from '../p
|
||||
import { indentFailureSegments, parseFailureSegments } from '../scan/failure.js';
|
||||
import { resolveWorkflowId } from '../session.js';
|
||||
import { displayPlainBanner, displaySplash } from '../splash.js';
|
||||
import { describeWorkflowLifecycle, getTerminalOutcome, queryProgress } from '../temporal-client.js';
|
||||
import {
|
||||
describeWorkflowLifecycle,
|
||||
getTerminalOutcome,
|
||||
queryProgress,
|
||||
runningActivityTypes,
|
||||
} from '../temporal-client.js';
|
||||
import { stdoutIsTerminal } from '../tty.js';
|
||||
import { tailUntilComplete } from './logs.js';
|
||||
|
||||
@@ -490,10 +495,14 @@ export async function start(args: StartArgs): Promise<void> {
|
||||
warn(`Scan ${workspace} started, but its launch record could not be removed.`);
|
||||
}
|
||||
|
||||
// Hold until preflight clears, so an unreachable target or bad credential is reported here
|
||||
// Hold until startup clears, so an unreachable target or bad credential is reported here
|
||||
// rather than after "Scan started".
|
||||
spinner.message('Running preflight checks');
|
||||
const outcome = await awaitPreflightOutcome(workflowId);
|
||||
spinner.message(PREFLIGHT_LABEL);
|
||||
const spec = resolveModelSpec();
|
||||
const providerId = typeof spec === 'string' ? '' : spec.providerId;
|
||||
// The cyber-access probe only runs for OpenAI/Anthropic, so only name it there.
|
||||
const showCyberAccess = providerId === 'anthropic' || providerId === 'openai';
|
||||
const outcome = await awaitStartupOutcome(workflowId, (label) => spinner.message(label), showCyberAccess);
|
||||
if (outcome.kind === 'failed') {
|
||||
spinner.error(couldNotStartLabel);
|
||||
printScanStartFailure(outcome.message);
|
||||
@@ -569,15 +578,26 @@ function readStartupError(startupErrorPath: string): StartupError | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
/** Outcome of waiting for the in-workflow preflight to clear. */
|
||||
/** Outcome of waiting for in-workflow startup (preflight + auth validation) to clear. */
|
||||
type PreflightOutcome = { kind: 'passed' } | { kind: 'failed'; message: string } | { kind: 'unconfirmed' };
|
||||
|
||||
const PREFLIGHT_LABEL = 'Running preflight checks';
|
||||
const CYBER_ACCESS_LABEL = 'Checking cyber access';
|
||||
const APP_LOGIN_LABEL = 'Verifying app login with provided credentials';
|
||||
|
||||
/**
|
||||
* Wait for the registered workflow's preflight to pass or fail: passed once `currentPhase` moves
|
||||
* beyond 'preflight' (or the scan already closed ok), failed when the workflow terminates with an
|
||||
* error. Bounded, so a Temporal query outage falls through as 'unconfirmed' rather than hanging.
|
||||
* Drive the startup spinner until the pentest begins, naming the cyber-access probe and the app
|
||||
* login while their activity runs. Labels only advance, so a gap between them holds the last step
|
||||
* rather than reverting to the generic line. Passed once the phase moves past preflight/auth (or
|
||||
* the scan closed ok), failed on a terminal error, unconfirmed if a query outage outlasts the bound.
|
||||
*/
|
||||
async function awaitPreflightOutcome(workflowId: string): Promise<PreflightOutcome> {
|
||||
async function awaitStartupOutcome(
|
||||
workflowId: string,
|
||||
onLabel: (label: string) => void,
|
||||
showCyberAccess: boolean,
|
||||
): Promise<PreflightOutcome> {
|
||||
let rank = 0;
|
||||
let label = PREFLIGHT_LABEL;
|
||||
for (let attempts = 0; attempts < 80; attempts++) {
|
||||
try {
|
||||
const lifecycle = await describeWorkflowLifecycle(workflowId);
|
||||
@@ -586,8 +606,25 @@ async function awaitPreflightOutcome(workflowId: string): Promise<PreflightOutco
|
||||
return outcome.kind === 'failed' ? { kind: 'failed', message: outcome.message } : { kind: 'passed' };
|
||||
}
|
||||
|
||||
const running = await runningActivityTypes(workflowId);
|
||||
if (showCyberAccess && rank < 1 && running.includes('runExploitReadinessProbe')) {
|
||||
rank = 1;
|
||||
label = CYBER_ACCESS_LABEL;
|
||||
}
|
||||
if (rank < 2 && running.includes('runAuthenticationValidation')) {
|
||||
rank = 2;
|
||||
label = APP_LOGIN_LABEL;
|
||||
}
|
||||
onLabel(label);
|
||||
|
||||
// Any phase past preflight/auth-validation means the pentest has begun.
|
||||
const progress = await queryProgress(workflowId);
|
||||
if (progress && progress.currentPhase !== null && progress.currentPhase !== 'preflight') {
|
||||
if (
|
||||
progress &&
|
||||
progress.currentPhase !== null &&
|
||||
progress.currentPhase !== 'preflight' &&
|
||||
progress.currentPhase !== 'auth-validation'
|
||||
) {
|
||||
return { kind: 'passed' };
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -257,6 +257,25 @@ export async function describeScan(workflowId: string): Promise<ScanDescription
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity-type names pending on a running scan; empty on any failure. Tolerant (it feeds the
|
||||
* start spinner) unlike describeScan, which fails closed so the status tree is never incomplete.
|
||||
*/
|
||||
export async function runningActivityTypes(workflowId: string): Promise<readonly string[]> {
|
||||
try {
|
||||
const client = await getClient();
|
||||
const desc = await client.workflow.getHandle(workflowId).describe();
|
||||
const names: string[] = [];
|
||||
for (const pending of desc.raw.pendingActivities ?? []) {
|
||||
const name = pending.activityType?.name;
|
||||
if (name) names.push(name);
|
||||
}
|
||||
return names;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** Live progress of a running scan via the getProgress query. Null if the query can't be served (no worker). */
|
||||
export async function queryProgress(workflowId: string): Promise<PipelineState | null> {
|
||||
const client = await getClient();
|
||||
|
||||
Reference in new issue
Block a user