@vigolium/piolium 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/agents/access-auditor.md +300 -0
- package/agents/assumption-breaker.md +154 -0
- package/agents/attack-designer.md +116 -0
- package/agents/code-scanner.md +139 -0
- package/agents/concurrency-auditor.md +238 -0
- package/agents/confirm-writer.md +257 -0
- package/agents/context-reviewer.md +274 -0
- package/agents/cross-verifier.md +165 -0
- package/agents/cve-scout.md +381 -0
- package/agents/env-builder.md +282 -0
- package/agents/env-profiler.md +205 -0
- package/agents/evidence-collector.md +140 -0
- package/agents/finding-grader.md +142 -0
- package/agents/finding-writer.md +148 -0
- package/agents/flow-tracer.md +106 -0
- package/agents/goal-backtracer.md +146 -0
- package/agents/history-miner.md +467 -0
- package/agents/independent-verifier.md +118 -0
- package/agents/intent-mapper.md +183 -0
- package/agents/longshot-collector.md +128 -0
- package/agents/longshot-prober.md +126 -0
- package/agents/patch-auditor.md +73 -0
- package/agents/poc-author.md +124 -0
- package/agents/poc-runner.md +194 -0
- package/agents/probe-lead.md +269 -0
- package/agents/red-challenger.md +101 -0
- package/agents/report-composer.md +208 -0
- package/agents/review-adjudicator.md +216 -0
- package/agents/spec-auditor.md +155 -0
- package/agents/taint-tracer.md +265 -0
- package/agents/test-locator.md +209 -0
- package/agents/threat-modeler.md +132 -0
- package/agents/variant-scanner.md +108 -0
- package/agents/variant-spotter.md +110 -0
- package/bin/piolium.mjs +376 -0
- package/extensions/piolium/_vendor/yaml.bundle.d.mts +6 -0
- package/extensions/piolium/_vendor/yaml.bundle.mjs +139 -0
- package/extensions/piolium/agent-runner.ts +322 -0
- package/extensions/piolium/agents.ts +266 -0
- package/extensions/piolium/audit-state.ts +522 -0
- package/extensions/piolium/bundled-resources.ts +97 -0
- package/extensions/piolium/candidate-scan.ts +966 -0
- package/extensions/piolium/command-target.ts +177 -0
- package/extensions/piolium/console-stream.ts +57 -0
- package/extensions/piolium/export-results.ts +380 -0
- package/extensions/piolium/findings.ts +448 -0
- package/extensions/piolium/heartbeat.ts +182 -0
- package/extensions/piolium/help.ts +234 -0
- package/extensions/piolium/index.ts +1865 -0
- package/extensions/piolium/longshot.ts +530 -0
- package/extensions/piolium/matcher-suggestions.ts +196 -0
- package/extensions/piolium/matcher-utils.ts +83 -0
- package/extensions/piolium/modes/balanced.ts +750 -0
- package/extensions/piolium/modes/confirm-bootstrap.ts +186 -0
- package/extensions/piolium/modes/confirm.ts +697 -0
- package/extensions/piolium/modes/deep.ts +917 -0
- package/extensions/piolium/modes/diff.ts +177 -0
- package/extensions/piolium/modes/lite.ts +540 -0
- package/extensions/piolium/modes/longshot.ts +595 -0
- package/extensions/piolium/modes/merge.ts +204 -0
- package/extensions/piolium/modes/phase-runner.ts +267 -0
- package/extensions/piolium/modes/reinvest.ts +546 -0
- package/extensions/piolium/modes/revisit.ts +279 -0
- package/extensions/piolium/modes.ts +48 -0
- package/extensions/piolium/phase-labels.ts +123 -0
- package/extensions/piolium/phase-status-strip.ts +92 -0
- package/extensions/piolium/prompt-prefix-editor.ts +39 -0
- package/extensions/piolium/providers/anthropic-vertex.ts +836 -0
- package/extensions/piolium/recon.ts +409 -0
- package/extensions/piolium/result-stats.ts +105 -0
- package/extensions/piolium/retry.ts +120 -0
- package/extensions/piolium/scheduler.ts +212 -0
- package/extensions/piolium/secrets.ts +368 -0
- package/extensions/piolium/tools/web-tools.ts +148 -0
- package/package.json +77 -0
- package/skills/agentic-actions-auditor/SKILL.md +327 -0
- package/skills/agentic-actions-auditor/references/action-profiles.md +186 -0
- package/skills/agentic-actions-auditor/references/cross-file-resolution.md +209 -0
- package/skills/agentic-actions-auditor/references/foundations.md +94 -0
- package/skills/agentic-actions-auditor/references/vector-a-env-var-intermediary.md +77 -0
- package/skills/agentic-actions-auditor/references/vector-b-direct-expression-injection.md +83 -0
- package/skills/agentic-actions-auditor/references/vector-c-cli-data-fetch.md +83 -0
- package/skills/agentic-actions-auditor/references/vector-d-pr-target-checkout.md +88 -0
- package/skills/agentic-actions-auditor/references/vector-e-error-log-injection.md +88 -0
- package/skills/agentic-actions-auditor/references/vector-f-subshell-expansion.md +82 -0
- package/skills/agentic-actions-auditor/references/vector-g-eval-of-ai-output.md +91 -0
- package/skills/agentic-actions-auditor/references/vector-h-dangerous-sandbox-configs.md +102 -0
- package/skills/agentic-actions-auditor/references/vector-i-wildcard-allowlists.md +88 -0
- package/skills/audit/SKILL.md +562 -0
- package/skills/audit/assets/icon.svg +7 -0
- package/skills/audit/hooks/scripts/validate_phase_output.py +550 -0
- package/skills/audit/references/adversarial-review.md +148 -0
- package/skills/audit/references/architecture-aware-sast.md +306 -0
- package/skills/audit/references/audit-workflow.md +737 -0
- package/skills/audit/references/chamber-protocol.md +384 -0
- package/skills/audit/references/creative-attack-modes.md +221 -0
- package/skills/audit/references/deep-analysis.md +273 -0
- package/skills/audit/references/domain-attack-playbooks.md +1129 -0
- package/skills/audit/references/knowledge-base-template.md +513 -0
- package/skills/audit/references/real-env-validation.md +191 -0
- package/skills/audit/references/report-templates.md +417 -0
- package/skills/audit/references/triage-and-prereqs.md +134 -0
- package/skills/audit/scripts/consolidate_drafts.py +554 -0
- package/skills/audit/scripts/partition_findings.py +152 -0
- package/skills/audit/scripts/rg-hotspots.sh +121 -0
- package/skills/audit/scripts/stamp_file_state.py +349 -0
- package/skills/code-reviewer/SKILL.md +65 -0
- package/skills/codeql/SKILL.md +281 -0
- package/skills/codeql/references/build-fixes.md +90 -0
- package/skills/codeql/references/diagnostic-query-templates.md +339 -0
- package/skills/codeql/references/extension-yaml-format.md +209 -0
- package/skills/codeql/references/important-only-suite.md +153 -0
- package/skills/codeql/references/language-details.md +207 -0
- package/skills/codeql/references/macos-arm64e-workaround.md +179 -0
- package/skills/codeql/references/performance-tuning.md +111 -0
- package/skills/codeql/references/quality-assessment.md +172 -0
- package/skills/codeql/references/ruleset-catalog.md +63 -0
- package/skills/codeql/references/run-all-suite.md +92 -0
- package/skills/codeql/references/sarif-processing.md +79 -0
- package/skills/codeql/references/threat-models.md +51 -0
- package/skills/codeql/workflows/build-database.md +280 -0
- package/skills/codeql/workflows/create-data-extensions.md +261 -0
- package/skills/codeql/workflows/run-analysis.md +301 -0
- package/skills/differential-review/SKILL.md +220 -0
- package/skills/differential-review/adversarial.md +203 -0
- package/skills/differential-review/methodology.md +234 -0
- package/skills/differential-review/patterns.md +300 -0
- package/skills/differential-review/reporting.md +369 -0
- package/skills/fp-check/SKILL.md +125 -0
- package/skills/fp-check/references/bug-class-verification.md +114 -0
- package/skills/fp-check/references/deep-verification.md +143 -0
- package/skills/fp-check/references/evidence-templates.md +91 -0
- package/skills/fp-check/references/false-positive-patterns.md +115 -0
- package/skills/fp-check/references/gate-reviews.md +27 -0
- package/skills/fp-check/references/standard-verification.md +78 -0
- package/skills/insecure-defaults/SKILL.md +117 -0
- package/skills/insecure-defaults/references/examples.md +409 -0
- package/skills/last30days/SKILL.md +444 -0
- package/skills/sarif-parsing/SKILL.md +483 -0
- package/skills/sarif-parsing/resources/jq-queries.md +162 -0
- package/skills/sarif-parsing/resources/sarif_helpers.py +331 -0
- package/skills/security-threat-model/LICENSE.txt +201 -0
- package/skills/security-threat-model/SKILL.md +81 -0
- package/skills/security-threat-model/agents/openai.yaml +4 -0
- package/skills/security-threat-model/references/prompt-template.md +255 -0
- package/skills/security-threat-model/references/security-controls-and-assets.md +32 -0
- package/skills/semgrep/SKILL.md +212 -0
- package/skills/semgrep/references/rulesets.md +162 -0
- package/skills/semgrep/references/scan-modes.md +110 -0
- package/skills/semgrep/references/scanner-task-prompt.md +140 -0
- package/skills/semgrep/scripts/merge_sarif.py +203 -0
- package/skills/semgrep/workflows/scan-workflow.md +311 -0
- package/skills/semgrep-rule-creator/SKILL.md +168 -0
- package/skills/semgrep-rule-creator/references/quick-reference.md +202 -0
- package/skills/semgrep-rule-creator/references/workflow.md +240 -0
- package/skills/semgrep-rule-variant-creator/SKILL.md +205 -0
- package/skills/semgrep-rule-variant-creator/references/applicability-analysis.md +250 -0
- package/skills/semgrep-rule-variant-creator/references/language-syntax-guide.md +324 -0
- package/skills/semgrep-rule-variant-creator/references/workflow.md +518 -0
- package/skills/sharp-edges/SKILL.md +292 -0
- package/skills/sharp-edges/references/auth-patterns.md +252 -0
- package/skills/sharp-edges/references/case-studies.md +274 -0
- package/skills/sharp-edges/references/config-patterns.md +333 -0
- package/skills/sharp-edges/references/crypto-apis.md +190 -0
- package/skills/sharp-edges/references/lang-c.md +205 -0
- package/skills/sharp-edges/references/lang-csharp.md +285 -0
- package/skills/sharp-edges/references/lang-go.md +270 -0
- package/skills/sharp-edges/references/lang-java.md +263 -0
- package/skills/sharp-edges/references/lang-javascript.md +269 -0
- package/skills/sharp-edges/references/lang-kotlin.md +265 -0
- package/skills/sharp-edges/references/lang-php.md +245 -0
- package/skills/sharp-edges/references/lang-python.md +274 -0
- package/skills/sharp-edges/references/lang-ruby.md +273 -0
- package/skills/sharp-edges/references/lang-rust.md +272 -0
- package/skills/sharp-edges/references/lang-swift.md +287 -0
- package/skills/sharp-edges/references/language-specific.md +588 -0
- package/skills/spec-to-code-compliance/SKILL.md +357 -0
- package/skills/spec-to-code-compliance/resources/COMPLETENESS_CHECKLIST.md +69 -0
- package/skills/spec-to-code-compliance/resources/IR_EXAMPLES.md +417 -0
- package/skills/spec-to-code-compliance/resources/OUTPUT_REQUIREMENTS.md +105 -0
- package/skills/supply-chain-risk-auditor/SKILL.md +67 -0
- package/skills/supply-chain-risk-auditor/resources/results-template.md +41 -0
- package/skills/variant-analysis/METHODOLOGY.md +327 -0
- package/skills/variant-analysis/SKILL.md +142 -0
- package/skills/variant-analysis/resources/codeql/cpp.ql +119 -0
- package/skills/variant-analysis/resources/codeql/go.ql +69 -0
- package/skills/variant-analysis/resources/codeql/java.ql +71 -0
- package/skills/variant-analysis/resources/codeql/javascript.ql +63 -0
- package/skills/variant-analysis/resources/codeql/python.ql +80 -0
- package/skills/variant-analysis/resources/semgrep/cpp.yaml +98 -0
- package/skills/variant-analysis/resources/semgrep/go.yaml +63 -0
- package/skills/variant-analysis/resources/semgrep/java.yaml +61 -0
- package/skills/variant-analysis/resources/semgrep/javascript.yaml +60 -0
- package/skills/variant-analysis/resources/semgrep/python.yaml +72 -0
- package/skills/variant-analysis/resources/variant-report-template.md +75 -0
- package/skills/vuln-report/SKILL.md +137 -0
- package/skills/vuln-report/agents/openai.yaml +4 -0
- package/skills/vuln-report/references/report-template.md +135 -0
- package/skills/wooyun-legacy/SKILL.md +367 -0
- package/skills/wooyun-legacy/references/bank-penetration.md +222 -0
- package/skills/wooyun-legacy/references/checklists/command-execution-checklist.md +119 -0
- package/skills/wooyun-legacy/references/checklists/csrf-checklist.md +74 -0
- package/skills/wooyun-legacy/references/checklists/file-upload-checklist.md +108 -0
- package/skills/wooyun-legacy/references/checklists/info-disclosure-checklist.md +114 -0
- package/skills/wooyun-legacy/references/checklists/logic-flaws-checklist.md +95 -0
- package/skills/wooyun-legacy/references/checklists/misconfig-checklist.md +124 -0
- package/skills/wooyun-legacy/references/checklists/path-traversal-checklist.md +87 -0
- package/skills/wooyun-legacy/references/checklists/rce-checklist.md +93 -0
- package/skills/wooyun-legacy/references/checklists/sql-injection-checklist.md +97 -0
- package/skills/wooyun-legacy/references/checklists/ssrf-checklist.md +99 -0
- package/skills/wooyun-legacy/references/checklists/unauthorized-access-checklist.md +89 -0
- package/skills/wooyun-legacy/references/checklists/weak-password-checklist.md +115 -0
- package/skills/wooyun-legacy/references/checklists/xss-checklist.md +103 -0
- package/skills/wooyun-legacy/references/checklists/xxe-checklist.md +130 -0
- package/skills/wooyun-legacy/references/info-disclosure.md +975 -0
- package/skills/wooyun-legacy/references/logic-flaws.md +721 -0
- package/skills/wooyun-legacy/references/path-traversal.md +1191 -0
- package/skills/wooyun-legacy/references/telecom-penetration.md +156 -0
- package/skills/wooyun-legacy/references/unauthorized-access.md +980 -0
- package/skills/wooyun-legacy/references/xss.md +746 -0
- package/skills/zeroize-audit/SKILL.md +371 -0
- package/skills/zeroize-audit/configs/c.yaml +21 -0
- package/skills/zeroize-audit/configs/default.yaml +128 -0
- package/skills/zeroize-audit/configs/rust.yaml +83 -0
- package/skills/zeroize-audit/prompts/report_template.md +238 -0
- package/skills/zeroize-audit/prompts/system.md +163 -0
- package/skills/zeroize-audit/prompts/task.md +97 -0
- package/skills/zeroize-audit/references/compile-commands.md +231 -0
- package/skills/zeroize-audit/references/detection-strategy.md +191 -0
- package/skills/zeroize-audit/references/ir-analysis.md +252 -0
- package/skills/zeroize-audit/references/mcp-analysis.md +221 -0
- package/skills/zeroize-audit/references/poc-generation.md +470 -0
- package/skills/zeroize-audit/references/rust-zeroization-patterns.md +867 -0
- package/skills/zeroize-audit/schemas/input.json +83 -0
- package/skills/zeroize-audit/schemas/output.json +140 -0
- package/skills/zeroize-audit/tools/analyze_asm.sh +202 -0
- package/skills/zeroize-audit/tools/analyze_cfg.py +381 -0
- package/skills/zeroize-audit/tools/analyze_heap.sh +211 -0
- package/skills/zeroize-audit/tools/analyze_ir_semantic.py +429 -0
- package/skills/zeroize-audit/tools/diff_ir.sh +135 -0
- package/skills/zeroize-audit/tools/diff_rust_mir.sh +189 -0
- package/skills/zeroize-audit/tools/emit_asm.sh +67 -0
- package/skills/zeroize-audit/tools/emit_ir.sh +77 -0
- package/skills/zeroize-audit/tools/emit_rust_asm.sh +178 -0
- package/skills/zeroize-audit/tools/emit_rust_ir.sh +150 -0
- package/skills/zeroize-audit/tools/emit_rust_mir.sh +158 -0
- package/skills/zeroize-audit/tools/extract_compile_flags.py +284 -0
- package/skills/zeroize-audit/tools/generate_poc.py +1329 -0
- package/skills/zeroize-audit/tools/mcp/apply_confidence_gates.py +113 -0
- package/skills/zeroize-audit/tools/mcp/check_mcp.sh +68 -0
- package/skills/zeroize-audit/tools/mcp/normalize_mcp_evidence.py +125 -0
- package/skills/zeroize-audit/tools/scripts/check_llvm_patterns.py +481 -0
- package/skills/zeroize-audit/tools/scripts/check_mir_patterns.py +554 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm.py +424 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm_aarch64.py +300 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm_x86.py +283 -0
- package/skills/zeroize-audit/tools/scripts/find_dangerous_apis.py +375 -0
- package/skills/zeroize-audit/tools/scripts/semantic_audit.py +923 -0
- package/skills/zeroize-audit/tools/track_dataflow.sh +196 -0
- package/skills/zeroize-audit/tools/validate_rust_toolchain.sh +298 -0
- package/skills/zeroize-audit/workflows/phase-0-preflight.md +150 -0
- package/skills/zeroize-audit/workflows/phase-1-source-analysis.md +144 -0
- package/skills/zeroize-audit/workflows/phase-2-compiler-analysis.md +139 -0
- package/skills/zeroize-audit/workflows/phase-3-interim-report.md +46 -0
- package/skills/zeroize-audit/workflows/phase-4-poc-generation.md +46 -0
- package/skills/zeroize-audit/workflows/phase-5-poc-validation.md +136 -0
- package/skills/zeroize-audit/workflows/phase-6-final-report.md +44 -0
- package/skills/zeroize-audit/workflows/phase-7-test-generation.md +42 -0
- package/themes/piolium-srcery.json +94 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Longshot mode (`/piolium-longshot`).
|
|
3
|
+
*
|
|
4
|
+
* "Hail Mary" hunt — point an agent at every interesting source file in the
|
|
5
|
+
* repo and tell it to dig as hard as possible for vulnerabilities.
|
|
6
|
+
*
|
|
7
|
+
* Pipeline:
|
|
8
|
+
*
|
|
9
|
+
* 1 Enumerate → 2 Hunt (fan-out under Scheduler) → 3 Aggregate
|
|
10
|
+
*
|
|
11
|
+
* Phase 1 is deterministic and runs in-process. Phase 2 spawns one `longshot-prober`
|
|
12
|
+
* sub-agent per target file (capped by Scheduler.maxConcurrent). Phase 3 runs a
|
|
13
|
+
* single `longshot-collector` agent that reads every draft and produces a
|
|
14
|
+
* curated, deduplicated summary.
|
|
15
|
+
*
|
|
16
|
+
* Scale guardrails:
|
|
17
|
+
* - --plm-longshot-limit / PIOLIUM_LONGSHOT_LIMIT (default 1000)
|
|
18
|
+
* - --plm-longshot-timeout / PIOLIUM_LONGSHOT_TIMEOUT_MS (default 6h per file)
|
|
19
|
+
* - Files >1MB skipped automatically.
|
|
20
|
+
* - Tests + generated files filtered out (see longshot.ts).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { randomUUID } from "node:crypto";
|
|
24
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { type AgentRuntimeModel, runAgent } from "../agent-runner.ts";
|
|
27
|
+
import { type AgentDefinition, loadAgents } from "../agents.ts";
|
|
28
|
+
import {
|
|
29
|
+
type AuditRunState,
|
|
30
|
+
applyPhaseStatus,
|
|
31
|
+
initAudit,
|
|
32
|
+
latestAudit,
|
|
33
|
+
markAuditStatus,
|
|
34
|
+
readAuditState,
|
|
35
|
+
} from "../audit-state.ts";
|
|
36
|
+
import { runCandidateScanAsync } from "../candidate-scan.ts";
|
|
37
|
+
import {
|
|
38
|
+
LONGSHOT_DEFAULT_LIMIT,
|
|
39
|
+
LONGSHOT_DEFAULT_TIMEOUT_MS,
|
|
40
|
+
LONGSHOT_FINDINGS_DRAFT_DIR,
|
|
41
|
+
LONGSHOT_SUMMARY_PATH,
|
|
42
|
+
LONGSHOT_TARGETS_PATH,
|
|
43
|
+
type LongshotTarget,
|
|
44
|
+
type LongshotTargetsFile,
|
|
45
|
+
enumerateTargets,
|
|
46
|
+
pendingTargets,
|
|
47
|
+
readLongshotTargets,
|
|
48
|
+
updateTargetStatus,
|
|
49
|
+
writeLongshotTargets,
|
|
50
|
+
} from "../longshot.ts";
|
|
51
|
+
import { runReconAsync } from "../recon.ts";
|
|
52
|
+
import { errorMessage, readPositiveIntEnv } from "../retry.ts";
|
|
53
|
+
import { Scheduler } from "../scheduler.ts";
|
|
54
|
+
import { type PhaseUiHooks, runAgentPhase } from "./phase-runner.ts";
|
|
55
|
+
|
|
56
|
+
export type LongshotUiHooks = PhaseUiHooks;
|
|
57
|
+
|
|
58
|
+
export interface RunLongshotOptions {
|
|
59
|
+
cwd: string;
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
ui?: LongshotUiHooks;
|
|
62
|
+
forceFresh?: boolean;
|
|
63
|
+
agentRuntime?: AgentRuntimeModel;
|
|
64
|
+
/** Per-run override for the file cap. */
|
|
65
|
+
limit?: number;
|
|
66
|
+
/** Per-file kill timer in milliseconds. */
|
|
67
|
+
perFileTimeoutMs?: number;
|
|
68
|
+
/** Comma-list (e.g. ["Python", "Go"]) — overrides auto-detection. */
|
|
69
|
+
languages?: string[];
|
|
70
|
+
/** When true, include test files in enumeration. */
|
|
71
|
+
includeTests?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface RunLongshotResult {
|
|
75
|
+
auditId: string;
|
|
76
|
+
status: "complete" | "failed";
|
|
77
|
+
phases: Record<string, "complete" | "failed" | "skipped">;
|
|
78
|
+
targetsPath: string;
|
|
79
|
+
summaryPath: string;
|
|
80
|
+
targetsTotal: number;
|
|
81
|
+
targetsCompleted: number;
|
|
82
|
+
targetsFailed: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const STATUS_KEY = "piolium-longshot";
|
|
86
|
+
|
|
87
|
+
function readLimit(opt?: number): number {
|
|
88
|
+
if (opt && opt > 0) return opt;
|
|
89
|
+
return readPositiveIntEnv("PIOLIUM_LONGSHOT_LIMIT", LONGSHOT_DEFAULT_LIMIT);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readPerFileTimeoutMs(opt?: number): number {
|
|
93
|
+
if (opt && opt > 0) return opt;
|
|
94
|
+
return readPositiveIntEnv("PIOLIUM_LONGSHOT_TIMEOUT_MS", LONGSHOT_DEFAULT_TIMEOUT_MS);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readLanguages(opt?: string[]): string[] | undefined {
|
|
98
|
+
if (opt && opt.length > 0) return opt;
|
|
99
|
+
const raw = process.env.PIOLIUM_LONGSHOT_LANGS;
|
|
100
|
+
if (!raw) return undefined;
|
|
101
|
+
const parts = raw
|
|
102
|
+
.split(/[,\s]+/)
|
|
103
|
+
.map((s) => s.trim())
|
|
104
|
+
.filter(Boolean);
|
|
105
|
+
return parts.length > 0 ? parts : undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function readIncludeTests(opt?: boolean): boolean {
|
|
109
|
+
if (opt) return true;
|
|
110
|
+
const raw = process.env.PIOLIUM_LONGSHOT_INCLUDE_TESTS;
|
|
111
|
+
if (!raw) return false;
|
|
112
|
+
return /^(1|true|yes|on)$/i.test(raw.trim());
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function pickResumeAudit(cwd: string, forceFresh: boolean): AuditRunState | undefined {
|
|
116
|
+
if (forceFresh) return undefined;
|
|
117
|
+
const state = readAuditState(cwd).state;
|
|
118
|
+
if (!state) return undefined;
|
|
119
|
+
const audit = latestAudit(state);
|
|
120
|
+
if (!audit) return undefined;
|
|
121
|
+
if (audit.mode !== "longshot") return undefined;
|
|
122
|
+
if (audit.status === "complete") return undefined;
|
|
123
|
+
return audit;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function notify(ui: LongshotUiHooks | undefined, level: "info" | "warning" | "error", msg: string) {
|
|
127
|
+
ui?.notify?.(msg, level);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function setStatus(ui: LongshotUiHooks | undefined, text?: string) {
|
|
131
|
+
ui?.setStatus?.(STATUS_KEY, text);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function runX1(
|
|
135
|
+
cwd: string,
|
|
136
|
+
audit: AuditRunState,
|
|
137
|
+
options: RunLongshotOptions,
|
|
138
|
+
ui?: LongshotUiHooks,
|
|
139
|
+
): Promise<LongshotTargetsFile> {
|
|
140
|
+
const existing = readLongshotTargets(cwd);
|
|
141
|
+
const phaseState = audit.phases["1"];
|
|
142
|
+
if (existing && phaseState?.status === "complete") {
|
|
143
|
+
return existing;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
setStatus(ui, "● 1 enumerating targets");
|
|
147
|
+
await applyPhaseStatus(cwd, audit, "1", { status: "in_progress" });
|
|
148
|
+
try {
|
|
149
|
+
mkdirSync(join(cwd, "piolium", "attack-surface"), { recursive: true });
|
|
150
|
+
const limit = readLimit(options.limit);
|
|
151
|
+
const languages = readLanguages(options.languages);
|
|
152
|
+
const includeTests = readIncludeTests(options.includeTests);
|
|
153
|
+
const result = enumerateTargets({
|
|
154
|
+
cwd,
|
|
155
|
+
limit,
|
|
156
|
+
...(languages ? { languages } : {}),
|
|
157
|
+
includeTests,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (existing) {
|
|
161
|
+
// Resume: keep prior status for files still in the new target list
|
|
162
|
+
// so we don't re-run completed work after a fresh enumeration.
|
|
163
|
+
const priorByPath = new Map(existing.targets.map((t) => [t.path, t]));
|
|
164
|
+
result.targets = result.targets.map((t) => {
|
|
165
|
+
const prior = priorByPath.get(t.path);
|
|
166
|
+
if (!prior) return t;
|
|
167
|
+
return {
|
|
168
|
+
...t,
|
|
169
|
+
status: prior.status,
|
|
170
|
+
attempts: prior.attempts,
|
|
171
|
+
last_error: prior.last_error,
|
|
172
|
+
completed_at: prior.completed_at,
|
|
173
|
+
run_id: prior.run_id,
|
|
174
|
+
draft_count: prior.draft_count,
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
writeLongshotTargets(cwd, result);
|
|
180
|
+
await applyPhaseStatus(cwd, audit, "1", {
|
|
181
|
+
status: "complete",
|
|
182
|
+
artifacts: [LONGSHOT_TARGETS_PATH],
|
|
183
|
+
});
|
|
184
|
+
notify(
|
|
185
|
+
ui,
|
|
186
|
+
"info",
|
|
187
|
+
`Phase 1 enumerated ${result.targets.length} target file(s) (skipped ${result.skipped_tests} test, ${result.skipped_generated} generated, ${result.skipped_oversized} oversized).`,
|
|
188
|
+
);
|
|
189
|
+
return result;
|
|
190
|
+
} catch (err) {
|
|
191
|
+
await applyPhaseStatus(cwd, audit, "1", {
|
|
192
|
+
status: "failed",
|
|
193
|
+
error: errorMessage(err),
|
|
194
|
+
});
|
|
195
|
+
throw err;
|
|
196
|
+
} finally {
|
|
197
|
+
setStatus(ui, undefined);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function buildHunterTask(target: LongshotTarget, totalFiles: number, rank: number): string {
|
|
202
|
+
return [
|
|
203
|
+
"You are running phase 2 (Hunt) of a /piolium-longshot hail-mary scan.",
|
|
204
|
+
"Your job is to find real, exploitable vulnerabilities anchored on a single target file.",
|
|
205
|
+
"",
|
|
206
|
+
`Target file (anchor): \`${target.path}\``,
|
|
207
|
+
`Language: ${target.language}`,
|
|
208
|
+
`Rank in this run: ${rank}/${totalFiles} · Heuristic score: ${target.score}`,
|
|
209
|
+
`File hash slug: ${target.sha8}`,
|
|
210
|
+
"",
|
|
211
|
+
"Hard rules — read carefully:",
|
|
212
|
+
" 1. The anchor file is your starting point. You MUST read it in full first.",
|
|
213
|
+
" 2. You MAY follow imports, callers, and called functions across the repo to understand context.",
|
|
214
|
+
" Use Grep/Glob to locate references; read every file you reason about.",
|
|
215
|
+
" 3. Evidence is mandatory. Every claim about behavior must cite `path:line` from a file you actually read.",
|
|
216
|
+
" 4. Do NOT fabricate. If you cannot verify the chain, write a clear theoretical/uncertain note instead.",
|
|
217
|
+
" 5. Stay under 6 hours wall-clock. If you find nothing concrete after exhausting your obvious leads, exit cleanly.",
|
|
218
|
+
" 6. Do NOT run network requests, do NOT execute the application, do NOT mutate the repo outside of writing draft markdown files.",
|
|
219
|
+
"",
|
|
220
|
+
"What to look for (non-exhaustive — pick what fits the file):",
|
|
221
|
+
" - Command injection, SQLi, SSRF, RCE via deserialization, prototype pollution",
|
|
222
|
+
" - Path traversal, unsafe file/archive handling",
|
|
223
|
+
" - Missing/broken authn or authz on a route or operation",
|
|
224
|
+
" - Race conditions, TOCTOU, idempotency gaps",
|
|
225
|
+
" - Hardcoded crypto/secrets, weak primitives, unsafe randomness",
|
|
226
|
+
" - Trust boundary violations: user input flowing into privileged sinks without validation",
|
|
227
|
+
" - Logic flaws specific to this code (don't force a generic CWE; describe what's actually wrong)",
|
|
228
|
+
"",
|
|
229
|
+
`For every concrete finding, write a draft to \`${LONGSHOT_FINDINGS_DRAFT_DIR}/longshot-${target.sha8}-NNN-<slug>.md\` (NNN starts at 001 for this anchor).`,
|
|
230
|
+
"",
|
|
231
|
+
"Draft frontmatter:",
|
|
232
|
+
" ---",
|
|
233
|
+
" id: longshot-<sha8>-NNN",
|
|
234
|
+
" phase: 2",
|
|
235
|
+
" anchor: <relative-path-of-anchor>",
|
|
236
|
+
" slug: <kebab-case>",
|
|
237
|
+
" severity: critical|high|medium|low",
|
|
238
|
+
" confidence: high|medium|low",
|
|
239
|
+
" ---",
|
|
240
|
+
"",
|
|
241
|
+
"Draft body — required sections:",
|
|
242
|
+
" ## Summary",
|
|
243
|
+
" ## Location (file:line ranges, all files involved in the chain)",
|
|
244
|
+
" ## Attacker Control",
|
|
245
|
+
" ## Trust Boundary Crossed",
|
|
246
|
+
" ## Impact",
|
|
247
|
+
" ## Evidence (verbatim code excerpts with path:line)",
|
|
248
|
+
" ## Exploit Sketch (high-level — no live PoC)",
|
|
249
|
+
" ## Open Questions (anything you couldn't verify)",
|
|
250
|
+
"",
|
|
251
|
+
"If the file genuinely has nothing exploitable after rigorous review, write a single short note draft titled `longshot-<sha8>-000-no-finding.md` with a one-line `## Summary` saying so, and exit. Do not pad with false positives.",
|
|
252
|
+
"",
|
|
253
|
+
"Begin now. Start by reading the anchor file in full.",
|
|
254
|
+
].join("\n");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function runX2(
|
|
258
|
+
cwd: string,
|
|
259
|
+
audit: AuditRunState,
|
|
260
|
+
hunter: AgentDefinition | undefined,
|
|
261
|
+
targets: LongshotTargetsFile,
|
|
262
|
+
options: RunLongshotOptions,
|
|
263
|
+
ui?: LongshotUiHooks,
|
|
264
|
+
): Promise<{ completed: number; failed: number }> {
|
|
265
|
+
const phaseState = audit.phases["2"];
|
|
266
|
+
const remaining = pendingTargets(targets);
|
|
267
|
+
if (remaining.length === 0 && phaseState?.status === "complete") {
|
|
268
|
+
const completedAlready = targets.targets.filter((t) => t.status === "complete").length;
|
|
269
|
+
return { completed: completedAlready, failed: 0 };
|
|
270
|
+
}
|
|
271
|
+
if (!hunter) {
|
|
272
|
+
await applyPhaseStatus(cwd, audit, "2", {
|
|
273
|
+
status: "failed",
|
|
274
|
+
error: "longshot-prober agent not found in bundled agents/.",
|
|
275
|
+
});
|
|
276
|
+
throw new Error("longshot-prober agent missing");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await applyPhaseStatus(cwd, audit, "2", { status: "in_progress" });
|
|
280
|
+
setStatus(ui, `● 2 hunting ${remaining.length} file(s)`);
|
|
281
|
+
|
|
282
|
+
const perFileTimeoutMs = readPerFileTimeoutMs(options.perFileTimeoutMs);
|
|
283
|
+
const scheduler = new Scheduler({
|
|
284
|
+
maxConcurrent: 3,
|
|
285
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
let completed = 0;
|
|
289
|
+
let failed = 0;
|
|
290
|
+
const total = targets.targets.length;
|
|
291
|
+
const previouslyCompleted = total - remaining.length;
|
|
292
|
+
completed += previouslyCompleted;
|
|
293
|
+
|
|
294
|
+
const updateProgressStatus = () => {
|
|
295
|
+
setStatus(
|
|
296
|
+
ui,
|
|
297
|
+
`● 2 hunting (${completed}/${total} done${failed > 0 ? `, ${failed} failed` : ""})`,
|
|
298
|
+
);
|
|
299
|
+
};
|
|
300
|
+
updateProgressStatus();
|
|
301
|
+
|
|
302
|
+
const auditIdSafe = audit.audit_id.replace(/[:.]/g, "-");
|
|
303
|
+
const tasks = remaining.map((target) => {
|
|
304
|
+
const rank = targets.targets.findIndex((t) => t.path === target.path) + 1;
|
|
305
|
+
return {
|
|
306
|
+
id: `longshot-${target.sha8}`,
|
|
307
|
+
timeoutMs: perFileTimeoutMs,
|
|
308
|
+
run: async (signal: AbortSignal) => {
|
|
309
|
+
const runId = `x2-${auditIdSafe}-${target.sha8}-${randomUUID().slice(0, 8)}`;
|
|
310
|
+
await updateTargetStatus(cwd, target.path, {
|
|
311
|
+
status: "in_progress",
|
|
312
|
+
incrementAttempts: true,
|
|
313
|
+
run_id: runId,
|
|
314
|
+
});
|
|
315
|
+
try {
|
|
316
|
+
const draftsBefore = countDraftsForAnchor(cwd, target.sha8);
|
|
317
|
+
await runAgent({
|
|
318
|
+
agent: hunter,
|
|
319
|
+
task: buildHunterTask(target, total, rank),
|
|
320
|
+
runId,
|
|
321
|
+
runtime: {
|
|
322
|
+
cwd,
|
|
323
|
+
mode: "longshot",
|
|
324
|
+
phase: "2",
|
|
325
|
+
outputPaths: [
|
|
326
|
+
join(cwd, LONGSHOT_FINDINGS_DRAFT_DIR),
|
|
327
|
+
join(cwd, "piolium", "attack-surface"),
|
|
328
|
+
],
|
|
329
|
+
notes: [
|
|
330
|
+
`Anchor file: ${target.path}`,
|
|
331
|
+
`Language: ${target.language}`,
|
|
332
|
+
`Per-file timeout: ${perFileTimeoutMs}ms`,
|
|
333
|
+
`This is a single tile in a swarm of ${remaining.length} parallel hunts (rank ${rank} of ${total}).`,
|
|
334
|
+
"Stay anchored on this file; cross-file reading is allowed but don't sprawl.",
|
|
335
|
+
],
|
|
336
|
+
},
|
|
337
|
+
...(options.agentRuntime ? options.agentRuntime : {}),
|
|
338
|
+
signal,
|
|
339
|
+
onEvent: (event) => ui?.onAgentEvent?.("2", event),
|
|
340
|
+
});
|
|
341
|
+
const draftsAfter = countDraftsForAnchor(cwd, target.sha8);
|
|
342
|
+
const drafts = Math.max(0, draftsAfter - draftsBefore);
|
|
343
|
+
await updateTargetStatus(cwd, target.path, {
|
|
344
|
+
status: "complete",
|
|
345
|
+
completed_at: new Date().toISOString(),
|
|
346
|
+
draft_count: drafts,
|
|
347
|
+
});
|
|
348
|
+
completed++;
|
|
349
|
+
updateProgressStatus();
|
|
350
|
+
} catch (err) {
|
|
351
|
+
failed++;
|
|
352
|
+
await updateTargetStatus(cwd, target.path, {
|
|
353
|
+
status: "failed",
|
|
354
|
+
last_error: errorMessage(err),
|
|
355
|
+
});
|
|
356
|
+
updateProgressStatus();
|
|
357
|
+
notify(ui, "warning", `Longshot ${target.sha8} (${target.path}) failed: ${errorMessage(err)}`);
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
await scheduler.runBatch(tasks);
|
|
364
|
+
scheduler.dispose();
|
|
365
|
+
|
|
366
|
+
if (failed > 0 && completed === previouslyCompleted) {
|
|
367
|
+
// Every remaining task failed — flag phase 2 as failed so the user sees it.
|
|
368
|
+
await applyPhaseStatus(cwd, audit, "2", {
|
|
369
|
+
status: "failed",
|
|
370
|
+
error: `All ${failed} hunter tasks failed.`,
|
|
371
|
+
artifacts: [LONGSHOT_TARGETS_PATH],
|
|
372
|
+
});
|
|
373
|
+
} else {
|
|
374
|
+
await applyPhaseStatus(cwd, audit, "2", {
|
|
375
|
+
status: "complete",
|
|
376
|
+
artifacts: [LONGSHOT_TARGETS_PATH],
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
setStatus(ui, undefined);
|
|
380
|
+
return { completed, failed };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function countDraftsForAnchor(cwd: string, sha8: string): number {
|
|
384
|
+
const dir = join(cwd, LONGSHOT_FINDINGS_DRAFT_DIR);
|
|
385
|
+
if (!existsSync(dir)) return 0;
|
|
386
|
+
try {
|
|
387
|
+
return readdirSync(dir).filter((f) => f.startsWith(`longshot-${sha8}-`) && f.endsWith(".md"))
|
|
388
|
+
.length;
|
|
389
|
+
} catch {
|
|
390
|
+
return 0;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function buildAggregatorTask(targets: LongshotTargetsFile): string {
|
|
395
|
+
return [
|
|
396
|
+
"You are running phase 3 (Aggregate) of /piolium-longshot.",
|
|
397
|
+
"Phase 2 produced a flood of per-file drafts under `piolium/findings-draft/longshot-*.md`.",
|
|
398
|
+
"Your job: read every draft, deduplicate overlapping findings, rank by severity + confidence,",
|
|
399
|
+
"and emit a curated report.",
|
|
400
|
+
"",
|
|
401
|
+
"Inputs:",
|
|
402
|
+
` - Targets file: \`${LONGSHOT_TARGETS_PATH}\` (read it for anchor → sha8 mapping)`,
|
|
403
|
+
" - Drafts: `piolium/findings-draft/longshot-*.md`",
|
|
404
|
+
` - Total anchors hunted: ${targets.targets.length}`,
|
|
405
|
+
` - Anchors completed: ${targets.targets.filter((t) => t.status === "complete").length}`,
|
|
406
|
+
` - Anchors failed: ${targets.targets.filter((t) => t.status === "failed").length}`,
|
|
407
|
+
"",
|
|
408
|
+
"Steps:",
|
|
409
|
+
" 1. List every `longshot-*-NNN-*.md` draft in the findings-draft directory.",
|
|
410
|
+
" 2. Read each one. Skip `*-000-no-finding.md` files (those are explicit no-result markers).",
|
|
411
|
+
" 3. Group drafts by root cause / sink / vulnerable symbol. Two drafts that point at the",
|
|
412
|
+
" same underlying bug from different files are duplicates — merge them.",
|
|
413
|
+
" 4. For each unique vulnerability, write a curated draft to",
|
|
414
|
+
" `piolium/findings-draft/longshot-curated-NNN-<slug>.md` with:",
|
|
415
|
+
" - frontmatter: id, phase: 3, slug, severity, confidence, source_drafts (list of merged paths)",
|
|
416
|
+
" - sections: Summary, Affected Files, Root Cause, Attacker Control, Impact, Evidence, Exploit Sketch, Confidence Notes.",
|
|
417
|
+
" 5. Rank curated findings by severity (critical > high > medium > low) then confidence.",
|
|
418
|
+
` 6. Write \`${LONGSHOT_SUMMARY_PATH}\` with:`,
|
|
419
|
+
" - Run metadata (date, anchor counts, languages targeted)",
|
|
420
|
+
" - Per-anchor table: path, score, status, draft_count",
|
|
421
|
+
" - Curated finding table: id, severity, confidence, slug, anchor file(s)",
|
|
422
|
+
" - Top 5 most concerning findings with one-paragraph summaries.",
|
|
423
|
+
"",
|
|
424
|
+
"Hard rules:",
|
|
425
|
+
" - Don't invent findings the drafts don't already claim. You are summarizing, not hunting.",
|
|
426
|
+
" - If two drafts disagree about severity, pick the better-evidenced one and note the discrepancy.",
|
|
427
|
+
" - Drop drafts that have no `## Evidence` section or lack `path:line` citations — they are unreliable.",
|
|
428
|
+
" - Always write the summary file even if zero findings survive curation.",
|
|
429
|
+
].join("\n");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function runX3(
|
|
433
|
+
cwd: string,
|
|
434
|
+
audit: AuditRunState,
|
|
435
|
+
aggregator: AgentDefinition | undefined,
|
|
436
|
+
targets: LongshotTargetsFile,
|
|
437
|
+
options: RunLongshotOptions,
|
|
438
|
+
ui?: LongshotUiHooks,
|
|
439
|
+
): Promise<void> {
|
|
440
|
+
const summaryAbs = join(cwd, LONGSHOT_SUMMARY_PATH);
|
|
441
|
+
if (!aggregator) {
|
|
442
|
+
await applyPhaseStatus(cwd, audit, "3", {
|
|
443
|
+
status: "failed",
|
|
444
|
+
error: "longshot-collector agent not found in bundled agents/.",
|
|
445
|
+
});
|
|
446
|
+
throw new Error("longshot-collector agent missing");
|
|
447
|
+
}
|
|
448
|
+
await runAgentPhase({
|
|
449
|
+
cwd,
|
|
450
|
+
audit,
|
|
451
|
+
phaseName: "3",
|
|
452
|
+
statusKey: STATUS_KEY,
|
|
453
|
+
statusLabel: "● 3 aggregating drafts",
|
|
454
|
+
agent: aggregator,
|
|
455
|
+
missingAgentMessage: "longshot-collector agent missing",
|
|
456
|
+
task: buildAggregatorTask(targets),
|
|
457
|
+
runtimeExtras: {
|
|
458
|
+
outputPaths: [join(cwd, LONGSHOT_FINDINGS_DRAFT_DIR), join(cwd, "piolium", "attack-surface")],
|
|
459
|
+
notes: [`Targets file: ${LONGSHOT_TARGETS_PATH}`, "Read drafts; do not re-run hunts."],
|
|
460
|
+
},
|
|
461
|
+
gate: () => existsSync(summaryAbs),
|
|
462
|
+
mode: "longshot",
|
|
463
|
+
ui,
|
|
464
|
+
agentRuntime: options.agentRuntime,
|
|
465
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export async function runLongshotAudit(opts: RunLongshotOptions): Promise<RunLongshotResult> {
|
|
470
|
+
const { cwd, ui } = opts;
|
|
471
|
+
mkdirSync(join(cwd, "piolium", "attack-surface"), { recursive: true });
|
|
472
|
+
mkdirSync(join(cwd, LONGSHOT_FINDINGS_DRAFT_DIR), { recursive: true });
|
|
473
|
+
|
|
474
|
+
ui?.setStatus?.(STATUS_KEY, "● preparing recon");
|
|
475
|
+
const recon = await runReconAsync(cwd, { signal: opts.signal });
|
|
476
|
+
ui?.setStatus?.(STATUS_KEY, "● scanning candidate files");
|
|
477
|
+
const candidateScan = await runCandidateScanAsync(cwd, { signal: opts.signal });
|
|
478
|
+
ui?.notify?.(
|
|
479
|
+
`Candidate scan: ${candidateScan.candidateCount} match(es) across ${candidateScan.candidateFiles} file(s).`,
|
|
480
|
+
"info",
|
|
481
|
+
);
|
|
482
|
+
let audit = pickResumeAudit(cwd, opts.forceFresh ?? false);
|
|
483
|
+
if (!audit) {
|
|
484
|
+
audit = await initAudit(cwd, {
|
|
485
|
+
mode: "longshot",
|
|
486
|
+
...(recon.commit ? { commit: recon.commit } : { commit: null }),
|
|
487
|
+
...(recon.branch ? { branch: recon.branch } : { branch: "nogit" }),
|
|
488
|
+
...(recon.repository ? { repository: recon.repository } : {}),
|
|
489
|
+
history_available: recon.historyAvailable,
|
|
490
|
+
agent_sdk: "pi",
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const { agents } = loadAgents({ cwd });
|
|
495
|
+
const hunter = agents.get("longshot-prober");
|
|
496
|
+
const aggregator = agents.get("longshot-collector");
|
|
497
|
+
|
|
498
|
+
let targets: LongshotTargetsFile;
|
|
499
|
+
let failed = false;
|
|
500
|
+
try {
|
|
501
|
+
targets = await runX1(cwd, audit, opts, ui);
|
|
502
|
+
} catch {
|
|
503
|
+
await markAuditStatus(cwd, audit.audit_id, "failed");
|
|
504
|
+
setStatus(ui, undefined);
|
|
505
|
+
return finalResult(audit, "failed", cwd, undefined);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (targets.targets.length === 0) {
|
|
509
|
+
notify(
|
|
510
|
+
ui,
|
|
511
|
+
"warning",
|
|
512
|
+
"Phase 1 produced 0 candidate files. Check --plm-longshot-langs or repository contents.",
|
|
513
|
+
);
|
|
514
|
+
// Skip phases 2/3 — nothing to hunt or aggregate.
|
|
515
|
+
await applyPhaseStatus(cwd, audit, "2", { status: "skipped" });
|
|
516
|
+
await applyPhaseStatus(cwd, audit, "3", { status: "skipped" });
|
|
517
|
+
writeFileSync(
|
|
518
|
+
join(cwd, LONGSHOT_SUMMARY_PATH),
|
|
519
|
+
[
|
|
520
|
+
"# piolium Longshot Summary",
|
|
521
|
+
"",
|
|
522
|
+
`Generated: ${new Date().toISOString()}`,
|
|
523
|
+
"",
|
|
524
|
+
"No candidate files matched the longshot enumeration filters. Adjust",
|
|
525
|
+
"`--plm-longshot-langs` or run on a repository with recognized source files.",
|
|
526
|
+
"",
|
|
527
|
+
].join("\n"),
|
|
528
|
+
);
|
|
529
|
+
await markAuditStatus(cwd, audit.audit_id, "complete");
|
|
530
|
+
return finalResult(audit, "complete", cwd, targets);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
let huntStats = { completed: 0, failed: 0 };
|
|
534
|
+
try {
|
|
535
|
+
huntStats = await runX2(cwd, audit, hunter, targets, opts, ui);
|
|
536
|
+
} catch {
|
|
537
|
+
failed = true;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Reload latest sidecar in case phase 2 updated statuses.
|
|
541
|
+
const refreshedTargets = readLongshotTargets(cwd) ?? targets;
|
|
542
|
+
|
|
543
|
+
if (!failed) {
|
|
544
|
+
try {
|
|
545
|
+
await runX3(cwd, audit, aggregator, refreshedTargets, opts, ui);
|
|
546
|
+
} catch {
|
|
547
|
+
failed = true;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const final = await markAuditStatus(cwd, audit.audit_id, failed ? "failed" : "complete");
|
|
552
|
+
setStatus(ui, undefined);
|
|
553
|
+
if (failed) {
|
|
554
|
+
notify(
|
|
555
|
+
ui,
|
|
556
|
+
"error",
|
|
557
|
+
`Longshot audit failed (phase 2 completed ${huntStats.completed}, failed ${huntStats.failed}).`,
|
|
558
|
+
);
|
|
559
|
+
} else {
|
|
560
|
+
notify(
|
|
561
|
+
ui,
|
|
562
|
+
"info",
|
|
563
|
+
`Longshot audit complete. ${huntStats.completed} file(s) hunted, ${huntStats.failed} failed.`,
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
return finalResult(final ?? audit, failed ? "failed" : "complete", cwd, refreshedTargets);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function finalResult(
|
|
570
|
+
audit: AuditRunState,
|
|
571
|
+
status: "complete" | "failed",
|
|
572
|
+
cwd: string,
|
|
573
|
+
targets: LongshotTargetsFile | undefined,
|
|
574
|
+
): RunLongshotResult {
|
|
575
|
+
const phases: Record<string, "complete" | "failed" | "skipped"> = {};
|
|
576
|
+
for (const [name, phase] of Object.entries(audit.phases)) {
|
|
577
|
+
if (phase.status === "complete" || phase.status === "failed" || phase.status === "skipped") {
|
|
578
|
+
phases[name] = phase.status;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const completed = targets?.targets.filter((t) => t.status === "complete").length ?? 0;
|
|
582
|
+
const failedCount = targets?.targets.filter((t) => t.status === "failed").length ?? 0;
|
|
583
|
+
return {
|
|
584
|
+
auditId: audit.audit_id,
|
|
585
|
+
status,
|
|
586
|
+
phases,
|
|
587
|
+
targetsPath: join(cwd, LONGSHOT_TARGETS_PATH),
|
|
588
|
+
summaryPath: join(cwd, LONGSHOT_SUMMARY_PATH),
|
|
589
|
+
targetsTotal: targets?.targets.length ?? 0,
|
|
590
|
+
targetsCompleted: completed,
|
|
591
|
+
targetsFailed: failedCount,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export { LONGSHOT_TARGETS_PATH, LONGSHOT_SUMMARY_PATH, LONGSHOT_FINDINGS_DRAFT_DIR };
|