@blamejs/exceptd-skills 0.9.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/AGENTS.md +232 -0
- package/ARCHITECTURE.md +267 -0
- package/CHANGELOG.md +616 -0
- package/CONTEXT.md +203 -0
- package/LICENSE +200 -0
- package/NOTICE +82 -0
- package/README.md +307 -0
- package/SECURITY.md +73 -0
- package/agents/README.md +81 -0
- package/agents/report-generator.md +156 -0
- package/agents/skill-updater.md +102 -0
- package/agents/source-validator.md +119 -0
- package/agents/threat-researcher.md +149 -0
- package/bin/exceptd.js +183 -0
- package/data/_indexes/_meta.json +88 -0
- package/data/_indexes/activity-feed.json +362 -0
- package/data/_indexes/catalog-summaries.json +229 -0
- package/data/_indexes/chains.json +7135 -0
- package/data/_indexes/currency.json +359 -0
- package/data/_indexes/did-ladders.json +451 -0
- package/data/_indexes/frequency.json +2072 -0
- package/data/_indexes/handoff-dag.json +476 -0
- package/data/_indexes/jurisdiction-clocks.json +967 -0
- package/data/_indexes/jurisdiction-map.json +536 -0
- package/data/_indexes/recipes.json +319 -0
- package/data/_indexes/section-offsets.json +3656 -0
- package/data/_indexes/stale-content.json +14 -0
- package/data/_indexes/summary-cards.json +1736 -0
- package/data/_indexes/theater-fingerprints.json +381 -0
- package/data/_indexes/token-budget.json +2137 -0
- package/data/_indexes/trigger-table.json +1374 -0
- package/data/_indexes/xref.json +818 -0
- package/data/atlas-ttps.json +282 -0
- package/data/cve-catalog.json +496 -0
- package/data/cwe-catalog.json +1017 -0
- package/data/d3fend-catalog.json +738 -0
- package/data/dlp-controls.json +1039 -0
- package/data/exploit-availability.json +67 -0
- package/data/framework-control-gaps.json +1255 -0
- package/data/global-frameworks.json +2913 -0
- package/data/rfc-references.json +324 -0
- package/data/zeroday-lessons.json +377 -0
- package/keys/public.pem +3 -0
- package/lib/framework-gap.js +328 -0
- package/lib/job-queue.js +195 -0
- package/lib/lint-skills.js +536 -0
- package/lib/prefetch.js +372 -0
- package/lib/refresh-external.js +713 -0
- package/lib/schemas/cve-catalog.schema.json +151 -0
- package/lib/schemas/manifest.schema.json +106 -0
- package/lib/schemas/skill-frontmatter.schema.json +113 -0
- package/lib/scoring.js +149 -0
- package/lib/sign.js +197 -0
- package/lib/ttp-mapper.js +80 -0
- package/lib/validate-catalog-meta.js +198 -0
- package/lib/validate-cve-catalog.js +213 -0
- package/lib/validate-indexes.js +83 -0
- package/lib/validate-package.js +162 -0
- package/lib/validate-vendor.js +85 -0
- package/lib/verify.js +216 -0
- package/lib/worker-pool.js +84 -0
- package/manifest-snapshot.json +1833 -0
- package/manifest.json +2108 -0
- package/orchestrator/README.md +124 -0
- package/orchestrator/dispatcher.js +140 -0
- package/orchestrator/event-bus.js +146 -0
- package/orchestrator/index.js +874 -0
- package/orchestrator/pipeline.js +201 -0
- package/orchestrator/scanner.js +327 -0
- package/orchestrator/scheduler.js +137 -0
- package/package.json +113 -0
- package/sbom.cdx.json +158 -0
- package/scripts/audit-cross-skill.js +261 -0
- package/scripts/audit-perf.js +160 -0
- package/scripts/bootstrap.js +205 -0
- package/scripts/build-indexes.js +721 -0
- package/scripts/builders/activity-feed.js +79 -0
- package/scripts/builders/catalog-summaries.js +67 -0
- package/scripts/builders/currency.js +109 -0
- package/scripts/builders/cwe-chains.js +105 -0
- package/scripts/builders/did-ladders.js +149 -0
- package/scripts/builders/frequency.js +89 -0
- package/scripts/builders/jurisdiction-clocks.js +126 -0
- package/scripts/builders/recipes.js +159 -0
- package/scripts/builders/section-offsets.js +162 -0
- package/scripts/builders/stale-content.js +171 -0
- package/scripts/builders/summary-cards.js +166 -0
- package/scripts/builders/theater-fingerprints.js +198 -0
- package/scripts/builders/token-budget.js +96 -0
- package/scripts/check-manifest-snapshot.js +217 -0
- package/scripts/predeploy.js +267 -0
- package/scripts/refresh-manifest-snapshot.js +57 -0
- package/scripts/refresh-sbom.js +222 -0
- package/skills/age-gates-child-safety/skill.md +456 -0
- package/skills/ai-attack-surface/skill.md +282 -0
- package/skills/ai-c2-detection/skill.md +440 -0
- package/skills/ai-risk-management/skill.md +311 -0
- package/skills/api-security/skill.md +287 -0
- package/skills/attack-surface-pentest/skill.md +381 -0
- package/skills/cloud-security/skill.md +384 -0
- package/skills/compliance-theater/skill.md +365 -0
- package/skills/container-runtime-security/skill.md +379 -0
- package/skills/coordinated-vuln-disclosure/skill.md +473 -0
- package/skills/defensive-countermeasure-mapping/skill.md +300 -0
- package/skills/dlp-gap-analysis/skill.md +337 -0
- package/skills/email-security-anti-phishing/skill.md +206 -0
- package/skills/exploit-scoring/skill.md +331 -0
- package/skills/framework-gap-analysis/skill.md +374 -0
- package/skills/fuzz-testing-strategy/skill.md +313 -0
- package/skills/global-grc/skill.md +564 -0
- package/skills/identity-assurance/skill.md +272 -0
- package/skills/incident-response-playbook/skill.md +546 -0
- package/skills/kernel-lpe-triage/skill.md +303 -0
- package/skills/mcp-agent-trust/skill.md +326 -0
- package/skills/mlops-security/skill.md +325 -0
- package/skills/ot-ics-security/skill.md +340 -0
- package/skills/policy-exception-gen/skill.md +437 -0
- package/skills/pqc-first/skill.md +546 -0
- package/skills/rag-pipeline-security/skill.md +294 -0
- package/skills/researcher/skill.md +310 -0
- package/skills/sector-energy/skill.md +409 -0
- package/skills/sector-federal-government/skill.md +302 -0
- package/skills/sector-financial/skill.md +398 -0
- package/skills/sector-healthcare/skill.md +373 -0
- package/skills/security-maturity-tiers/skill.md +464 -0
- package/skills/skill-update-loop/skill.md +463 -0
- package/skills/supply-chain-integrity/skill.md +318 -0
- package/skills/threat-model-currency/skill.md +404 -0
- package/skills/threat-modeling-methodology/skill.md +312 -0
- package/skills/webapp-security/skill.md +281 -0
- package/skills/zeroday-gap-learn/skill.md +350 -0
- package/vendor/blamejs/LICENSE +201 -0
- package/vendor/blamejs/README.md +54 -0
- package/vendor/blamejs/_PROVENANCE.json +54 -0
- package/vendor/blamejs/retry.js +335 -0
- package/vendor/blamejs/worker-pool.js +418 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* scripts/audit-perf.js
|
|
4
|
+
*
|
|
5
|
+
* Micro-benchmarks the hot paths a skill / orchestrator / audit
|
|
6
|
+
* actually exercises. Times each operation so we can decide what's
|
|
7
|
+
* worth pre-computing into a seeded index.
|
|
8
|
+
*
|
|
9
|
+
* Usage: node scripts/audit-perf.js
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
|
|
15
|
+
const ROOT = path.join(__dirname, "..");
|
|
16
|
+
const ABS = (p) => path.join(ROOT, p);
|
|
17
|
+
|
|
18
|
+
function bench(label, fn, iters = 1) {
|
|
19
|
+
const start = process.hrtime.bigint();
|
|
20
|
+
let result;
|
|
21
|
+
for (let i = 0; i < iters; i++) result = fn();
|
|
22
|
+
const ns = Number(process.hrtime.bigint() - start);
|
|
23
|
+
const ms = (ns / 1e6 / iters).toFixed(3);
|
|
24
|
+
console.log(` ${ms.padStart(10)} ms ${label}`);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log("\n=== exceptd hot-path performance ===\n");
|
|
29
|
+
console.log("Operation Time");
|
|
30
|
+
console.log("-".repeat(70));
|
|
31
|
+
|
|
32
|
+
// 1. Load manifest
|
|
33
|
+
const manifest = bench("load manifest.json (parse)", () =>
|
|
34
|
+
JSON.parse(fs.readFileSync(ABS("manifest.json"), "utf8"))
|
|
35
|
+
);
|
|
36
|
+
const skills = manifest.skills;
|
|
37
|
+
|
|
38
|
+
// 2. Load every data catalog
|
|
39
|
+
const catalogs = [
|
|
40
|
+
"cve-catalog.json",
|
|
41
|
+
"atlas-ttps.json",
|
|
42
|
+
"framework-control-gaps.json",
|
|
43
|
+
"global-frameworks.json",
|
|
44
|
+
"cwe-catalog.json",
|
|
45
|
+
"d3fend-catalog.json",
|
|
46
|
+
"rfc-references.json",
|
|
47
|
+
"dlp-controls.json",
|
|
48
|
+
"zeroday-lessons.json",
|
|
49
|
+
"exploit-availability.json",
|
|
50
|
+
];
|
|
51
|
+
const catalogObjs = bench(`load all ${catalogs.length} data catalogs`, () => {
|
|
52
|
+
const out = {};
|
|
53
|
+
for (const c of catalogs) out[c] = JSON.parse(fs.readFileSync(ABS("data/" + c), "utf8"));
|
|
54
|
+
return out;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 3. Read every skill body
|
|
58
|
+
bench(`read all ${skills.length} skill.md bodies`, () => {
|
|
59
|
+
for (const s of skills) fs.readFileSync(ABS(s.path), "utf8");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 4. Parse every skill frontmatter (the linter's expensive op)
|
|
63
|
+
function parseFm(text) {
|
|
64
|
+
if (!text.startsWith("---")) return null;
|
|
65
|
+
const end = text.indexOf("\n---", 3);
|
|
66
|
+
if (end < 0) return null;
|
|
67
|
+
const fm = text.slice(3, end).replace(/^\r?\n/, "");
|
|
68
|
+
const r = {};
|
|
69
|
+
const lines = fm.split(/\r?\n/);
|
|
70
|
+
let i = 0;
|
|
71
|
+
while (i < lines.length) {
|
|
72
|
+
const L = lines[i];
|
|
73
|
+
if (!L.trim() || L.trimStart().startsWith("#")) { i++; continue; }
|
|
74
|
+
const m = L.match(/^([A-Za-z_]+):\s*(.*)$/);
|
|
75
|
+
if (!m) { i++; continue; }
|
|
76
|
+
const k = m[1], rest = m[2].trim();
|
|
77
|
+
if (rest === "" || rest === undefined) {
|
|
78
|
+
const items = []; i++;
|
|
79
|
+
while (i < lines.length && /^\s+-\s+/.test(lines[i])) {
|
|
80
|
+
items.push(lines[i].match(/^\s+-\s+(.*)$/)[1].trim()); i++;
|
|
81
|
+
}
|
|
82
|
+
r[k] = items; continue;
|
|
83
|
+
}
|
|
84
|
+
if (rest === "[]") { r[k] = []; i++; continue; }
|
|
85
|
+
r[k] = rest; i++;
|
|
86
|
+
}
|
|
87
|
+
return r;
|
|
88
|
+
}
|
|
89
|
+
bench(`parse all ${skills.length} skill frontmatters`, () => {
|
|
90
|
+
for (const s of skills) parseFm(fs.readFileSync(ABS(s.path), "utf8"));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// 5. Trigger lookup (what the dispatcher does)
|
|
94
|
+
const flatTriggers = [];
|
|
95
|
+
for (const s of skills) for (const t of s.triggers || []) flatTriggers.push([t.toLowerCase(), s.name]);
|
|
96
|
+
bench("trigger string-match against all skills (single query)", () => {
|
|
97
|
+
const q = "ai red team";
|
|
98
|
+
return flatTriggers.filter(([t]) => t.includes(q) || q.includes(t));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// 6. Cross-reference lookup: which skills cite a given CWE?
|
|
102
|
+
bench("xref: which skills cite CWE-79? (linear scan)", () => {
|
|
103
|
+
const refSet = "CWE-79";
|
|
104
|
+
return skills.filter((s) => (s.cwe_refs || []).includes(refSet)).map((s) => s.name);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// 7. Multi-hop chain: CVE → CWE → ATLAS → framework_gaps for one CVE
|
|
108
|
+
const cve = catalogObjs["cve-catalog.json"]["CVE-2026-31431"];
|
|
109
|
+
bench("multi-hop chain: CVE-2026-31431 → CWE → ATLAS → frameworks", () => {
|
|
110
|
+
// skills that mention CVE → their CWE refs → their ATLAS refs → their framework gaps
|
|
111
|
+
const skillsCiting = skills.filter((s) =>
|
|
112
|
+
(catalogObjs["cve-catalog.json"]["CVE-2026-31431"].evidence_cves || []).length > 0 // dummy filter
|
|
113
|
+
);
|
|
114
|
+
const cwes = new Set();
|
|
115
|
+
const atlases = new Set();
|
|
116
|
+
const fws = new Set();
|
|
117
|
+
for (const s of skills) {
|
|
118
|
+
if (!(s.atlas_refs || []).length) continue;
|
|
119
|
+
for (const c of s.cwe_refs || []) cwes.add(c);
|
|
120
|
+
for (const a of s.atlas_refs || []) atlases.add(a);
|
|
121
|
+
for (const f of s.framework_gaps || []) fws.add(f);
|
|
122
|
+
}
|
|
123
|
+
return { cwes: [...cwes], atlases: [...atlases], fws: [...fws] };
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// 8. Forward_watch aggregator (read 38 skill files, parse frontmatter, union all forward_watch)
|
|
127
|
+
bench(`watchlist aggregator (full scan, ${skills.length} skills)`, () => {
|
|
128
|
+
const watch = new Set();
|
|
129
|
+
for (const s of skills) {
|
|
130
|
+
const fm = parseFm(fs.readFileSync(ABS(s.path), "utf8"));
|
|
131
|
+
if (fm && Array.isArray(fm.forward_watch)) for (const w of fm.forward_watch) watch.add(w);
|
|
132
|
+
}
|
|
133
|
+
return watch.size;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// 9. Full cross-skill audit
|
|
137
|
+
bench("full cross-skill audit script (subprocess overhead included)", () => {
|
|
138
|
+
// Simulate: load manifest + all catalogs + all skill files + compute every refset
|
|
139
|
+
for (const s of skills) {
|
|
140
|
+
fs.readFileSync(ABS(s.path), "utf8");
|
|
141
|
+
for (const f of s.cwe_refs || []) { /* lookup */ }
|
|
142
|
+
for (const f of s.d3fend_refs || []) { /* lookup */ }
|
|
143
|
+
for (const f of s.framework_gaps || []) { /* lookup */ }
|
|
144
|
+
for (const f of s.atlas_refs || []) { /* lookup */ }
|
|
145
|
+
for (const f of s.rfc_refs || []) { /* lookup */ }
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
console.log("");
|
|
150
|
+
console.log("=== Sizes ===");
|
|
151
|
+
const totalBytes = (paths) => paths.reduce((t, p) => t + fs.statSync(ABS(p)).size, 0);
|
|
152
|
+
console.log(` manifest.json: ${fs.statSync(ABS("manifest.json")).size.toLocaleString()} bytes`);
|
|
153
|
+
console.log(` manifest-snapshot.json: ${fs.statSync(ABS("manifest-snapshot.json")).size.toLocaleString()} bytes`);
|
|
154
|
+
console.log(` data/*.json (${catalogs.length} files): ${totalBytes(catalogs.map(c => "data/" + c)).toLocaleString()} bytes`);
|
|
155
|
+
console.log(` skills/*/skill.md (${skills.length} files): ${totalBytes(skills.map(s => s.path)).toLocaleString()} bytes`);
|
|
156
|
+
|
|
157
|
+
console.log("\n=== Recommendation surfaces (manual review) ===");
|
|
158
|
+
console.log(" - Anything slower than 50 ms in the hot path = candidate for pre-computed index");
|
|
159
|
+
console.log(" - Anything called >1×/operation = candidate for cached + invalidated index");
|
|
160
|
+
console.log(" - JSON files >100 KB = candidate for streaming or partial load if hot-path indexed");
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* exceptd Security — bootstrap ceremony.
|
|
6
|
+
*
|
|
7
|
+
* Three audiences, three behaviors. The script auto-detects which mode is
|
|
8
|
+
* appropriate so a downstream consumer never accidentally invalidates the
|
|
9
|
+
* maintainer's signing key.
|
|
10
|
+
*
|
|
11
|
+
* 1. Downstream consumer (keys/public.pem exists, .keys/private.pem missing,
|
|
12
|
+
* no --init flag) — VERIFY ONLY. The maintainer already shipped the public
|
|
13
|
+
* key + signed manifest. Running `npm run bootstrap` here just confirms
|
|
14
|
+
* the working tree is intact. No keypair is generated; no signatures are
|
|
15
|
+
* rewritten. This is the safe default.
|
|
16
|
+
*
|
|
17
|
+
* 2. Maintainer re-sign (.keys/private.pem exists) — SIGN + VERIFY. Used
|
|
18
|
+
* after editing skill content. Re-signs every skill with the existing
|
|
19
|
+
* private key, then verifies.
|
|
20
|
+
*
|
|
21
|
+
* 3. First-maintainer init (no keys/public.pem, OR --init explicitly passed)
|
|
22
|
+
* — GENERATE + SIGN + VERIFY. Used once when a maintainer sets up signing
|
|
23
|
+
* for a brand-new clone. Generates an Ed25519 keypair, signs every skill,
|
|
24
|
+
* and verifies. The new public key is committed; the private key stays in
|
|
25
|
+
* .keys/ (gitignored).
|
|
26
|
+
*
|
|
27
|
+
* The private key never leaves the maintainer's machine. The public key in
|
|
28
|
+
* keys/public.pem is the one tracked artifact and is committed by the
|
|
29
|
+
* maintainer after first init.
|
|
30
|
+
*
|
|
31
|
+
* Subprocesses use execFileSync (no shell) with argument arrays — there is no
|
|
32
|
+
* user input on the path, and avoiding the shell removes the injection surface
|
|
33
|
+
* regardless.
|
|
34
|
+
*
|
|
35
|
+
* Usage:
|
|
36
|
+
* node scripts/bootstrap.js Auto-detect mode and run.
|
|
37
|
+
* node scripts/bootstrap.js --init Force first-maintainer init (generate
|
|
38
|
+
* keypair + sign + verify).
|
|
39
|
+
* node scripts/bootstrap.js --force Re-run even if marker exists.
|
|
40
|
+
* node scripts/bootstrap.js --help Print this help text.
|
|
41
|
+
*
|
|
42
|
+
* Dependencies: Node 24 stdlib only. package.json has no runtime deps and
|
|
43
|
+
* this script keeps it that way.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
const fs = require('node:fs');
|
|
47
|
+
const path = require('node:path');
|
|
48
|
+
const childProcess = require('node:child_process');
|
|
49
|
+
|
|
50
|
+
const ROOT = path.join(__dirname, '..');
|
|
51
|
+
const PRIVATE_KEY_PATH = path.join(ROOT, '.keys', 'private.pem');
|
|
52
|
+
const PUBLIC_KEY_PATH = path.join(ROOT, 'keys', 'public.pem');
|
|
53
|
+
const MARKER_PATH = path.join(ROOT, '.bootstrap-complete');
|
|
54
|
+
const SIGN_SCRIPT = path.join(ROOT, 'lib', 'sign.js');
|
|
55
|
+
const VERIFY_SCRIPT = path.join(ROOT, 'lib', 'verify.js');
|
|
56
|
+
|
|
57
|
+
const HELP = `
|
|
58
|
+
exceptd Security — bootstrap ceremony
|
|
59
|
+
|
|
60
|
+
Mode is auto-detected from current state:
|
|
61
|
+
|
|
62
|
+
Downstream consumer (default):
|
|
63
|
+
Public key present, private key absent → VERIFY ONLY.
|
|
64
|
+
No keypair generated; no signatures rewritten.
|
|
65
|
+
|
|
66
|
+
Maintainer re-sign:
|
|
67
|
+
Private key present → SIGN + VERIFY.
|
|
68
|
+
Re-signs every skill in manifest.json with the existing private key.
|
|
69
|
+
|
|
70
|
+
First-maintainer init (--init or no keys at all):
|
|
71
|
+
GENERATE + SIGN + VERIFY.
|
|
72
|
+
Generates an Ed25519 keypair, signs every skill, runs the verifier.
|
|
73
|
+
|
|
74
|
+
Usage:
|
|
75
|
+
node scripts/bootstrap.js Auto-detect mode and run.
|
|
76
|
+
node scripts/bootstrap.js --init Force first-maintainer init.
|
|
77
|
+
node scripts/bootstrap.js --force Re-run even if marker exists.
|
|
78
|
+
node scripts/bootstrap.js --help Show this message.
|
|
79
|
+
|
|
80
|
+
Outputs (only in maintainer modes):
|
|
81
|
+
.keys/private.pem Ed25519 private key (gitignored — never commit).
|
|
82
|
+
keys/public.pem Ed25519 public key (commit this).
|
|
83
|
+
manifest.json Updated with per-skill signatures.
|
|
84
|
+
.bootstrap-complete Local timestamp marker (gitignored).
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
function parseArgs(argv) {
|
|
88
|
+
const args = { force: false, init: false, help: false };
|
|
89
|
+
for (const a of argv.slice(2)) {
|
|
90
|
+
if (a === '--force') args.force = true;
|
|
91
|
+
else if (a === '--init') args.init = true;
|
|
92
|
+
else if (a === '--help' || a === '-h') args.help = true;
|
|
93
|
+
else {
|
|
94
|
+
console.error(`[bootstrap] Unknown argument: ${a}`);
|
|
95
|
+
console.error('[bootstrap] Run with --help for usage.');
|
|
96
|
+
process.exit(2);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return args;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function detectMode(args) {
|
|
103
|
+
const hasPublic = fs.existsSync(PUBLIC_KEY_PATH);
|
|
104
|
+
const hasPrivate = fs.existsSync(PRIVATE_KEY_PATH);
|
|
105
|
+
|
|
106
|
+
if (args.init) return 'init';
|
|
107
|
+
if (hasPrivate) return 'resign';
|
|
108
|
+
if (hasPublic) return 'verify-only';
|
|
109
|
+
return 'init';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function run(label, scriptPath, scriptArgs) {
|
|
113
|
+
const pretty = `node ${path.relative(ROOT, scriptPath)} ${scriptArgs.join(' ')}`.trim();
|
|
114
|
+
console.log(`[bootstrap] ${label}: ${pretty}`);
|
|
115
|
+
try {
|
|
116
|
+
// execFileSync: no shell, args passed as a vetted array. The script path
|
|
117
|
+
// and all args here are constants under this repo's control.
|
|
118
|
+
childProcess.execFileSync(process.execPath, [scriptPath, ...scriptArgs], {
|
|
119
|
+
cwd: ROOT,
|
|
120
|
+
stdio: 'inherit'
|
|
121
|
+
});
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error(`[bootstrap] FAILED at step "${label}".`);
|
|
124
|
+
process.exit(err.status && Number.isInteger(err.status) ? err.status : 1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function writeMarker() {
|
|
129
|
+
const payload = {
|
|
130
|
+
completed_at: new Date().toISOString(),
|
|
131
|
+
node_version: process.version,
|
|
132
|
+
platform: process.platform
|
|
133
|
+
};
|
|
134
|
+
fs.writeFileSync(MARKER_PATH, JSON.stringify(payload, null, 2) + '\n', 'utf8');
|
|
135
|
+
console.log(`[bootstrap] Wrote ${path.relative(ROOT, MARKER_PATH)}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function main() {
|
|
139
|
+
const args = parseArgs(process.argv);
|
|
140
|
+
|
|
141
|
+
if (args.help) {
|
|
142
|
+
process.stdout.write(HELP);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (fs.existsSync(MARKER_PATH) && !args.force) {
|
|
147
|
+
console.log('[bootstrap] Already complete (.bootstrap-complete present).');
|
|
148
|
+
console.log('[bootstrap] Pass --force to re-run the ceremony.');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const mode = detectMode(args);
|
|
153
|
+
|
|
154
|
+
if (mode === 'verify-only') {
|
|
155
|
+
// Downstream consumer path. The maintainer already shipped the public
|
|
156
|
+
// key and signed manifest. Running bootstrap here just confirms tree
|
|
157
|
+
// integrity — never generates or signs, which would invalidate the
|
|
158
|
+
// upstream maintainer's signing chain.
|
|
159
|
+
console.log('[bootstrap] Detected downstream-consumer state:');
|
|
160
|
+
console.log(' - keys/public.pem present (shipped by maintainer)');
|
|
161
|
+
console.log(' - .keys/private.pem absent');
|
|
162
|
+
console.log('[bootstrap] Running VERIFY ONLY. No keypair will be generated.');
|
|
163
|
+
console.log('[bootstrap] If you ARE the maintainer setting up a new clone, pass --init.');
|
|
164
|
+
console.log();
|
|
165
|
+
run('verify signatures', VERIFY_SCRIPT, []);
|
|
166
|
+
writeMarker();
|
|
167
|
+
console.log('\n[bootstrap] Verify-only ceremony complete. Tree integrity confirmed.');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (mode === 'resign') {
|
|
172
|
+
// Maintainer re-sign path. Private key already exists; re-sign every
|
|
173
|
+
// skill against the current content and verify.
|
|
174
|
+
console.log('[bootstrap] Detected maintainer re-sign state (private key present).');
|
|
175
|
+
console.log('[bootstrap] Re-signing every skill with the existing private key.');
|
|
176
|
+
console.log();
|
|
177
|
+
run('sign all skills', SIGN_SCRIPT, ['sign-all']);
|
|
178
|
+
run('verify signatures', VERIFY_SCRIPT, []);
|
|
179
|
+
writeMarker();
|
|
180
|
+
console.log('\n[bootstrap] Re-sign ceremony complete.');
|
|
181
|
+
console.log('[bootstrap] If skill content or manifest.json changed, commit the updates:');
|
|
182
|
+
console.log(' git add manifest.json');
|
|
183
|
+
console.log(' git commit -m "resign: skill content updated"');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// mode === 'init' — first-maintainer setup.
|
|
188
|
+
console.log('[bootstrap] First-maintainer init mode:');
|
|
189
|
+
console.log(' - generating Ed25519 keypair (one-time)');
|
|
190
|
+
console.log(' - signing every skill listed in manifest.json');
|
|
191
|
+
console.log(' - verifying the signature chain end-to-end');
|
|
192
|
+
console.log();
|
|
193
|
+
run('generate Ed25519 keypair', SIGN_SCRIPT, ['generate-keypair']);
|
|
194
|
+
run('sign all skills', SIGN_SCRIPT, ['sign-all']);
|
|
195
|
+
run('verify signatures', VERIFY_SCRIPT, []);
|
|
196
|
+
writeMarker();
|
|
197
|
+
|
|
198
|
+
console.log('\n[bootstrap] First-maintainer init complete. Next steps:');
|
|
199
|
+
console.log(' git add keys/public.pem manifest.json');
|
|
200
|
+
console.log(' git commit -m "bootstrap: add signing public key and signed manifest"');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (require.main === module) {
|
|
204
|
+
main();
|
|
205
|
+
}
|