mirror of
https://github.com/KeygraphHQ/shannon.git
synced 2026-07-05 20:57:54 +02:00
feat(preflight): support multi-repo targets by removing .git check (#371)
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
* time and API costs compared to failing mid-pipeline.
|
||||
*
|
||||
* Checks run sequentially, cheapest first:
|
||||
* 1. Repository path exists and contains .git
|
||||
* 1. Repository path exists and is a directory
|
||||
* 2. Config file parses and validates (if provided)
|
||||
* 3. code_path rules match real entries in the repo (filesystem only)
|
||||
* 4. Credentials validate via Claude Agent SDK query (API key, OAuth, Bedrock, or Vertex AI)
|
||||
@@ -78,14 +78,12 @@ function pinnedLookup(addresses: LookupAddress[]): LookupFunction {
|
||||
|
||||
// === Repository Validation ===
|
||||
|
||||
async function validateRepo(
|
||||
repoPath: string,
|
||||
logger: ActivityLogger,
|
||||
skipGitCheck?: boolean,
|
||||
): Promise<Result<void, PentestError>> {
|
||||
async function validateRepo(repoPath: string, logger: ActivityLogger): Promise<Result<void, PentestError>> {
|
||||
logger.info('Checking repository path...', { repoPath });
|
||||
|
||||
// 1. Check repo directory exists
|
||||
// Check repo directory exists. The repo is not required to be a git repository:
|
||||
// multi-repo targets (a parent directory containing several repos) have no top-level
|
||||
// .git, and git-based checkpoint/rollback in git-manager already no-ops on non-git dirs.
|
||||
try {
|
||||
const stats = await fs.stat(repoPath);
|
||||
if (!stats.isDirectory()) {
|
||||
@@ -111,36 +109,6 @@ async function validateRepo(
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Check .git directory exists (skipped when consumer removes .git after clone)
|
||||
if (!skipGitCheck) {
|
||||
try {
|
||||
const gitStats = await fs.stat(`${repoPath}/.git`);
|
||||
if (!gitStats.isDirectory()) {
|
||||
return err(
|
||||
new PentestError(
|
||||
`Not a git repository (no .git directory): ${repoPath}`,
|
||||
'config',
|
||||
false,
|
||||
{ repoPath },
|
||||
ErrorCode.REPO_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
return err(
|
||||
new PentestError(
|
||||
`Not a git repository (no .git directory): ${repoPath}`,
|
||||
'config',
|
||||
false,
|
||||
{ repoPath },
|
||||
ErrorCode.REPO_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logger.info('Skipping .git check (skipGitCheck enabled)');
|
||||
}
|
||||
|
||||
logger.info('Repository path OK');
|
||||
return ok(undefined);
|
||||
}
|
||||
@@ -618,7 +586,7 @@ async function validateTargetUrl(targetUrl: string, logger: ActivityLogger): Pro
|
||||
/**
|
||||
* Run all preflight checks sequentially (cheapest first).
|
||||
*
|
||||
* 1. Repository path exists and contains .git
|
||||
* 1. Repository path exists and is a directory
|
||||
* 2. Config file parses and validates (if configPath provided)
|
||||
* 3. code_path rules match at least one entry in the repo (skipped without config)
|
||||
* 4. Credentials validate (API key, OAuth, Bedrock, or Vertex AI)
|
||||
@@ -631,12 +599,11 @@ export async function runPreflightChecks(
|
||||
repoPath: string,
|
||||
configPath: string | undefined,
|
||||
logger: ActivityLogger,
|
||||
skipGitCheck?: boolean,
|
||||
apiKey?: string,
|
||||
providerConfig?: import('../types/config.js').ProviderConfig,
|
||||
): Promise<Result<void, PentestError>> {
|
||||
// 1. Repository check (free — filesystem only)
|
||||
const repoResult = await validateRepo(repoPath, logger, skipGitCheck);
|
||||
const repoResult = await validateRepo(repoPath, logger);
|
||||
if (!repoResult.ok) {
|
||||
return repoResult;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,6 @@ export interface ActivityInput {
|
||||
auditDir?: string;
|
||||
promptDir?: string;
|
||||
sastSarifPath?: string;
|
||||
skipGitCheck?: boolean;
|
||||
providerConfig?: ProviderConfig;
|
||||
}
|
||||
|
||||
@@ -457,7 +456,7 @@ export async function runReportAgent(input: ActivityInput): Promise<AgentMetrics
|
||||
* Preflight validation activity.
|
||||
*
|
||||
* Runs cheap checks before any agent execution:
|
||||
* 1. Repository path exists with .git
|
||||
* 1. Repository path exists and is a directory
|
||||
* 2. Config file validates (if provided)
|
||||
* 3. Credential validation (API key, OAuth, Bedrock, or Vertex AI)
|
||||
* 4. Target URL reachable from the container
|
||||
@@ -482,7 +481,6 @@ export async function runPreflightValidation(input: ActivityInput): Promise<void
|
||||
input.repoPath,
|
||||
input.configPath,
|
||||
logger,
|
||||
input.skipGitCheck,
|
||||
input.apiKey,
|
||||
input.providerConfig,
|
||||
);
|
||||
|
||||
@@ -27,7 +27,6 @@ export interface PipelineInput {
|
||||
promptDir?: string; // Override prompt template directory
|
||||
sastSarifPath?: string; // Optional path for consumer-supplied findings input
|
||||
checkpointsEnabled?: boolean; // Enable checkpoint activities (default: false)
|
||||
skipGitCheck?: boolean; // Skip .git directory validation in preflight (e.g. when .git is removed after clone)
|
||||
providerConfig?: ProviderConfig; // LLM provider configuration (Bedrock, Vertex, etc.)
|
||||
vulnClasses?: VulnClass[]; // omitted = all five
|
||||
exploit?: boolean; // false skips the exploitation phase
|
||||
|
||||
@@ -242,7 +242,6 @@ export async function pentestPipeline(input: PipelineInput): Promise<PipelineSta
|
||||
...(input.auditDir !== undefined && { auditDir: input.auditDir }),
|
||||
...(input.promptDir !== undefined && { promptDir: input.promptDir }),
|
||||
...(input.sastSarifPath !== undefined && { sastSarifPath: input.sastSarifPath }),
|
||||
...(input.skipGitCheck !== undefined && { skipGitCheck: input.skipGitCheck }),
|
||||
...(input.providerConfig !== undefined && { providerConfig: input.providerConfig }),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user