paqad-ai 0.1.1 → 0.1.3
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/dist/cli/index.js +540 -163
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.js +555 -219
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/runtime/base/rules/design-system.md +13 -0
- package/runtime/capabilities/coding/stacks/flutter/pack.yaml +5 -1
- package/runtime/capabilities/coding/stacks/laravel/pack.yaml +15 -1
- package/runtime/capabilities/coding/stacks/react/pack.yaml +6 -2
- package/runtime/capabilities/coding/stacks/vue/pack.yaml +6 -2
- package/runtime/capabilities/security/rules/pentest.md +169 -0
- package/runtime/capabilities/security/skills/auth-mechanism-review/SKILL.md +88 -0
- package/runtime/capabilities/security/skills/auth-mechanism-review/agents/openai.yaml +3 -0
- package/runtime/capabilities/security/skills/auth-mechanism-review/references/auth-attack-checklist.md +84 -0
- package/runtime/capabilities/security/skills/business-logic-abuse-review/references/abuse-cases.md +33 -5
- package/runtime/capabilities/security/skills/cryptographic-review/SKILL.md +72 -0
- package/runtime/capabilities/security/skills/cryptographic-review/agents/openai.yaml +3 -0
- package/runtime/capabilities/security/skills/cryptographic-review/references/crypto-weakness-patterns.md +156 -0
- package/runtime/capabilities/security/skills/dependency-advisory-triage/references/advisory-normalization.md +33 -1
- package/runtime/capabilities/security/skills/input-validation-review/SKILL.md +75 -0
- package/runtime/capabilities/security/skills/input-validation-review/agents/openai.yaml +3 -0
- package/runtime/capabilities/security/skills/input-validation-review/references/input-attack-patterns.md +74 -0
- package/runtime/capabilities/security/skills/logging-monitoring-review/SKILL.md +72 -0
- package/runtime/capabilities/security/skills/logging-monitoring-review/agents/openai.yaml +3 -0
- package/runtime/capabilities/security/skills/logging-monitoring-review/references/logging-gaps-checklist.md +77 -0
- package/runtime/capabilities/security/skills/permission-boundary-review/references/boundary-checklist.md +31 -1
- package/runtime/capabilities/security/skills/rate-limiting-review/SKILL.md +67 -0
- package/runtime/capabilities/security/skills/rate-limiting-review/agents/openai.yaml +3 -0
- package/runtime/capabilities/security/skills/rate-limiting-review/references/rate-limit-signals.md +153 -0
- package/runtime/capabilities/security/skills/runtime-surface-probing/references/runtime-surface-checks.md +57 -3
- package/runtime/capabilities/security/skills/stride-threat-model/SKILL.md +60 -0
- package/runtime/capabilities/security/skills/stride-threat-model/agents/openai.yaml +3 -0
- package/runtime/capabilities/security/skills/stride-threat-model/references/stride-checklist.md +69 -0
- package/runtime/hooks/silent-update.sh +115 -0
- package/runtime/templates/agent-configs/agents.md.hbs +10 -0
- package/runtime/templates/agent-configs/claude.md.hbs +10 -0
- package/runtime/templates/agent-configs/gemini.md.hbs +10 -0
- package/runtime/templates/agent-configs/junie.md.hbs +11 -0
- package/scripts/deprecate-old-versions.sh +55 -0
package/dist/cli/index.js
CHANGED
|
@@ -96,7 +96,12 @@ var PATHS = {
|
|
|
96
96
|
CLAUDE_MD: "CLAUDE.md",
|
|
97
97
|
AGENTS_MD: "AGENTS.md",
|
|
98
98
|
GEMINI_MD: "GEMINI.md",
|
|
99
|
-
SCRIPTS_DIR: "scripts"
|
|
99
|
+
SCRIPTS_DIR: "scripts",
|
|
100
|
+
HOOKS_DIR: ".paqad/hooks",
|
|
101
|
+
LOGS_DIR: ".paqad/logs",
|
|
102
|
+
LOCKS_DIR: ".paqad/locks",
|
|
103
|
+
AUTO_UPDATE_LOG: ".paqad/logs/auto-update.log",
|
|
104
|
+
HOOKS_SILENT_UPDATE: ".paqad/hooks/silent-update.sh"
|
|
100
105
|
};
|
|
101
106
|
var REGISTRIES = [
|
|
102
107
|
"module-registry.md",
|
|
@@ -4137,8 +4142,10 @@ function writeFrameworkMetadata(projectRoot, version) {
|
|
|
4137
4142
|
mkdirSync2(dirname11(join21(projectRoot, PATHS.FRAMEWORK_VERSION)), {
|
|
4138
4143
|
recursive: true
|
|
4139
4144
|
});
|
|
4140
|
-
|
|
4141
|
-
|
|
4145
|
+
const content = `version=${version}
|
|
4146
|
+
updated_at=${(/* @__PURE__ */ new Date()).toISOString()}
|
|
4147
|
+
`;
|
|
4148
|
+
writeFileSync2(join21(projectRoot, PATHS.FRAMEWORK_VERSION), content);
|
|
4142
4149
|
writeFileSync2(join21(projectRoot, PATHS.FRAMEWORK_PATH), `${resolveFrameworkInstallPath()}
|
|
4143
4150
|
`);
|
|
4144
4151
|
}
|
|
@@ -5518,6 +5525,10 @@ function writeGeneratedFiles(projectRoot, files) {
|
|
|
5518
5525
|
return { written, skipped };
|
|
5519
5526
|
}
|
|
5520
5527
|
|
|
5528
|
+
// src/onboarding/orchestrator.ts
|
|
5529
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
5530
|
+
import { join as join33 } from "path";
|
|
5531
|
+
|
|
5521
5532
|
// src/resolver/resolver.ts
|
|
5522
5533
|
import fg2 from "fast-glob";
|
|
5523
5534
|
import { basename as basename3, extname, relative as relative3 } from "pathe";
|
|
@@ -5711,6 +5722,8 @@ function resolveArtifactDirectoryName(artifactType) {
|
|
|
5711
5722
|
var RULE_SEED_PRIORITY = [
|
|
5712
5723
|
"constitution",
|
|
5713
5724
|
"security",
|
|
5725
|
+
"pentest",
|
|
5726
|
+
"design-system",
|
|
5714
5727
|
"content-rules",
|
|
5715
5728
|
"testing",
|
|
5716
5729
|
"documentation",
|
|
@@ -5806,9 +5819,34 @@ function getRulePriority(filePath) {
|
|
|
5806
5819
|
return index === -1 ? RULE_SEED_PRIORITY.length : index;
|
|
5807
5820
|
}
|
|
5808
5821
|
|
|
5822
|
+
// src/onboarding/gitignore-writer.ts
|
|
5823
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
|
|
5824
|
+
import { join as join30 } from "path";
|
|
5825
|
+
var PAQAD_MARKER = "# paqad-ai";
|
|
5826
|
+
var PAQAD_GITIGNORE_ENTRIES = [
|
|
5827
|
+
PAQAD_MARKER,
|
|
5828
|
+
".paqad/cache/",
|
|
5829
|
+
".paqad/session/",
|
|
5830
|
+
".paqad/context/",
|
|
5831
|
+
".paqad/workflows/",
|
|
5832
|
+
".paqad/indexes/",
|
|
5833
|
+
".paqad/pentest/",
|
|
5834
|
+
".paqad/theme/"
|
|
5835
|
+
].join("\n");
|
|
5836
|
+
function writeGitignore(projectRoot) {
|
|
5837
|
+
const gitignorePath = join30(projectRoot, ".gitignore");
|
|
5838
|
+
const existing = existsSync13(gitignorePath) ? readFileSync11(gitignorePath, "utf8") : "";
|
|
5839
|
+
if (existing.includes(PAQAD_MARKER)) {
|
|
5840
|
+
return;
|
|
5841
|
+
}
|
|
5842
|
+
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
5843
|
+
writeFileSync4(gitignorePath, `${existing}${separator}
|
|
5844
|
+
${PAQAD_GITIGNORE_ENTRIES}
|
|
5845
|
+
`);
|
|
5846
|
+
}
|
|
5847
|
+
|
|
5809
5848
|
// src/onboarding/prompts.ts
|
|
5810
5849
|
import { checkbox, select } from "@inquirer/prompts";
|
|
5811
|
-
var ALL_PROVIDERS = ["codex-cli", "claude-code", "gemini-cli", "junie"];
|
|
5812
5850
|
function isInteractive() {
|
|
5813
5851
|
return Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
5814
5852
|
}
|
|
@@ -5842,7 +5880,7 @@ function buildFromOverridesAndDetection(detection, snapshot, overrides) {
|
|
|
5842
5880
|
snapshot
|
|
5843
5881
|
));
|
|
5844
5882
|
return {
|
|
5845
|
-
providers: overrides?.providers ??
|
|
5883
|
+
providers: overrides?.providers ?? ["claude-code"],
|
|
5846
5884
|
domain,
|
|
5847
5885
|
stack_profile: stackProfile,
|
|
5848
5886
|
stack: overrides?.stack ?? (snapshot?.profile ? getPrimaryStack({
|
|
@@ -6233,9 +6271,9 @@ function inferSelectionDomain(detection, overrides, snapshot) {
|
|
|
6233
6271
|
}
|
|
6234
6272
|
|
|
6235
6273
|
// src/onboarding/reference-generator.ts
|
|
6236
|
-
import { existsSync as
|
|
6274
|
+
import { existsSync as existsSync14 } from "fs";
|
|
6237
6275
|
import { readFile as readFile13 } from "fs/promises";
|
|
6238
|
-
import { join as
|
|
6276
|
+
import { join as join31, relative as relative4 } from "path";
|
|
6239
6277
|
import fg3 from "fast-glob";
|
|
6240
6278
|
async function generateReferenceGuides(runtimeRoot, context) {
|
|
6241
6279
|
if (context.domain !== "coding") {
|
|
@@ -6246,8 +6284,8 @@ async function generateReferenceGuides(runtimeRoot, context) {
|
|
|
6246
6284
|
routing: { domain: context.domain },
|
|
6247
6285
|
stack_profile: context.stack_profile
|
|
6248
6286
|
});
|
|
6249
|
-
const referencesRoot =
|
|
6250
|
-
if (!
|
|
6287
|
+
const referencesRoot = join31(runtimeRoot, "capabilities", "coding", "stacks", stack, "references");
|
|
6288
|
+
if (!existsSync14(referencesRoot)) {
|
|
6251
6289
|
return [buildFallbackReferenceGuide(stack)];
|
|
6252
6290
|
}
|
|
6253
6291
|
const entries = await fg3(["tools/*.md", "tools-catalog.md"], {
|
|
@@ -6269,14 +6307,14 @@ async function generateReferenceGuides(runtimeRoot, context) {
|
|
|
6269
6307
|
function toProjectReferencePath(stack, relativePath) {
|
|
6270
6308
|
const normalized = relativePath.replaceAll("\\", "/");
|
|
6271
6309
|
if (normalized === "tools-catalog.md") {
|
|
6272
|
-
return
|
|
6310
|
+
return join31(PATHS.TOOLS_DIR, stack, "README.md");
|
|
6273
6311
|
}
|
|
6274
|
-
return
|
|
6312
|
+
return join31(PATHS.TOOLS_DIR, stack, normalized.replace(/^tools\//, ""));
|
|
6275
6313
|
}
|
|
6276
6314
|
function buildFallbackReferenceGuide(stack) {
|
|
6277
6315
|
const title = stack.split("-").map((segment) => segment.slice(0, 1).toUpperCase() + segment.slice(1)).join(" ");
|
|
6278
6316
|
return {
|
|
6279
|
-
path:
|
|
6317
|
+
path: join31(PATHS.TOOLS_DIR, stack, "README.md"),
|
|
6280
6318
|
autoUpdate: false,
|
|
6281
6319
|
content: [
|
|
6282
6320
|
`# ${title} Tool References`,
|
|
@@ -6294,7 +6332,7 @@ function buildFallbackReferenceGuide(stack) {
|
|
|
6294
6332
|
|
|
6295
6333
|
// src/onboarding/rule-generator.ts
|
|
6296
6334
|
import { readFile as readFile14 } from "fs/promises";
|
|
6297
|
-
import { join as
|
|
6335
|
+
import { join as join32 } from "path";
|
|
6298
6336
|
async function generateProjectRules(rules) {
|
|
6299
6337
|
return Promise.all(
|
|
6300
6338
|
rules.map(async (rule) => ({
|
|
@@ -6307,22 +6345,22 @@ async function generateProjectRules(rules) {
|
|
|
6307
6345
|
function toProjectRulePath(source) {
|
|
6308
6346
|
const normalized = source.replaceAll("\\", "/");
|
|
6309
6347
|
if (normalized.startsWith("base/rules/")) {
|
|
6310
|
-
return
|
|
6348
|
+
return join32(PATHS.RULES_DIR, "_shared", normalized.replace(/^base\/rules\//, ""));
|
|
6311
6349
|
}
|
|
6312
6350
|
if (normalized.startsWith("capabilities/")) {
|
|
6313
6351
|
const capabilityNormalized = normalized.replace(/^capabilities\//, "");
|
|
6314
6352
|
const [prefix2, suffix2] = capabilityNormalized.split("/rules/");
|
|
6315
6353
|
if (prefix2 !== void 0 && suffix2 !== void 0) {
|
|
6316
6354
|
const target2 = suffix2.endsWith("/guide.md") ? suffix2.replace("/guide.md", ".md") : suffix2;
|
|
6317
|
-
return
|
|
6355
|
+
return join32(PATHS.RULES_DIR, prefix2, target2);
|
|
6318
6356
|
}
|
|
6319
6357
|
}
|
|
6320
6358
|
const [prefix, suffix] = normalized.split("/rules/");
|
|
6321
6359
|
if (prefix === void 0 || suffix === void 0) {
|
|
6322
|
-
return
|
|
6360
|
+
return join32(PATHS.RULES_DIR, normalized);
|
|
6323
6361
|
}
|
|
6324
6362
|
const target = suffix.endsWith("/guide.md") ? suffix.replace("/guide.md", ".md") : suffix;
|
|
6325
|
-
return
|
|
6363
|
+
return join32(PATHS.RULES_DIR, prefix, target);
|
|
6326
6364
|
}
|
|
6327
6365
|
|
|
6328
6366
|
// src/onboarding/orchestrator.ts
|
|
@@ -6337,7 +6375,7 @@ var OnboardingOrchestrator = class {
|
|
|
6337
6375
|
const runtimeRoot = options.runtimeRoot ?? getRuntimeRoot();
|
|
6338
6376
|
const resolver = new Resolver({ runtimeRoot });
|
|
6339
6377
|
const resolved = await resolver.resolve(selections);
|
|
6340
|
-
const adapters = options.adapters ?? selections.providers ?? ["claude-code"
|
|
6378
|
+
const adapters = options.adapters ?? selections.providers ?? ["claude-code"];
|
|
6341
6379
|
const profile = buildProjectProfile(selections, liveSnapshot, options.profileOverrides);
|
|
6342
6380
|
const validator = new SchemaValidator();
|
|
6343
6381
|
const validation = validator.validate("project-profile", profile);
|
|
@@ -6366,6 +6404,17 @@ var OnboardingOrchestrator = class {
|
|
|
6366
6404
|
stack_profile: selections.stack_profile
|
|
6367
6405
|
})
|
|
6368
6406
|
);
|
|
6407
|
+
const silentUpdateSrc = join33(runtimeRoot, "..", "hooks", "silent-update.sh");
|
|
6408
|
+
try {
|
|
6409
|
+
const hookContent = readFileSync12(silentUpdateSrc, "utf8");
|
|
6410
|
+
generatedFiles.push({
|
|
6411
|
+
path: PATHS.HOOKS_SILENT_UPDATE,
|
|
6412
|
+
content: hookContent,
|
|
6413
|
+
autoUpdate: true,
|
|
6414
|
+
executable: true
|
|
6415
|
+
});
|
|
6416
|
+
} catch {
|
|
6417
|
+
}
|
|
6369
6418
|
const writeResult = writeGeneratedFiles(options.projectRoot, generatedFiles);
|
|
6370
6419
|
const drift = await writeStackArtifacts(
|
|
6371
6420
|
options.projectRoot,
|
|
@@ -6374,7 +6423,9 @@ var OnboardingOrchestrator = class {
|
|
|
6374
6423
|
{ writeHumanDocs: false }
|
|
6375
6424
|
);
|
|
6376
6425
|
writeProjectProfile2(options.projectRoot, profile);
|
|
6426
|
+
writeGitignore(options.projectRoot);
|
|
6377
6427
|
writeDetectionReport(options.projectRoot, detection);
|
|
6428
|
+
writeFrameworkMetadata(options.projectRoot, VERSION);
|
|
6378
6429
|
bootstrapFramework(options.projectRoot);
|
|
6379
6430
|
const manifestPath = writeOnboardingManifest(options.projectRoot, {
|
|
6380
6431
|
framework_version: VERSION,
|
|
@@ -6648,7 +6699,7 @@ function buildRustCommands(usingCompose) {
|
|
|
6648
6699
|
}
|
|
6649
6700
|
|
|
6650
6701
|
// src/onboarding/scaffold-generator.ts
|
|
6651
|
-
import { join as
|
|
6702
|
+
import { join as join34 } from "path";
|
|
6652
6703
|
|
|
6653
6704
|
// src/templates/registry.ts
|
|
6654
6705
|
import fg4 from "fast-glob";
|
|
@@ -6656,29 +6707,29 @@ import { basename as basename4, relative as relative5 } from "pathe";
|
|
|
6656
6707
|
|
|
6657
6708
|
// src/onboarding/scaffold-generator.ts
|
|
6658
6709
|
var FEATURE_TEMPLATE_TARGETS = [
|
|
6659
|
-
["business.md.hbs",
|
|
6660
|
-
["technical.md.hbs",
|
|
6710
|
+
["business.md.hbs", join34(PATHS.MODULE_FEATURES_DIR, "core", "business.md")],
|
|
6711
|
+
["technical.md.hbs", join34(PATHS.MODULE_FEATURES_DIR, "core", "technical.md")]
|
|
6661
6712
|
];
|
|
6662
6713
|
|
|
6663
6714
|
// src/packs/manager.ts
|
|
6664
6715
|
import {
|
|
6665
6716
|
cpSync,
|
|
6666
|
-
existsSync as
|
|
6717
|
+
existsSync as existsSync15,
|
|
6667
6718
|
mkdirSync as mkdirSync5,
|
|
6668
6719
|
mkdtempSync,
|
|
6669
6720
|
readdirSync as readdirSync3,
|
|
6670
6721
|
rmSync as rmSync2,
|
|
6671
|
-
writeFileSync as
|
|
6722
|
+
writeFileSync as writeFileSync5
|
|
6672
6723
|
} from "fs";
|
|
6673
6724
|
import { homedir as homedir2 } from "os";
|
|
6674
|
-
import { join as
|
|
6725
|
+
import { join as join35, resolve as resolve2 } from "path";
|
|
6675
6726
|
import { execa } from "execa";
|
|
6676
6727
|
var SOURCE_ORDER2 = ["built-in", "global", "project"];
|
|
6677
6728
|
function resolvePackManagerRoots(projectRoot = process.cwd(), overrides = {}) {
|
|
6678
6729
|
return {
|
|
6679
6730
|
runtimeRoot: overrides.runtimeRoot ?? getRuntimeRoot(),
|
|
6680
|
-
globalPacksRoot: overrides.globalPacksRoot ?? process.env.PAQAD_GLOBAL_PACKS_ROOT ??
|
|
6681
|
-
projectPacksRoot: overrides.projectPacksRoot ??
|
|
6731
|
+
globalPacksRoot: overrides.globalPacksRoot ?? process.env.PAQAD_GLOBAL_PACKS_ROOT ?? join35(homedir2(), ".paqad", "packs"),
|
|
6732
|
+
projectPacksRoot: overrides.projectPacksRoot ?? join35(projectRoot, ".paqad", "packs"),
|
|
6682
6733
|
registryUrl: overrides.registryUrl ?? process.env.PAQAD_PACK_REGISTRY_URL
|
|
6683
6734
|
};
|
|
6684
6735
|
}
|
|
@@ -6726,7 +6777,7 @@ async function installPack(source, options = {}) {
|
|
|
6726
6777
|
if (!pack.validation.valid) {
|
|
6727
6778
|
throw new Error(formatValidationIssues(pack.validation.issues));
|
|
6728
6779
|
}
|
|
6729
|
-
const destination =
|
|
6780
|
+
const destination = join35(installRoot, pack.manifest.name);
|
|
6730
6781
|
rmSync2(destination, { recursive: true, force: true });
|
|
6731
6782
|
cpSync(candidateRoot, destination, { recursive: true });
|
|
6732
6783
|
const installed = loader.validatePack(destination, scope === "project" ? "project" : "global");
|
|
@@ -6738,13 +6789,13 @@ async function installPack(source, options = {}) {
|
|
|
6738
6789
|
function removePack(name, projectRoot = process.cwd(), scope = "global", overrides = {}) {
|
|
6739
6790
|
const roots = resolvePackManagerRoots(projectRoot, overrides);
|
|
6740
6791
|
const targetRoot = scope === "project" ? roots.projectPacksRoot : roots.globalPacksRoot;
|
|
6741
|
-
const target =
|
|
6742
|
-
if (
|
|
6792
|
+
const target = join35(targetRoot, name);
|
|
6793
|
+
if (existsSync15(target)) {
|
|
6743
6794
|
rmSync2(target, { recursive: true, force: true });
|
|
6744
6795
|
return;
|
|
6745
6796
|
}
|
|
6746
|
-
const builtInRoot =
|
|
6747
|
-
if (
|
|
6797
|
+
const builtInRoot = join35(roots.runtimeRoot, "capabilities", "coding", "stacks", name);
|
|
6798
|
+
if (existsSync15(builtInRoot)) {
|
|
6748
6799
|
throw new Error(
|
|
6749
6800
|
`Cannot remove built-in pack "${name}"; remove a global or project override instead`
|
|
6750
6801
|
);
|
|
@@ -6761,14 +6812,14 @@ function validatePackAt(path) {
|
|
|
6761
6812
|
function createPack(name, options = {}) {
|
|
6762
6813
|
const destinationRoot = options.destinationRoot ?? process.cwd();
|
|
6763
6814
|
const ecosystem = options.ecosystem ?? "node";
|
|
6764
|
-
const packRoot =
|
|
6765
|
-
if (
|
|
6815
|
+
const packRoot = join35(destinationRoot, name);
|
|
6816
|
+
if (existsSync15(packRoot)) {
|
|
6766
6817
|
throw new Error(`Pack scaffold already exists at ${packRoot}`);
|
|
6767
6818
|
}
|
|
6768
|
-
mkdirSync5(
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6819
|
+
mkdirSync5(join35(packRoot, "rules"), { recursive: true });
|
|
6820
|
+
writeFileSync5(join35(packRoot, "pack.yaml"), renderPackTemplate(name, ecosystem));
|
|
6821
|
+
writeFileSync5(
|
|
6822
|
+
join35(packRoot, "rules", "conventions.md"),
|
|
6772
6823
|
`# ${name}
|
|
6773
6824
|
|
|
6774
6825
|
Document project-specific conventions for the ${name} stack here.
|
|
@@ -6778,14 +6829,14 @@ Document project-specific conventions for the ${name} stack here.
|
|
|
6778
6829
|
}
|
|
6779
6830
|
function listPackNamesForSource(source, roots) {
|
|
6780
6831
|
const sourceRoot = resolveSourceRoot(source, roots);
|
|
6781
|
-
if (!
|
|
6832
|
+
if (!existsSync15(sourceRoot)) {
|
|
6782
6833
|
return [];
|
|
6783
6834
|
}
|
|
6784
6835
|
return readdirSync3(sourceRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
6785
6836
|
}
|
|
6786
6837
|
function resolveSourceRoot(source, roots) {
|
|
6787
6838
|
if (source === "built-in") {
|
|
6788
|
-
return
|
|
6839
|
+
return join35(roots.runtimeRoot, "capabilities", "coding", "stacks");
|
|
6789
6840
|
}
|
|
6790
6841
|
return source === "global" ? roots.globalPacksRoot : roots.projectPacksRoot;
|
|
6791
6842
|
}
|
|
@@ -6805,13 +6856,13 @@ async function materializePackSource(source, roots) {
|
|
|
6805
6856
|
return clonePackSource(buildRegistryPackUrl(roots.registryUrl, source));
|
|
6806
6857
|
}
|
|
6807
6858
|
function looksLikeLocalPath(source) {
|
|
6808
|
-
return source.startsWith(".") || source.startsWith("/") ||
|
|
6859
|
+
return source.startsWith(".") || source.startsWith("/") || existsSync15(resolve2(source));
|
|
6809
6860
|
}
|
|
6810
6861
|
function looksLikeGitUrl(source) {
|
|
6811
6862
|
return source.startsWith("http://") || source.startsWith("https://") || source.startsWith("ssh://") || source.startsWith("git@") || source.startsWith("file://") || source.endsWith(".git");
|
|
6812
6863
|
}
|
|
6813
6864
|
async function clonePackSource(source) {
|
|
6814
|
-
const tempRoot = mkdtempSync(
|
|
6865
|
+
const tempRoot = mkdtempSync(join35(homedir2(), ".paqad-pack-clone-"));
|
|
6815
6866
|
try {
|
|
6816
6867
|
await execa("git", ["clone", "--depth", "1", source, tempRoot]);
|
|
6817
6868
|
} catch (error) {
|
|
@@ -6821,11 +6872,11 @@ async function clonePackSource(source) {
|
|
|
6821
6872
|
return findPackRoot(tempRoot);
|
|
6822
6873
|
}
|
|
6823
6874
|
function findPackRoot(root) {
|
|
6824
|
-
const rootManifest =
|
|
6825
|
-
if (
|
|
6875
|
+
const rootManifest = join35(root, "pack.yaml");
|
|
6876
|
+
if (existsSync15(rootManifest)) {
|
|
6826
6877
|
return root;
|
|
6827
6878
|
}
|
|
6828
|
-
const candidates = readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) =>
|
|
6879
|
+
const candidates = readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join35(root, entry.name)).filter((candidate) => existsSync15(join35(candidate, "pack.yaml")));
|
|
6829
6880
|
if (candidates.length === 1) {
|
|
6830
6881
|
return candidates[0];
|
|
6831
6882
|
}
|
|
@@ -6924,15 +6975,15 @@ async function queryOsv(packages) {
|
|
|
6924
6975
|
}
|
|
6925
6976
|
|
|
6926
6977
|
// src/pentest/progress-tracker.ts
|
|
6927
|
-
import { existsSync as
|
|
6978
|
+
import { existsSync as existsSync17 } from "fs";
|
|
6928
6979
|
import { mkdir as mkdir12, readdir as readdir4, readFile as readFile16, writeFile as writeFile12 } from "fs/promises";
|
|
6929
|
-
import { dirname as dirname17, join as
|
|
6980
|
+
import { dirname as dirname17, join as join37 } from "path";
|
|
6930
6981
|
|
|
6931
6982
|
// src/pentest/shared.ts
|
|
6932
6983
|
import { createHash as createHash7 } from "crypto";
|
|
6933
|
-
import { existsSync as
|
|
6984
|
+
import { existsSync as existsSync16 } from "fs";
|
|
6934
6985
|
import { mkdir as mkdir11, readFile as readFile15, readdir as readdir3, writeFile as writeFile11 } from "fs/promises";
|
|
6935
|
-
import { basename as basename5, dirname as dirname16, join as
|
|
6986
|
+
import { basename as basename5, dirname as dirname16, join as join36, relative as relative6 } from "path";
|
|
6936
6987
|
import { execa as execa2 } from "execa";
|
|
6937
6988
|
import fg5 from "fast-glob";
|
|
6938
6989
|
function toLocalTimestamp(date) {
|
|
@@ -6950,7 +7001,7 @@ async function writeJson(target, data) {
|
|
|
6950
7001
|
`);
|
|
6951
7002
|
}
|
|
6952
7003
|
async function readJsonIfExists(target) {
|
|
6953
|
-
if (!
|
|
7004
|
+
if (!existsSync16(target)) {
|
|
6954
7005
|
return null;
|
|
6955
7006
|
}
|
|
6956
7007
|
return JSON.parse(await readFile15(target, "utf8"));
|
|
@@ -6996,8 +7047,8 @@ async function discoverTargetUrl(projectRoot, stack, explicit) {
|
|
|
6996
7047
|
}
|
|
6997
7048
|
const envFiles = [".env", ".env.local", ".env.example"];
|
|
6998
7049
|
for (const envFile of envFiles) {
|
|
6999
|
-
const path =
|
|
7000
|
-
if (!
|
|
7050
|
+
const path = join36(projectRoot, envFile);
|
|
7051
|
+
if (!existsSync16(path)) {
|
|
7001
7052
|
continue;
|
|
7002
7053
|
}
|
|
7003
7054
|
const content = await readFile15(path, "utf8");
|
|
@@ -7018,11 +7069,11 @@ async function discoverTargetUrl(projectRoot, stack, explicit) {
|
|
|
7018
7069
|
return null;
|
|
7019
7070
|
}
|
|
7020
7071
|
async function runProjectScript(projectRoot, scriptName, env, logDir) {
|
|
7021
|
-
const scriptPath =
|
|
7022
|
-
const stdoutPath =
|
|
7023
|
-
const stderrPath =
|
|
7072
|
+
const scriptPath = join36(projectRoot, PATHS.SCRIPTS_DIR, scriptName);
|
|
7073
|
+
const stdoutPath = join36(logDir, `${scriptName}.stdout.log`);
|
|
7074
|
+
const stderrPath = join36(logDir, `${scriptName}.stderr.log`);
|
|
7024
7075
|
await mkdir11(logDir, { recursive: true });
|
|
7025
|
-
if (!
|
|
7076
|
+
if (!existsSync16(scriptPath)) {
|
|
7026
7077
|
await writeFile11(stdoutPath, "");
|
|
7027
7078
|
await writeFile11(stderrPath, `Missing script: ${relative6(projectRoot, scriptPath)}
|
|
7028
7079
|
`);
|
|
@@ -7050,8 +7101,8 @@ async function runProjectScript(projectRoot, scriptName, env, logDir) {
|
|
|
7050
7101
|
};
|
|
7051
7102
|
}
|
|
7052
7103
|
async function loadModuleDocs(projectRoot, focusModules = []) {
|
|
7053
|
-
const moduleRoot =
|
|
7054
|
-
if (!
|
|
7104
|
+
const moduleRoot = join36(projectRoot, PATHS.MODULES_DIR);
|
|
7105
|
+
if (!existsSync16(moduleRoot)) {
|
|
7055
7106
|
return [];
|
|
7056
7107
|
}
|
|
7057
7108
|
const dirs = (await readdir3(moduleRoot, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((moduleName) => focusModules.length === 0 || focusModules.includes(moduleName)).sort();
|
|
@@ -7119,7 +7170,7 @@ function inferSourceArtifacts(baseDir, scriptResults) {
|
|
|
7119
7170
|
return [
|
|
7120
7171
|
...new Set(
|
|
7121
7172
|
artifacts.map(
|
|
7122
|
-
(artifact) => artifact.startsWith(".") ? artifact : relative6(dirname16(baseDir),
|
|
7173
|
+
(artifact) => artifact.startsWith(".") ? artifact : relative6(dirname16(baseDir), join36(dirname16(baseDir), artifact))
|
|
7123
7174
|
)
|
|
7124
7175
|
)
|
|
7125
7176
|
];
|
|
@@ -7193,8 +7244,8 @@ var PentestProgressTracker = class {
|
|
|
7193
7244
|
};
|
|
7194
7245
|
}
|
|
7195
7246
|
async load(projectRoot, runId) {
|
|
7196
|
-
const target =
|
|
7197
|
-
if (!
|
|
7247
|
+
const target = join37(projectRoot, PATHS.PENTEST_RUNS_DIR, runId, "progress.json");
|
|
7248
|
+
if (!existsSync17(target)) {
|
|
7198
7249
|
return null;
|
|
7199
7250
|
}
|
|
7200
7251
|
const parsed = JSON.parse(await readFile16(target, "utf8"));
|
|
@@ -7205,7 +7256,7 @@ var PentestProgressTracker = class {
|
|
|
7205
7256
|
return parsed;
|
|
7206
7257
|
}
|
|
7207
7258
|
async save(projectRoot, progress) {
|
|
7208
|
-
const target =
|
|
7259
|
+
const target = join37(projectRoot, PATHS.PENTEST_RUNS_DIR, progress.run_id, "progress.json");
|
|
7209
7260
|
progress.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
7210
7261
|
await mkdir12(dirname17(target), { recursive: true });
|
|
7211
7262
|
await writeFile12(target, `${JSON.stringify(progress, null, 2)}
|
|
@@ -7264,8 +7315,8 @@ var PentestProgressTracker = class {
|
|
|
7264
7315
|
return step.status === "completed" && step.input_hash === inputHash;
|
|
7265
7316
|
}
|
|
7266
7317
|
async findIncompleteRun(projectRoot, workflow, sourceReportPath) {
|
|
7267
|
-
const runsDir =
|
|
7268
|
-
if (!
|
|
7318
|
+
const runsDir = join37(projectRoot, PATHS.PENTEST_RUNS_DIR);
|
|
7319
|
+
if (!existsSync17(runsDir)) {
|
|
7269
7320
|
return null;
|
|
7270
7321
|
}
|
|
7271
7322
|
const entries = (await readdir4(runsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort().reverse();
|
|
@@ -7307,15 +7358,15 @@ var PentestProgressTracker = class {
|
|
|
7307
7358
|
}
|
|
7308
7359
|
};
|
|
7309
7360
|
function runArtifactsDir(projectRoot, runId) {
|
|
7310
|
-
return
|
|
7361
|
+
return join37(projectRoot, PATHS.PENTEST_RUNS_DIR, runId, "artifacts");
|
|
7311
7362
|
}
|
|
7312
7363
|
function runLogsDir(projectRoot, runId) {
|
|
7313
|
-
return
|
|
7364
|
+
return join37(projectRoot, PATHS.PENTEST_RUNS_DIR, runId, "logs");
|
|
7314
7365
|
}
|
|
7315
7366
|
|
|
7316
7367
|
// src/pipeline/lane-runner.ts
|
|
7317
7368
|
import { mkdir as mkdir16, writeFile as writeFile16 } from "fs/promises";
|
|
7318
|
-
import { dirname as dirname20, join as
|
|
7369
|
+
import { dirname as dirname20, join as join43 } from "path";
|
|
7319
7370
|
|
|
7320
7371
|
// src/pipeline/phases/shared.ts
|
|
7321
7372
|
function createPassResult(phase, summary, context, artifacts = [`handoff:${context.phases.length + 1}`]) {
|
|
@@ -7435,12 +7486,12 @@ var LoadDocsPhase = class {
|
|
|
7435
7486
|
|
|
7436
7487
|
// src/workflows/pentest.ts
|
|
7437
7488
|
import { mkdir as mkdir13, writeFile as writeFile13 } from "fs/promises";
|
|
7438
|
-
import { join as
|
|
7489
|
+
import { join as join40, relative as relative7 } from "path";
|
|
7439
7490
|
|
|
7440
7491
|
// src/pentest/findings.ts
|
|
7441
|
-
import { existsSync as
|
|
7492
|
+
import { existsSync as existsSync18 } from "fs";
|
|
7442
7493
|
import { readFile as readFile17 } from "fs/promises";
|
|
7443
|
-
import { join as
|
|
7494
|
+
import { join as join38 } from "path";
|
|
7444
7495
|
function createFinding(input) {
|
|
7445
7496
|
return {
|
|
7446
7497
|
id: "",
|
|
@@ -7610,12 +7661,265 @@ function buildModuleFindings(docs, tests) {
|
|
|
7610
7661
|
);
|
|
7611
7662
|
}
|
|
7612
7663
|
}
|
|
7664
|
+
const authMechanismTests = extractRelevantTestPaths(tests, moduleDoc.module, [
|
|
7665
|
+
"algorithm",
|
|
7666
|
+
"expir",
|
|
7667
|
+
"signature",
|
|
7668
|
+
"refresh",
|
|
7669
|
+
"revok",
|
|
7670
|
+
"logout",
|
|
7671
|
+
"session.invalidat",
|
|
7672
|
+
"alg:none",
|
|
7673
|
+
"jwt"
|
|
7674
|
+
]);
|
|
7675
|
+
if (hasAtLeast(lowered, 2, [
|
|
7676
|
+
"jwt",
|
|
7677
|
+
"token",
|
|
7678
|
+
"bearer",
|
|
7679
|
+
"session",
|
|
7680
|
+
"cookie",
|
|
7681
|
+
"oauth",
|
|
7682
|
+
"saml",
|
|
7683
|
+
"oidc",
|
|
7684
|
+
"sanctum"
|
|
7685
|
+
])) {
|
|
7686
|
+
if (authMechanismTests.length === 0) {
|
|
7687
|
+
findings.push(
|
|
7688
|
+
createFinding({
|
|
7689
|
+
title: `Auth token lifecycle checks are thin for ${moduleDoc.module}`,
|
|
7690
|
+
description: "Module docs describe authentication tokens, sessions, or OAuth flows, but the current tests do not show evidence of token expiry, signature validation, session invalidation, or logout handling.",
|
|
7691
|
+
impact: "high",
|
|
7692
|
+
effort: "medium",
|
|
7693
|
+
possible_solution_direction: "Add tests for token expiry enforcement, alg:none rejection, session invalidation on logout, and refresh token rotation for the documented auth flows.",
|
|
7694
|
+
how_to_reproduce: [
|
|
7695
|
+
`Review ${moduleDoc.paths.join(", ")} for token or session handling.`,
|
|
7696
|
+
"Search tests for algorithm, expiry, signature, revocation, and logout coverage for that module."
|
|
7697
|
+
],
|
|
7698
|
+
impact_area: [...moduleDoc.paths, `module:${moduleDoc.module}`],
|
|
7699
|
+
evidence: [
|
|
7700
|
+
...moduleDoc.paths.map((path) => `Doc evidence: ${path}`),
|
|
7701
|
+
"No matching auth token lifecycle test evidence found for this module."
|
|
7702
|
+
],
|
|
7703
|
+
category: "auth-mechanism",
|
|
7704
|
+
status: "open",
|
|
7705
|
+
confidence: 0.71,
|
|
7706
|
+
source_types: ["docs", "tests"],
|
|
7707
|
+
affected_modules: [moduleDoc.module],
|
|
7708
|
+
affected_packages: [],
|
|
7709
|
+
runtime_required: false,
|
|
7710
|
+
manual_follow_up: true
|
|
7711
|
+
})
|
|
7712
|
+
);
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7715
|
+
const ssrfTests = extractRelevantTestPaths(tests, moduleDoc.module, [
|
|
7716
|
+
"ssrf",
|
|
7717
|
+
"redirect",
|
|
7718
|
+
"allowlist",
|
|
7719
|
+
"block",
|
|
7720
|
+
"internal",
|
|
7721
|
+
"private",
|
|
7722
|
+
"localhost",
|
|
7723
|
+
"metadata"
|
|
7724
|
+
]);
|
|
7725
|
+
if (hasAtLeast(lowered, 2, [
|
|
7726
|
+
"url",
|
|
7727
|
+
"redirect",
|
|
7728
|
+
"callback",
|
|
7729
|
+
"webhook",
|
|
7730
|
+
"fetch",
|
|
7731
|
+
"http",
|
|
7732
|
+
"proxy",
|
|
7733
|
+
"curl",
|
|
7734
|
+
"axios"
|
|
7735
|
+
])) {
|
|
7736
|
+
if (ssrfTests.length === 0) {
|
|
7737
|
+
findings.push(
|
|
7738
|
+
createFinding({
|
|
7739
|
+
title: `Outbound request / redirect surface lacks abuse validation for ${moduleDoc.module}`,
|
|
7740
|
+
description: "Module docs describe outbound HTTP calls, URL parameters, webhooks, or redirect flows, but the current tests do not show evidence of SSRF prevention, redirect allowlist validation, or private-range blocking.",
|
|
7741
|
+
impact: "high",
|
|
7742
|
+
effort: "medium",
|
|
7743
|
+
possible_solution_direction: "Add tests that verify SSRF prevention (blocked private ranges, redirect chain validation) and open-redirect protection for the documented outbound request surfaces.",
|
|
7744
|
+
how_to_reproduce: [
|
|
7745
|
+
`Review ${moduleDoc.paths.join(", ")} for URL, redirect, or webhook parameter handling.`,
|
|
7746
|
+
"Test with SSRF payloads: 127.0.0.1, 169.254.169.254, [::1], redirect chain to internal IP."
|
|
7747
|
+
],
|
|
7748
|
+
impact_area: [...moduleDoc.paths, `module:${moduleDoc.module}`],
|
|
7749
|
+
evidence: [
|
|
7750
|
+
...moduleDoc.paths.map((path) => `Doc evidence: ${path}`),
|
|
7751
|
+
"No matching SSRF or redirect validation test evidence found for this module."
|
|
7752
|
+
],
|
|
7753
|
+
category: "input-validation",
|
|
7754
|
+
status: "open",
|
|
7755
|
+
confidence: 0.7,
|
|
7756
|
+
source_types: ["docs", "tests"],
|
|
7757
|
+
affected_modules: [moduleDoc.module],
|
|
7758
|
+
affected_packages: [],
|
|
7759
|
+
runtime_required: false,
|
|
7760
|
+
manual_follow_up: true
|
|
7761
|
+
})
|
|
7762
|
+
);
|
|
7763
|
+
}
|
|
7764
|
+
}
|
|
7765
|
+
const massAssignTests = extractRelevantTestPaths(tests, moduleDoc.module, [
|
|
7766
|
+
"mass",
|
|
7767
|
+
"assign",
|
|
7768
|
+
"fillable",
|
|
7769
|
+
"guarded",
|
|
7770
|
+
"allowlist",
|
|
7771
|
+
"forbidden",
|
|
7772
|
+
"attr_protected",
|
|
7773
|
+
"protected"
|
|
7774
|
+
]);
|
|
7775
|
+
if (hasAtLeast(lowered, 2, [
|
|
7776
|
+
"fill",
|
|
7777
|
+
"create",
|
|
7778
|
+
"update",
|
|
7779
|
+
"patch",
|
|
7780
|
+
"assign",
|
|
7781
|
+
"body",
|
|
7782
|
+
"request",
|
|
7783
|
+
"fillable",
|
|
7784
|
+
"guarded"
|
|
7785
|
+
])) {
|
|
7786
|
+
if (massAssignTests.length === 0) {
|
|
7787
|
+
findings.push(
|
|
7788
|
+
createFinding({
|
|
7789
|
+
title: `Mass assignment protection evidence is absent for ${moduleDoc.module}`,
|
|
7790
|
+
description: "Module docs describe model creation or update flows accepting request body fields, but the current tests do not show evidence of mass assignment protection (fillable allowlist, guarded fields, or forbidden-field rejection).",
|
|
7791
|
+
impact: "medium",
|
|
7792
|
+
effort: "low",
|
|
7793
|
+
possible_solution_direction: "Verify the model uses an explicit $fillable allowlist or $guarded blocklist. Add tests that confirm unintended fields (is_admin, role, price, balance) are rejected when submitted in the request body.",
|
|
7794
|
+
how_to_reproduce: [
|
|
7795
|
+
`Review ${moduleDoc.paths.join(", ")} for model create/update operations.`,
|
|
7796
|
+
"Submit a PATCH/POST request with extra privileged fields: is_admin=true, role=admin, price=0."
|
|
7797
|
+
],
|
|
7798
|
+
impact_area: [...moduleDoc.paths, `module:${moduleDoc.module}`],
|
|
7799
|
+
evidence: [
|
|
7800
|
+
...moduleDoc.paths.map((path) => `Doc evidence: ${path}`),
|
|
7801
|
+
"No matching mass assignment protection test evidence found for this module."
|
|
7802
|
+
],
|
|
7803
|
+
category: "input-validation",
|
|
7804
|
+
status: "open",
|
|
7805
|
+
confidence: 0.68,
|
|
7806
|
+
source_types: ["docs", "tests"],
|
|
7807
|
+
affected_modules: [moduleDoc.module],
|
|
7808
|
+
affected_packages: [],
|
|
7809
|
+
runtime_required: false,
|
|
7810
|
+
manual_follow_up: false
|
|
7811
|
+
})
|
|
7812
|
+
);
|
|
7813
|
+
}
|
|
7814
|
+
}
|
|
7815
|
+
const cryptoTests = extractRelevantTestPaths(tests, moduleDoc.module, [
|
|
7816
|
+
"crypto",
|
|
7817
|
+
"encrypt",
|
|
7818
|
+
"hash.strength",
|
|
7819
|
+
"key.rotation",
|
|
7820
|
+
"salt",
|
|
7821
|
+
"entropy",
|
|
7822
|
+
"randomBytes",
|
|
7823
|
+
"bcrypt",
|
|
7824
|
+
"argon"
|
|
7825
|
+
]);
|
|
7826
|
+
if (hasAtLeast(lowered, 2, [
|
|
7827
|
+
"encrypt",
|
|
7828
|
+
"decrypt",
|
|
7829
|
+
"hash",
|
|
7830
|
+
"md5",
|
|
7831
|
+
"sha1",
|
|
7832
|
+
"secret",
|
|
7833
|
+
"key",
|
|
7834
|
+
"cipher",
|
|
7835
|
+
"aes",
|
|
7836
|
+
"rsa",
|
|
7837
|
+
"hmac"
|
|
7838
|
+
])) {
|
|
7839
|
+
if (cryptoTests.length === 0) {
|
|
7840
|
+
findings.push(
|
|
7841
|
+
createFinding({
|
|
7842
|
+
title: `Cryptographic implementation lacks verification coverage for ${moduleDoc.module}`,
|
|
7843
|
+
description: "Module docs describe encryption, hashing, or key management, but the current tests do not show evidence of cryptographic strength validation (algorithm checks, key rotation, salt uniqueness, or secure PRNG usage).",
|
|
7844
|
+
impact: "high",
|
|
7845
|
+
effort: "medium",
|
|
7846
|
+
possible_solution_direction: "Add tests that verify password hashing uses bcrypt/argon2 with a sufficient cost factor, encryption uses a secure algorithm with a randomly generated IV, and tokens are generated with a cryptographically secure PRNG.",
|
|
7847
|
+
how_to_reproduce: [
|
|
7848
|
+
`Review ${moduleDoc.paths.join(", ")} for cryptographic operations.`,
|
|
7849
|
+
"Check for MD5/SHA1 password hashing, hardcoded IVs, or Math.random() for token generation."
|
|
7850
|
+
],
|
|
7851
|
+
impact_area: [...moduleDoc.paths, `module:${moduleDoc.module}`],
|
|
7852
|
+
evidence: [
|
|
7853
|
+
...moduleDoc.paths.map((path) => `Doc evidence: ${path}`),
|
|
7854
|
+
"No matching cryptographic strength or key management test evidence found for this module."
|
|
7855
|
+
],
|
|
7856
|
+
category: "cryptographic",
|
|
7857
|
+
status: "open",
|
|
7858
|
+
confidence: 0.67,
|
|
7859
|
+
source_types: ["docs", "tests"],
|
|
7860
|
+
affected_modules: [moduleDoc.module],
|
|
7861
|
+
affected_packages: [],
|
|
7862
|
+
runtime_required: false,
|
|
7863
|
+
manual_follow_up: true
|
|
7864
|
+
})
|
|
7865
|
+
);
|
|
7866
|
+
}
|
|
7867
|
+
}
|
|
7868
|
+
const loggingTests = extractRelevantTestPaths(tests, moduleDoc.module, [
|
|
7869
|
+
"audit",
|
|
7870
|
+
"log.assert",
|
|
7871
|
+
"event.dispatch",
|
|
7872
|
+
"trail",
|
|
7873
|
+
"activity",
|
|
7874
|
+
"log.entry",
|
|
7875
|
+
"logged",
|
|
7876
|
+
"event"
|
|
7877
|
+
]);
|
|
7878
|
+
if (hasAtLeast(lowered, 2, [
|
|
7879
|
+
"log",
|
|
7880
|
+
"audit",
|
|
7881
|
+
"event",
|
|
7882
|
+
"track",
|
|
7883
|
+
"monitor",
|
|
7884
|
+
"alert",
|
|
7885
|
+
"sentry",
|
|
7886
|
+
"activity"
|
|
7887
|
+
])) {
|
|
7888
|
+
if (loggingTests.length === 0) {
|
|
7889
|
+
findings.push(
|
|
7890
|
+
createFinding({
|
|
7891
|
+
title: `Security-relevant actions lack audit logging evidence for ${moduleDoc.module}`,
|
|
7892
|
+
description: "Module docs describe operations that should be audited (authentication, financial transactions, admin actions, or data exports), but the current tests do not show evidence of audit log assertions or event dispatch verification.",
|
|
7893
|
+
impact: "medium",
|
|
7894
|
+
effort: "medium",
|
|
7895
|
+
possible_solution_direction: "Add tests that assert audit log entries are written for high-value actions with actor identity, IP, and timestamp. Verify sensitive data (passwords, tokens) is not included in log output.",
|
|
7896
|
+
how_to_reproduce: [
|
|
7897
|
+
`Review ${moduleDoc.paths.join(", ")} for operations that must be auditable.`,
|
|
7898
|
+
"Check logging configuration and assert that log entries are created with sufficient context for forensic analysis."
|
|
7899
|
+
],
|
|
7900
|
+
impact_area: [...moduleDoc.paths, `module:${moduleDoc.module}`],
|
|
7901
|
+
evidence: [
|
|
7902
|
+
...moduleDoc.paths.map((path) => `Doc evidence: ${path}`),
|
|
7903
|
+
"No matching audit logging or event dispatch test evidence found for this module."
|
|
7904
|
+
],
|
|
7905
|
+
category: "logging-monitoring",
|
|
7906
|
+
status: "open",
|
|
7907
|
+
confidence: 0.65,
|
|
7908
|
+
source_types: ["docs", "tests"],
|
|
7909
|
+
affected_modules: [moduleDoc.module],
|
|
7910
|
+
affected_packages: [],
|
|
7911
|
+
runtime_required: false,
|
|
7912
|
+
manual_follow_up: false
|
|
7913
|
+
})
|
|
7914
|
+
);
|
|
7915
|
+
}
|
|
7916
|
+
}
|
|
7613
7917
|
}
|
|
7614
7918
|
return findings;
|
|
7615
7919
|
}
|
|
7616
7920
|
async function buildSecretFindings(artifactDir) {
|
|
7617
|
-
const path =
|
|
7618
|
-
if (!
|
|
7921
|
+
const path = join38(artifactDir, "secrets", "matches.txt");
|
|
7922
|
+
if (!existsSync18(path)) {
|
|
7619
7923
|
return [];
|
|
7620
7924
|
}
|
|
7621
7925
|
const matches = parseSecretMatches(await readFile17(path, "utf8"));
|
|
@@ -7715,6 +8019,9 @@ function evaluateRetestStatus(sourceFinding, currentFindings, runtimeStatus, blo
|
|
|
7715
8019
|
function hasAny(content, tokens) {
|
|
7716
8020
|
return tokens.some((token) => content.includes(token));
|
|
7717
8021
|
}
|
|
8022
|
+
function hasAtLeast(content, count, tokens) {
|
|
8023
|
+
return tokens.filter((token) => content.includes(token)).length >= count;
|
|
8024
|
+
}
|
|
7718
8025
|
function normalizeImpact(content) {
|
|
7719
8026
|
const lowered = content.toLowerCase();
|
|
7720
8027
|
if (lowered.includes("critical") || lowered.includes("remote code execution") || lowered.includes("credential") || lowered.includes("auth bypass")) {
|
|
@@ -7727,8 +8034,8 @@ function normalizeImpact(content) {
|
|
|
7727
8034
|
}
|
|
7728
8035
|
async function readNativeAuditFindings(artifactDir) {
|
|
7729
8036
|
const findings = [];
|
|
7730
|
-
const dependencyDir =
|
|
7731
|
-
const npmAudit = await readJsonMaybe(
|
|
8037
|
+
const dependencyDir = join38(artifactDir, "dependencies");
|
|
8038
|
+
const npmAudit = await readJsonMaybe(join38(dependencyDir, "npm-audit.json"));
|
|
7732
8039
|
for (const [name, vulnerability] of Object.entries(npmAudit?.vulnerabilities ?? {})) {
|
|
7733
8040
|
const via = (vulnerability.via ?? []).find((entry) => typeof entry === "object");
|
|
7734
8041
|
findings.push({
|
|
@@ -7740,7 +8047,7 @@ async function readNativeAuditFindings(artifactDir) {
|
|
|
7740
8047
|
details: via?.url ?? ""
|
|
7741
8048
|
});
|
|
7742
8049
|
}
|
|
7743
|
-
const pnpmAudit = await readJsonMaybe(
|
|
8050
|
+
const pnpmAudit = await readJsonMaybe(join38(dependencyDir, "pnpm-audit.json"));
|
|
7744
8051
|
for (const advisory of Object.values(pnpmAudit?.advisories ?? {})) {
|
|
7745
8052
|
findings.push({
|
|
7746
8053
|
package_name: advisory.module_name ?? "unknown",
|
|
@@ -7751,7 +8058,7 @@ async function readNativeAuditFindings(artifactDir) {
|
|
|
7751
8058
|
details: advisory.overview ?? ""
|
|
7752
8059
|
});
|
|
7753
8060
|
}
|
|
7754
|
-
const composerAudit = await readJsonMaybe(
|
|
8061
|
+
const composerAudit = await readJsonMaybe(join38(dependencyDir, "composer-audit.json"));
|
|
7755
8062
|
if (Array.isArray(composerAudit?.advisories)) {
|
|
7756
8063
|
for (const advisory of composerAudit.advisories) {
|
|
7757
8064
|
findings.push({
|
|
@@ -7788,7 +8095,7 @@ async function readNativeAuditFindings(artifactDir) {
|
|
|
7788
8095
|
});
|
|
7789
8096
|
}
|
|
7790
8097
|
async function readJsonMaybe(path) {
|
|
7791
|
-
if (!
|
|
8098
|
+
if (!existsSync18(path)) {
|
|
7792
8099
|
return null;
|
|
7793
8100
|
}
|
|
7794
8101
|
try {
|
|
@@ -7800,10 +8107,33 @@ async function readJsonMaybe(path) {
|
|
|
7800
8107
|
|
|
7801
8108
|
// src/pentest/file-check-mapper.ts
|
|
7802
8109
|
var GENERIC_SECURITY_MAP = [
|
|
7803
|
-
{
|
|
8110
|
+
{
|
|
8111
|
+
glob: "**/auth*",
|
|
8112
|
+
checks: ["permission-boundary-review", "runtime-surface-probing", "auth-mechanism-review"]
|
|
8113
|
+
},
|
|
7804
8114
|
{ glob: "**/guard*", checks: ["permission-boundary-review"] },
|
|
7805
|
-
{ glob: "**/.env*", checks: ["runtime-surface-probing"] },
|
|
7806
|
-
{ glob: "**/config*", checks: ["runtime-surface-probing"] }
|
|
8115
|
+
{ glob: "**/.env*", checks: ["runtime-surface-probing", "cryptographic-review"] },
|
|
8116
|
+
{ glob: "**/config*", checks: ["runtime-surface-probing"] },
|
|
8117
|
+
// Auth / session files
|
|
8118
|
+
{ glob: "**/jwt*", checks: ["auth-mechanism-review"] },
|
|
8119
|
+
{ glob: "**/token*", checks: ["auth-mechanism-review"] },
|
|
8120
|
+
{ glob: "**/passport*", checks: ["auth-mechanism-review"] },
|
|
8121
|
+
{ glob: "**/session*", checks: ["auth-mechanism-review"] },
|
|
8122
|
+
// GraphQL schema and resolvers
|
|
8123
|
+
{ glob: "**/*.graphql", checks: ["input-validation-review", "permission-boundary-review"] },
|
|
8124
|
+
{ glob: "**/schema.graphql", checks: ["input-validation-review", "permission-boundary-review"] },
|
|
8125
|
+
{ glob: "**/resolvers/**", checks: ["input-validation-review", "permission-boundary-review"] },
|
|
8126
|
+
// Logging and audit
|
|
8127
|
+
{ glob: "**/log*", checks: ["logging-monitoring-review"] },
|
|
8128
|
+
{ glob: "**/audit*", checks: ["logging-monitoring-review"] },
|
|
8129
|
+
{ glob: "**/monitor*", checks: ["logging-monitoring-review"] },
|
|
8130
|
+
// Cryptography
|
|
8131
|
+
{ glob: "**/*crypt*", checks: ["cryptographic-review"] },
|
|
8132
|
+
{ glob: "**/*cipher*", checks: ["cryptographic-review"] },
|
|
8133
|
+
{ glob: "**/*hash*", checks: ["cryptographic-review"] },
|
|
8134
|
+
// File uploads and input
|
|
8135
|
+
{ glob: "**/upload*", checks: ["input-validation-review"] },
|
|
8136
|
+
{ glob: "**/file*", checks: ["input-validation-review"] }
|
|
7807
8137
|
];
|
|
7808
8138
|
var FileCheckMapper = class {
|
|
7809
8139
|
constructor(frameworks, projectRoot) {
|
|
@@ -7864,7 +8194,7 @@ var FileCheckMapper = class {
|
|
|
7864
8194
|
|
|
7865
8195
|
// src/pentest/incremental-scanner.ts
|
|
7866
8196
|
import { readFile as readFile18 } from "fs/promises";
|
|
7867
|
-
import { join as
|
|
8197
|
+
import { join as join39 } from "path";
|
|
7868
8198
|
import { createHash as createHash8 } from "crypto";
|
|
7869
8199
|
import { execa as execa3 } from "execa";
|
|
7870
8200
|
var IncrementalScanner = class {
|
|
@@ -7898,7 +8228,7 @@ var IncrementalScanner = class {
|
|
|
7898
8228
|
}
|
|
7899
8229
|
}
|
|
7900
8230
|
async gitDiff(projectRoot, lastRunId) {
|
|
7901
|
-
const progressPath =
|
|
8231
|
+
const progressPath = join39(projectRoot, ".paqad", "pentest", "runs", lastRunId, "progress.json");
|
|
7902
8232
|
let baseCommit;
|
|
7903
8233
|
try {
|
|
7904
8234
|
const raw = await readFile18(progressPath, "utf8");
|
|
@@ -7915,7 +8245,7 @@ var IncrementalScanner = class {
|
|
|
7915
8245
|
return result.stdout.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
7916
8246
|
}
|
|
7917
8247
|
async hashDiff(projectRoot, lastRunId) {
|
|
7918
|
-
const manifestPath =
|
|
8248
|
+
const manifestPath = join39(projectRoot, ".paqad", "pentest", "runs", lastRunId, "progress.json");
|
|
7919
8249
|
let fileManifest = {};
|
|
7920
8250
|
try {
|
|
7921
8251
|
const raw = await readFile18(manifestPath, "utf8");
|
|
@@ -7927,7 +8257,7 @@ var IncrementalScanner = class {
|
|
|
7927
8257
|
const changed = [];
|
|
7928
8258
|
for (const [filePath, storedHash] of Object.entries(fileManifest)) {
|
|
7929
8259
|
try {
|
|
7930
|
-
const content = await readFile18(
|
|
8260
|
+
const content = await readFile18(join39(projectRoot, filePath), "utf8");
|
|
7931
8261
|
const currentHash = createHash8("sha256").update(content).digest("hex");
|
|
7932
8262
|
if (currentHash !== storedHash) {
|
|
7933
8263
|
changed.push(filePath);
|
|
@@ -7940,11 +8270,11 @@ var IncrementalScanner = class {
|
|
|
7940
8270
|
}
|
|
7941
8271
|
async warnIfFullScanStale(projectRoot, thresholdDays) {
|
|
7942
8272
|
try {
|
|
7943
|
-
const runsDir =
|
|
8273
|
+
const runsDir = join39(projectRoot, ".paqad", "pentest", "runs");
|
|
7944
8274
|
const { readdir: readdir8 } = await import("fs/promises");
|
|
7945
8275
|
const runs = await readdir8(runsDir).catch(() => []);
|
|
7946
8276
|
for (const run of runs.sort().reverse()) {
|
|
7947
|
-
const progressPath =
|
|
8277
|
+
const progressPath = join39(runsDir, run, "progress.json");
|
|
7948
8278
|
try {
|
|
7949
8279
|
const raw = await readFile18(progressPath, "utf8");
|
|
7950
8280
|
const progress = JSON.parse(raw);
|
|
@@ -8142,8 +8472,8 @@ var PentestWorkflow = class {
|
|
|
8142
8472
|
await this.tracker.save(options.projectRoot, progress);
|
|
8143
8473
|
const docs = await loadModuleDocs(options.projectRoot, focusModules);
|
|
8144
8474
|
const tests = await loadTests(options.projectRoot, focusModules);
|
|
8145
|
-
const docsPath =
|
|
8146
|
-
const testsPath =
|
|
8475
|
+
const docsPath = join40(artifactsDir, "docs-summary.json");
|
|
8476
|
+
const testsPath = join40(artifactsDir, "tests-summary.json");
|
|
8147
8477
|
await writeJson(docsPath, docs);
|
|
8148
8478
|
await writeJson(
|
|
8149
8479
|
testsPath,
|
|
@@ -8244,7 +8574,7 @@ var PentestWorkflow = class {
|
|
|
8244
8574
|
progressRunId: progress.run_id,
|
|
8245
8575
|
reportTimestamp: new Date(progress.started_at)
|
|
8246
8576
|
});
|
|
8247
|
-
const findingIndexPath =
|
|
8577
|
+
const findingIndexPath = join40(
|
|
8248
8578
|
options.projectRoot,
|
|
8249
8579
|
PATHS.PENTEST_RUNS_DIR,
|
|
8250
8580
|
progress.run_id,
|
|
@@ -8261,7 +8591,7 @@ var PentestWorkflow = class {
|
|
|
8261
8591
|
}
|
|
8262
8592
|
if (report === null) {
|
|
8263
8593
|
const existingSidecar = progress.sidecar_path ? await readJsonIfExists(
|
|
8264
|
-
|
|
8594
|
+
join40(options.projectRoot, progress.sidecar_path)
|
|
8265
8595
|
) : null;
|
|
8266
8596
|
report = existingSidecar;
|
|
8267
8597
|
}
|
|
@@ -8276,18 +8606,18 @@ var PentestWorkflow = class {
|
|
|
8276
8606
|
this.tracker.markStepRunning(progress, "write-report", writeHash);
|
|
8277
8607
|
await this.tracker.save(options.projectRoot, progress);
|
|
8278
8608
|
await writeJson(
|
|
8279
|
-
|
|
8609
|
+
join40(options.projectRoot, PATHS.PENTEST_RUNS_DIR, progress.run_id, "report-preview.json"),
|
|
8280
8610
|
{ report_id: report.report_id, findings: report.findings.length }
|
|
8281
8611
|
);
|
|
8282
|
-
await writeJson(
|
|
8612
|
+
await writeJson(join40(options.projectRoot, report.sidecar_path), report);
|
|
8283
8613
|
await writeJson(
|
|
8284
|
-
|
|
8614
|
+
join40(options.projectRoot, PATHS.PENTEST_RUNS_DIR, progress.run_id, "blocked-checks.json"),
|
|
8285
8615
|
report.blocked_checks
|
|
8286
8616
|
);
|
|
8287
|
-
await mkdir13(
|
|
8617
|
+
await mkdir13(join40(options.projectRoot, PATHS.PENTEST_DIR), { recursive: true });
|
|
8288
8618
|
const markdown = buildPentestMarkdown(report);
|
|
8289
|
-
await writeJson(
|
|
8290
|
-
await writeFile13(
|
|
8619
|
+
await writeJson(join40(options.projectRoot, report.sidecar_path), report);
|
|
8620
|
+
await writeFile13(join40(options.projectRoot, report.report_path), markdown);
|
|
8291
8621
|
this.tracker.markStepCompleted(
|
|
8292
8622
|
progress,
|
|
8293
8623
|
"write-report",
|
|
@@ -8314,7 +8644,7 @@ async function buildCurrentPentestReport(input) {
|
|
|
8314
8644
|
const docs = await loadModuleDocs(input.projectRoot, input.focusModules);
|
|
8315
8645
|
const tests = await loadTests(input.projectRoot, input.focusModules);
|
|
8316
8646
|
const osvFindings = await queryOsv(input.snapshot.packages);
|
|
8317
|
-
const osvPath =
|
|
8647
|
+
const osvPath = join40(input.artifactsDir, "dependencies", "osv-results.json");
|
|
8318
8648
|
await writeJson(osvPath, osvFindings);
|
|
8319
8649
|
const dependencyFindings = await buildDependencyFindings(
|
|
8320
8650
|
input.snapshot,
|
|
@@ -8323,7 +8653,7 @@ async function buildCurrentPentestReport(input) {
|
|
|
8323
8653
|
);
|
|
8324
8654
|
const moduleFindings = buildModuleFindings(docs, tests);
|
|
8325
8655
|
const secretFindings = await buildSecretFindings(input.artifactsDir);
|
|
8326
|
-
const runtimePayload = await readJsonIfExists(
|
|
8656
|
+
const runtimePayload = await readJsonIfExists(join40(input.artifactsDir, "runtime", "runtime-checks.json"));
|
|
8327
8657
|
const runtimeStatus = input.targetUrl ? runtimePayload?.reachable ? {
|
|
8328
8658
|
target_url: input.targetUrl,
|
|
8329
8659
|
status: "reachable",
|
|
@@ -8349,8 +8679,8 @@ async function buildCurrentPentestReport(input) {
|
|
|
8349
8679
|
...runtimeFindings
|
|
8350
8680
|
]);
|
|
8351
8681
|
const timestamp = toLocalTimestamp(input.reportTimestamp);
|
|
8352
|
-
const reportPath =
|
|
8353
|
-
const sidecarPath =
|
|
8682
|
+
const reportPath = join40(PATHS.PENTEST_DIR, `${timestamp}.md`);
|
|
8683
|
+
const sidecarPath = join40(PATHS.PENTEST_DIR, `${timestamp}.json`);
|
|
8354
8684
|
const stack = getPrimaryStack({
|
|
8355
8685
|
routing: { domain: "coding" },
|
|
8356
8686
|
stack_profile: input.snapshot.profile
|
|
@@ -8403,9 +8733,9 @@ async function buildCurrentPentestReport(input) {
|
|
|
8403
8733
|
raw_evidence_paths: [
|
|
8404
8734
|
relative7(input.projectRoot, osvPath),
|
|
8405
8735
|
...[
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8736
|
+
join40(PATHS.PENTEST_RUNS_DIR, input.progressRunId, "finding-index.json"),
|
|
8737
|
+
join40(PATHS.PENTEST_RUNS_DIR, input.progressRunId, "artifacts", "docs-summary.json"),
|
|
8738
|
+
join40(PATHS.PENTEST_RUNS_DIR, input.progressRunId, "artifacts", "tests-summary.json")
|
|
8409
8739
|
]
|
|
8410
8740
|
]
|
|
8411
8741
|
};
|
|
@@ -8445,7 +8775,7 @@ var PentestPhase = class {
|
|
|
8445
8775
|
|
|
8446
8776
|
// src/workflows/pentest-retest.ts
|
|
8447
8777
|
import { mkdir as mkdir14, writeFile as writeFile14 } from "fs/promises";
|
|
8448
|
-
import { dirname as dirname18, join as
|
|
8778
|
+
import { dirname as dirname18, join as join41 } from "path";
|
|
8449
8779
|
var RETEST_STEPS = [
|
|
8450
8780
|
{ id: "load-source-report", title: "Load source pentest report" },
|
|
8451
8781
|
{ id: "rerun-evidence", title: "Collect fresh evidence for source findings" },
|
|
@@ -8466,7 +8796,7 @@ var PentestRetestWorkflow = class {
|
|
|
8466
8796
|
}
|
|
8467
8797
|
const normalizedSourcePath = normalizeSidecarPath(sourceReportPath);
|
|
8468
8798
|
const sourceSidecar = await readJsonIfExists(
|
|
8469
|
-
|
|
8799
|
+
join41(options.projectRoot, normalizedSourcePath)
|
|
8470
8800
|
);
|
|
8471
8801
|
if (sourceSidecar === null) {
|
|
8472
8802
|
throw new Error(`Source pentest sidecar not found at ${normalizedSourcePath}`);
|
|
@@ -8491,12 +8821,12 @@ var PentestRetestWorkflow = class {
|
|
|
8491
8821
|
if (!this.tracker.shouldSkipStep(progress, "load-source-report", sourceHash)) {
|
|
8492
8822
|
this.tracker.markStepRunning(progress, "load-source-report", sourceHash);
|
|
8493
8823
|
await this.tracker.save(options.projectRoot, progress);
|
|
8494
|
-
const sourceCopy =
|
|
8824
|
+
const sourceCopy = join41(artifactsDir, "source-report.json");
|
|
8495
8825
|
await writeJson(sourceCopy, sourceSidecar);
|
|
8496
8826
|
this.tracker.markStepCompleted(
|
|
8497
8827
|
progress,
|
|
8498
8828
|
"load-source-report",
|
|
8499
|
-
[
|
|
8829
|
+
[join41(PATHS.PENTEST_RUNS_DIR, progress.run_id, "artifacts", "source-report.json")],
|
|
8500
8830
|
[...RETEST_SKILLS.source].map(skillPath)
|
|
8501
8831
|
);
|
|
8502
8832
|
await this.tracker.save(options.projectRoot, progress);
|
|
@@ -8513,7 +8843,7 @@ var PentestRetestWorkflow = class {
|
|
|
8513
8843
|
PENTEST_RUN_ID: progress.run_id,
|
|
8514
8844
|
PENTEST_ARTIFACT_DIR: artifactsDir,
|
|
8515
8845
|
PENTEST_TARGET_URL: targetUrl ?? "",
|
|
8516
|
-
PENTEST_SOURCE_REPORT:
|
|
8846
|
+
PENTEST_SOURCE_REPORT: join41(options.projectRoot, normalizedSourcePath),
|
|
8517
8847
|
PENTEST_DB_CONNECTION_NAME: options.dbConnectionName ?? ""
|
|
8518
8848
|
};
|
|
8519
8849
|
const scriptResults = await Promise.all([
|
|
@@ -8562,8 +8892,8 @@ var PentestRetestWorkflow = class {
|
|
|
8562
8892
|
...currentReport,
|
|
8563
8893
|
report_id: toReportId("RETEST", new Date(progress.started_at)),
|
|
8564
8894
|
workflow: "pentest-retest",
|
|
8565
|
-
report_path:
|
|
8566
|
-
sidecar_path:
|
|
8895
|
+
report_path: join41(PATHS.PENTEST_RETEST_DIR, `${timestamp}-${sourceSlug}.md`),
|
|
8896
|
+
sidecar_path: join41(PATHS.PENTEST_RETEST_DIR, `${timestamp}-${sourceSlug}.json`),
|
|
8567
8897
|
source_report_path: normalizedSourcePath,
|
|
8568
8898
|
source_report_id: sourceSidecar.report_id,
|
|
8569
8899
|
findings: retestFindings,
|
|
@@ -8576,7 +8906,7 @@ var PentestRetestWorkflow = class {
|
|
|
8576
8906
|
...[...RETEST_SKILLS.source, ...RETEST_SKILLS.evaluate].map(skillPath)
|
|
8577
8907
|
]
|
|
8578
8908
|
};
|
|
8579
|
-
const findingIndexPath =
|
|
8909
|
+
const findingIndexPath = join41(
|
|
8580
8910
|
options.projectRoot,
|
|
8581
8911
|
PATHS.PENTEST_RUNS_DIR,
|
|
8582
8912
|
progress.run_id,
|
|
@@ -8586,14 +8916,14 @@ var PentestRetestWorkflow = class {
|
|
|
8586
8916
|
this.tracker.markStepCompleted(
|
|
8587
8917
|
progress,
|
|
8588
8918
|
"evaluate-source-findings",
|
|
8589
|
-
[
|
|
8919
|
+
[join41(PATHS.PENTEST_RUNS_DIR, progress.run_id, "finding-index.json")],
|
|
8590
8920
|
[...RETEST_SKILLS.evaluate].map(skillPath)
|
|
8591
8921
|
);
|
|
8592
8922
|
await this.tracker.save(options.projectRoot, progress);
|
|
8593
8923
|
}
|
|
8594
8924
|
if (retestReport === null) {
|
|
8595
8925
|
const existing = progress.sidecar_path ? await readJsonIfExists(
|
|
8596
|
-
|
|
8926
|
+
join41(options.projectRoot, progress.sidecar_path)
|
|
8597
8927
|
) : null;
|
|
8598
8928
|
retestReport = existing;
|
|
8599
8929
|
}
|
|
@@ -8607,12 +8937,12 @@ var PentestRetestWorkflow = class {
|
|
|
8607
8937
|
if (!this.tracker.shouldSkipStep(progress, "write-report", writeHash)) {
|
|
8608
8938
|
this.tracker.markStepRunning(progress, "write-report", writeHash);
|
|
8609
8939
|
await this.tracker.save(options.projectRoot, progress);
|
|
8610
|
-
await writeJson(
|
|
8611
|
-
await mkdir14(dirname18(
|
|
8940
|
+
await writeJson(join41(options.projectRoot, retestReport.sidecar_path), retestReport);
|
|
8941
|
+
await mkdir14(dirname18(join41(options.projectRoot, retestReport.report_path)), {
|
|
8612
8942
|
recursive: true
|
|
8613
8943
|
});
|
|
8614
8944
|
await writeFile14(
|
|
8615
|
-
|
|
8945
|
+
join41(options.projectRoot, retestReport.report_path),
|
|
8616
8946
|
buildPentestMarkdown(retestReport)
|
|
8617
8947
|
);
|
|
8618
8948
|
this.tracker.markStepCompleted(
|
|
@@ -8690,7 +9020,7 @@ var ProjectQuestionPhase = class {
|
|
|
8690
9020
|
|
|
8691
9021
|
// src/workflows/root-cause-analysis.ts
|
|
8692
9022
|
import { mkdir as mkdir15, writeFile as writeFile15 } from "fs/promises";
|
|
8693
|
-
import { dirname as dirname19, join as
|
|
9023
|
+
import { dirname as dirname19, join as join42 } from "path";
|
|
8694
9024
|
var DEFAULT_TITLE_SLUG = "root-cause-analysis";
|
|
8695
9025
|
var RCA_SECTIONS = [
|
|
8696
9026
|
"Summary",
|
|
@@ -8709,8 +9039,8 @@ var RootCauseAnalysisWorkflow = class {
|
|
|
8709
9039
|
async run(options) {
|
|
8710
9040
|
const title = deriveTitle(options.classification.request_text);
|
|
8711
9041
|
const filename = `${toLocalTimestamp2(/* @__PURE__ */ new Date())}-${slugify2(title) || DEFAULT_TITLE_SLUG}.md`;
|
|
8712
|
-
const relativePath =
|
|
8713
|
-
const outputPath =
|
|
9042
|
+
const relativePath = join42(PATHS.RCA_DIR, filename);
|
|
9043
|
+
const outputPath = join42(options.projectRoot, relativePath);
|
|
8714
9044
|
await mkdir15(dirname19(outputPath), { recursive: true });
|
|
8715
9045
|
await writeFile15(outputPath, buildRcaDocument(title, options.classification));
|
|
8716
9046
|
return {
|
|
@@ -8859,22 +9189,22 @@ var DEFAULT_PHASES = {
|
|
|
8859
9189
|
|
|
8860
9190
|
// src/pipeline/stream-truncator.ts
|
|
8861
9191
|
import { appendFile, mkdir as mkdir17 } from "fs/promises";
|
|
8862
|
-
import { join as
|
|
9192
|
+
import { join as join44, dirname as dirname21 } from "path";
|
|
8863
9193
|
|
|
8864
9194
|
// src/resolver/deduplicator.ts
|
|
8865
9195
|
import { createHash as createHash9 } from "crypto";
|
|
8866
9196
|
import { readFile as readFile19, writeFile as writeFile17, mkdir as mkdir18 } from "fs/promises";
|
|
8867
|
-
import { join as
|
|
9197
|
+
import { join as join45, dirname as dirname22 } from "path";
|
|
8868
9198
|
|
|
8869
9199
|
// src/scripts/generator.ts
|
|
8870
9200
|
import { chmodSync as chmodSync2 } from "fs";
|
|
8871
9201
|
import { mkdir as mkdir19, writeFile as writeFile18 } from "fs/promises";
|
|
8872
|
-
import { dirname as dirname23, join as
|
|
9202
|
+
import { dirname as dirname23, join as join46 } from "path";
|
|
8873
9203
|
|
|
8874
9204
|
// src/skills/cache-manager.ts
|
|
8875
9205
|
import { createHash as createHash10 } from "crypto";
|
|
8876
9206
|
import { mkdir as mkdir20, readFile as readFile20, readdir as readdir5, rm as rm2, stat as stat3, writeFile as writeFile19 } from "fs/promises";
|
|
8877
|
-
import { join as
|
|
9207
|
+
import { join as join47 } from "path";
|
|
8878
9208
|
import fg6 from "fast-glob";
|
|
8879
9209
|
|
|
8880
9210
|
// src/skills/frontmatter-parser.ts
|
|
@@ -8899,24 +9229,49 @@ var conditionalProcessor = new ConditionalSectionProcessor();
|
|
|
8899
9229
|
|
|
8900
9230
|
// src/skills/index-generator.ts
|
|
8901
9231
|
import { mkdir as mkdir21, readFile as readFile21, writeFile as writeFile20 } from "fs/promises";
|
|
8902
|
-
import { dirname as dirname24, join as
|
|
9232
|
+
import { dirname as dirname24, join as join48, relative as relative8 } from "path";
|
|
8903
9233
|
import fg7 from "fast-glob";
|
|
8904
9234
|
|
|
8905
9235
|
// src/skills/loader.ts
|
|
8906
9236
|
import { readFile as readFile22 } from "fs/promises";
|
|
8907
9237
|
import { basename as basename6 } from "pathe";
|
|
8908
9238
|
|
|
9239
|
+
// src/update/audit.ts
|
|
9240
|
+
import { appendFileSync, mkdirSync as mkdirSync6 } from "fs";
|
|
9241
|
+
import { dirname as dirname25, join as join49 } from "path";
|
|
9242
|
+
function auditPath(projectRoot) {
|
|
9243
|
+
return join49(projectRoot, PATHS.AUDIT_LOG);
|
|
9244
|
+
}
|
|
9245
|
+
function ts() {
|
|
9246
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
9247
|
+
}
|
|
9248
|
+
function appendAuditLog2(projectRoot, previous, updated) {
|
|
9249
|
+
const path = auditPath(projectRoot);
|
|
9250
|
+
mkdirSync6(dirname25(path), { recursive: true });
|
|
9251
|
+
const line = `[${ts()}] INFO silent-update previous=${previous ?? "unknown"} updated=${updated}
|
|
9252
|
+
`;
|
|
9253
|
+
appendFileSync(path, line);
|
|
9254
|
+
}
|
|
9255
|
+
function appendAuditLogFailure(projectRoot, previous, target, error) {
|
|
9256
|
+
const path = auditPath(projectRoot);
|
|
9257
|
+
mkdirSync6(dirname25(path), { recursive: true });
|
|
9258
|
+
const sanitized = error.replace(/"/g, "'");
|
|
9259
|
+
const line = `[${ts()}] WARN silent-update-failed previous=${previous ?? "unknown"} target=${target} error="${sanitized}"
|
|
9260
|
+
`;
|
|
9261
|
+
appendFileSync(path, line);
|
|
9262
|
+
}
|
|
9263
|
+
|
|
8909
9264
|
// src/update/updater.ts
|
|
8910
|
-
import { chmodSync as chmodSync3, existsSync as
|
|
9265
|
+
import { chmodSync as chmodSync3, existsSync as existsSync19, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
8911
9266
|
import { mkdtemp, readdir as readdir6, readFile as readFile23, rm as rm3 } from "fs/promises";
|
|
8912
9267
|
import { tmpdir } from "os";
|
|
8913
|
-
import { dirname as
|
|
9268
|
+
import { dirname as dirname26, join as join50, relative as relative9 } from "path";
|
|
8914
9269
|
var FrameworkUpdater = class {
|
|
8915
9270
|
constructor(options = {}) {
|
|
8916
9271
|
this.options = options;
|
|
8917
9272
|
}
|
|
8918
9273
|
async run(projectRoot) {
|
|
8919
|
-
const previousVersion = readText(
|
|
9274
|
+
const previousVersion = readText(join50(projectRoot, PATHS.FRAMEWORK_VERSION));
|
|
8920
9275
|
const manifest = readManifest(projectRoot);
|
|
8921
9276
|
const artifactPolicy = new Map(
|
|
8922
9277
|
manifest?.generated_artifacts.map((artifact) => [artifact.path, artifact.auto_update]) ?? []
|
|
@@ -8926,19 +9281,19 @@ var FrameworkUpdater = class {
|
|
|
8926
9281
|
const skipped = [];
|
|
8927
9282
|
const newScripts = [];
|
|
8928
9283
|
for (const candidate of candidates) {
|
|
8929
|
-
const target =
|
|
8930
|
-
const existed =
|
|
9284
|
+
const target = join50(projectRoot, candidate.path);
|
|
9285
|
+
const existed = existsSync19(target);
|
|
8931
9286
|
const autoUpdate = artifactPolicy.get(candidate.path) ?? candidate.autoUpdate;
|
|
8932
9287
|
if (existed && autoUpdate === false) {
|
|
8933
9288
|
skipped.push({
|
|
8934
9289
|
path: candidate.path,
|
|
8935
|
-
before:
|
|
9290
|
+
before: readFileSync13(target, "utf8"),
|
|
8936
9291
|
after: candidate.content
|
|
8937
9292
|
});
|
|
8938
9293
|
continue;
|
|
8939
9294
|
}
|
|
8940
|
-
|
|
8941
|
-
|
|
9295
|
+
mkdirSync7(dirname26(target), { recursive: true });
|
|
9296
|
+
writeFileSync6(target, candidate.content);
|
|
8942
9297
|
if (candidate.executable === true) {
|
|
8943
9298
|
chmodSync3(target, 493);
|
|
8944
9299
|
}
|
|
@@ -8947,9 +9302,13 @@ var FrameworkUpdater = class {
|
|
|
8947
9302
|
newScripts.push(candidate.path);
|
|
8948
9303
|
}
|
|
8949
9304
|
}
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
9305
|
+
mkdirSync7(dirname26(join50(projectRoot, PATHS.FRAMEWORK_VERSION)), { recursive: true });
|
|
9306
|
+
writeFileSync6(
|
|
9307
|
+
join50(projectRoot, PATHS.FRAMEWORK_VERSION),
|
|
9308
|
+
`version=${VERSION}
|
|
9309
|
+
updated_at=${(/* @__PURE__ */ new Date()).toISOString()}
|
|
9310
|
+
`
|
|
9311
|
+
);
|
|
8953
9312
|
return {
|
|
8954
9313
|
previous_version: previousVersion,
|
|
8955
9314
|
target_version: VERSION,
|
|
@@ -8968,7 +9327,7 @@ var FrameworkUpdater = class {
|
|
|
8968
9327
|
if (profile === null) {
|
|
8969
9328
|
throw new Error("Cannot update framework-managed artifacts without a project profile");
|
|
8970
9329
|
}
|
|
8971
|
-
const tempRoot = await mkdtemp(
|
|
9330
|
+
const tempRoot = await mkdtemp(join50(tmpdir(), "paqad-ai-update-"));
|
|
8972
9331
|
try {
|
|
8973
9332
|
const result = await new OnboardingOrchestrator().run({
|
|
8974
9333
|
projectRoot: tempRoot,
|
|
@@ -8988,27 +9347,29 @@ var FrameworkUpdater = class {
|
|
|
8988
9347
|
}
|
|
8989
9348
|
};
|
|
8990
9349
|
function readManifest(projectRoot) {
|
|
8991
|
-
const path =
|
|
8992
|
-
if (!
|
|
9350
|
+
const path = join50(projectRoot, PATHS.ONBOARDING_MANIFEST);
|
|
9351
|
+
if (!existsSync19(path)) {
|
|
8993
9352
|
return null;
|
|
8994
9353
|
}
|
|
8995
|
-
return JSON.parse(
|
|
9354
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
8996
9355
|
}
|
|
8997
9356
|
function readProfile(projectRoot) {
|
|
8998
9357
|
return readProjectProfile(projectRoot);
|
|
8999
9358
|
}
|
|
9000
9359
|
function readText(path) {
|
|
9001
|
-
if (!
|
|
9360
|
+
if (!existsSync19(path)) {
|
|
9002
9361
|
return null;
|
|
9003
9362
|
}
|
|
9004
|
-
|
|
9363
|
+
const raw = readFileSync13(path, "utf8");
|
|
9364
|
+
const match = raw.match(/^version=(.+)$/m);
|
|
9365
|
+
return match ? match[1].trim() : raw.trim();
|
|
9005
9366
|
}
|
|
9006
9367
|
async function collectFiles(root, generated) {
|
|
9007
9368
|
const paths = generated.length > 0 ? generated : await walk3(root);
|
|
9008
9369
|
return Promise.all(
|
|
9009
9370
|
paths.map(async (file) => ({
|
|
9010
9371
|
path: file,
|
|
9011
|
-
content: await readFile23(
|
|
9372
|
+
content: await readFile23(join50(root, file), "utf8"),
|
|
9012
9373
|
autoUpdate: true,
|
|
9013
9374
|
executable: file.startsWith("scripts/")
|
|
9014
9375
|
}))
|
|
@@ -9018,7 +9379,7 @@ async function walk3(root, current = root) {
|
|
|
9018
9379
|
const entries = await readdir6(current, { withFileTypes: true });
|
|
9019
9380
|
const files = [];
|
|
9020
9381
|
for (const entry of entries) {
|
|
9021
|
-
const absolute =
|
|
9382
|
+
const absolute = join50(current, entry.name);
|
|
9022
9383
|
if (entry.isDirectory()) {
|
|
9023
9384
|
files.push(...await walk3(root, absolute));
|
|
9024
9385
|
continue;
|
|
@@ -9029,31 +9390,31 @@ async function walk3(root, current = root) {
|
|
|
9029
9390
|
}
|
|
9030
9391
|
|
|
9031
9392
|
// src/verification/gates/documentation-freshness.ts
|
|
9032
|
-
import { existsSync as
|
|
9033
|
-
import { join as
|
|
9393
|
+
import { existsSync as existsSync20 } from "fs";
|
|
9394
|
+
import { join as join51 } from "path";
|
|
9034
9395
|
var STALENESS_WINDOW_MS2 = 1e3 * 60 * 60 * 24 * 7;
|
|
9035
9396
|
|
|
9036
9397
|
// src/patterns/pattern-store.ts
|
|
9037
9398
|
import { readFile as readFile24, writeFile as writeFile21, mkdir as mkdir22, unlink } from "fs/promises";
|
|
9038
|
-
import { join as
|
|
9399
|
+
import { join as join52, dirname as dirname27 } from "path";
|
|
9039
9400
|
import { homedir as homedir3 } from "os";
|
|
9040
|
-
var GLOBAL_PATTERNS_DIR =
|
|
9401
|
+
var GLOBAL_PATTERNS_DIR = join52(homedir3(), ".paqad", "patterns");
|
|
9041
9402
|
var PatternStore = class {
|
|
9042
9403
|
get indexPath() {
|
|
9043
|
-
return
|
|
9404
|
+
return join52(GLOBAL_PATTERNS_DIR, "index.json");
|
|
9044
9405
|
}
|
|
9045
9406
|
get entriesDir() {
|
|
9046
|
-
return
|
|
9407
|
+
return join52(GLOBAL_PATTERNS_DIR, "entries");
|
|
9047
9408
|
}
|
|
9048
9409
|
async save(pattern) {
|
|
9049
9410
|
await mkdir22(this.entriesDir, { recursive: true });
|
|
9050
|
-
const entryPath =
|
|
9411
|
+
const entryPath = join52(this.entriesDir, `${pattern.id}.json`);
|
|
9051
9412
|
await writeFile21(entryPath, JSON.stringify(pattern, null, 2), "utf8");
|
|
9052
9413
|
await this.updateIndex(pattern);
|
|
9053
9414
|
}
|
|
9054
9415
|
async load(id) {
|
|
9055
9416
|
try {
|
|
9056
|
-
const raw = await readFile24(
|
|
9417
|
+
const raw = await readFile24(join52(this.entriesDir, `${id}.json`), "utf8");
|
|
9057
9418
|
return JSON.parse(raw);
|
|
9058
9419
|
} catch {
|
|
9059
9420
|
return null;
|
|
@@ -9083,12 +9444,12 @@ var PatternStore = class {
|
|
|
9083
9444
|
} else {
|
|
9084
9445
|
index.entries.push(entry);
|
|
9085
9446
|
}
|
|
9086
|
-
await mkdir22(
|
|
9447
|
+
await mkdir22(dirname27(this.indexPath), { recursive: true });
|
|
9087
9448
|
await writeFile21(this.indexPath, JSON.stringify(index, null, 2), "utf8");
|
|
9088
9449
|
}
|
|
9089
9450
|
async delete(id) {
|
|
9090
9451
|
try {
|
|
9091
|
-
await unlink(
|
|
9452
|
+
await unlink(join52(this.entriesDir, `${id}.json`));
|
|
9092
9453
|
} catch {
|
|
9093
9454
|
}
|
|
9094
9455
|
const index = await this.loadIndex();
|
|
@@ -9188,16 +9549,16 @@ ${p.solution}
|
|
|
9188
9549
|
|
|
9189
9550
|
// src/workflows/template-loader.ts
|
|
9190
9551
|
import { readFile as readFile25, readdir as readdir7 } from "fs/promises";
|
|
9191
|
-
import { join as
|
|
9552
|
+
import { join as join53 } from "path";
|
|
9192
9553
|
import YAML6 from "yaml";
|
|
9193
9554
|
|
|
9194
9555
|
// src/workflows/engine.ts
|
|
9195
9556
|
import { readFile as readFile26, writeFile as writeFile23, mkdir as mkdir23 } from "fs/promises";
|
|
9196
|
-
import { join as
|
|
9557
|
+
import { join as join54, dirname as dirname28 } from "path";
|
|
9197
9558
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
9198
9559
|
|
|
9199
9560
|
// src/index.ts
|
|
9200
|
-
var VERSION = "0.1.
|
|
9561
|
+
var VERSION = "0.1.3";
|
|
9201
9562
|
|
|
9202
9563
|
// src/cli/commands/capabilities.ts
|
|
9203
9564
|
import { Command } from "commander";
|
|
@@ -9373,8 +9734,8 @@ function createPacksCommand() {
|
|
|
9373
9734
|
|
|
9374
9735
|
// src/cli/commands/refresh.ts
|
|
9375
9736
|
import { Command as Command7 } from "commander";
|
|
9376
|
-
import { readFileSync as
|
|
9377
|
-
import { join as
|
|
9737
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
9738
|
+
import { join as join55 } from "path";
|
|
9378
9739
|
function createRefreshCommand() {
|
|
9379
9740
|
return new Command7("refresh").description("Refresh derived framework artifacts").option("--project-root <path>", "Project root", process.cwd()).option("--design-system", "Refresh design-system markdown from design tokens").option("--stack", "Refresh the cached stack snapshot").action(async (options) => {
|
|
9380
9741
|
const shouldRefreshDesignSystem = options.designSystem ?? true;
|
|
@@ -9410,14 +9771,14 @@ function createRefreshCommand() {
|
|
|
9410
9771
|
});
|
|
9411
9772
|
}
|
|
9412
9773
|
function writeRefreshDrift(projectRoot, refreshDrift) {
|
|
9413
|
-
const path =
|
|
9774
|
+
const path = join55(projectRoot, PATHS.STACK_DRIFT);
|
|
9414
9775
|
const current = readExistingJson(path);
|
|
9415
|
-
|
|
9776
|
+
writeFileSync7(path, `${JSON.stringify({ ...current ?? {}, ...refreshDrift }, null, 2)}
|
|
9416
9777
|
`);
|
|
9417
9778
|
}
|
|
9418
9779
|
function readExistingJson(path) {
|
|
9419
9780
|
try {
|
|
9420
|
-
return JSON.parse(
|
|
9781
|
+
return JSON.parse(readFileSync14(path, "utf8"));
|
|
9421
9782
|
} catch {
|
|
9422
9783
|
return null;
|
|
9423
9784
|
}
|
|
@@ -9426,9 +9787,25 @@ function readExistingJson(path) {
|
|
|
9426
9787
|
// src/cli/commands/update.ts
|
|
9427
9788
|
import { Command as Command8 } from "commander";
|
|
9428
9789
|
function createUpdateCommand() {
|
|
9429
|
-
return new Command8("update").description("Update framework-managed artifacts").option("--project-root <path>", "Project root", process.cwd()).action(async (options) => {
|
|
9430
|
-
|
|
9431
|
-
|
|
9790
|
+
return new Command8("update").description("Update framework-managed artifacts").option("--project-root <path>", "Project root", process.cwd()).option("--silent", "Suppress output; write results to audit log only").action(async (options) => {
|
|
9791
|
+
try {
|
|
9792
|
+
const report = await new FrameworkUpdater().run(options.projectRoot);
|
|
9793
|
+
if (!options.silent) {
|
|
9794
|
+
console.log(JSON.stringify(report, null, 2));
|
|
9795
|
+
}
|
|
9796
|
+
appendAuditLog2(options.projectRoot, report.previous_version, report.target_version);
|
|
9797
|
+
} catch (err) {
|
|
9798
|
+
if (!options.silent) {
|
|
9799
|
+
throw err;
|
|
9800
|
+
}
|
|
9801
|
+
appendAuditLogFailure(
|
|
9802
|
+
options.projectRoot,
|
|
9803
|
+
null,
|
|
9804
|
+
VERSION,
|
|
9805
|
+
err instanceof Error ? err.message : String(err)
|
|
9806
|
+
);
|
|
9807
|
+
process.exit(0);
|
|
9808
|
+
}
|
|
9432
9809
|
});
|
|
9433
9810
|
}
|
|
9434
9811
|
|