@blamejs/exceptd-skills 0.15.48 → 0.15.49
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/CHANGELOG.md +4 -0
- package/data/_indexes/_meta.json +2 -2
- package/lib/collectors/cicd-pipeline-compromise.js +4 -4
- package/lib/lint-skills.js +3 -1
- package/lib/playbook-runner.js +1 -1
- package/lib/sign.js +2 -1
- package/lib/upstream-check-cli.js +3 -1
- package/lib/validate-catalog-meta.js +6 -2
- package/lib/validate-cve-catalog.js +7 -3
- package/lib/validate-package.js +5 -2
- package/lib/validate-playbooks.js +9 -4
- package/lib/validate-vendor.js +5 -2
- package/lib/verify.js +1 -1
- package/manifest.json +44 -44
- package/package.json +3 -1
- package/sbom.cdx.json +68 -38
- package/scripts/check-codebase-patterns-currency.js +142 -0
- package/scripts/check-codebase-patterns.js +296 -0
- package/scripts/predeploy.js +13 -0
- package/scripts/release.js +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.49 — 2026-05-30
|
|
4
|
+
|
|
5
|
+
Internal: a new predeploy gate, `scripts/check-codebase-patterns.js`, enforces code-shape bug classes that recurred across releases. It blocks a library-callable function that writes to stdout and then calls `process.exit()` (which truncates buffered output when the stream is piped — the class the v0.15.47 validate-cves fix addressed) and a stale or reason-less `// allow:` suppression marker, and warns on dynamic `RegExp` construction. The flagged `process.exit` sites across the catalog, playbook, package, and vendor validators were converted to the flush-safe `safeExit` form, and the dynamic-`RegExp` sites carry inline justification markers. A companion advisory, wired into the release `prepare` step, flags when the upstream pattern catalog grows a class exceptd hasn't triaged. No change to the shipped CLI surface, catalogs, or skills.
|
|
6
|
+
|
|
3
7
|
## 0.15.48 — 2026-05-30
|
|
4
8
|
|
|
5
9
|
Internal: the release flow is now driven by a phased orchestrator, `scripts/release.js`. Each subcommand (prepare, gates, commit, push, watch, merge, tag, release) runs one idempotent, resumable phase and exits with a script-safe code; the tag phase enforces a GUARD against tag-on-stale-HEAD and version skew between `package.json`, `manifest.json`, and the CHANGELOG heading. No change to the shipped CLI, catalogs, or skills.
|
package/data/_indexes/_meta.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.1.0",
|
|
3
|
-
"generated_at": "2026-05-
|
|
3
|
+
"generated_at": "2026-05-30T20:22:06.536Z",
|
|
4
4
|
"generator": "scripts/build-indexes.js",
|
|
5
5
|
"source_count": 54,
|
|
6
6
|
"source_hashes": {
|
|
7
|
-
"manifest.json": "
|
|
7
|
+
"manifest.json": "b2d79207d42e38d82a76c13b827623baa3e951b16cfbcc05250572070eb76aed",
|
|
8
8
|
"data/atlas-ttps.json": "878b4a08bb73c8d20396d85cf433a88f2bc5e7a8cbf7f6ab773ce7ede0a11251",
|
|
9
9
|
"data/attack-techniques.json": "84fad74c8497cab922ed64b814752f54aa4620c2a938cb06642ff1510e1c5cb3",
|
|
10
10
|
"data/cve-catalog.json": "7a5f4e31401505e53330cdc4b54b39f8a8b04459d6b9411676d291c583ae535f",
|
|
@@ -73,16 +73,16 @@ function walkWorkflows(root) {
|
|
|
73
73
|
// - mapping: `on:\n push:\n pull_request_target:`
|
|
74
74
|
// Heuristic accepts all four.
|
|
75
75
|
function workflowHasTrigger(content, name) {
|
|
76
|
-
if (new RegExp(`^\\s*on:\\s*['"]?${name}['"]?\\s*(?:#.*)?$`, "m").test(content)) return true;
|
|
76
|
+
if (new RegExp(`^\\s*on:\\s*['"]?${name}['"]?\\s*(?:#.*)?$`, "m").test(content)) return true; // allow:dynamic-regex — `name` is a hardcoded trigger literal (pull_request_target / issue_comment / pull_request), never operator/file input
|
|
77
77
|
const listMatch = content.match(/^\s*on:\s*\[([^\]]*)\]/m);
|
|
78
|
-
if (listMatch && new RegExp(`(?:^|,)\\s*['"]?${name}['"]?\\s*(?:,|$)`).test(listMatch[1])) return true;
|
|
78
|
+
if (listMatch && new RegExp(`(?:^|,)\\s*['"]?${name}['"]?\\s*(?:,|$)`).test(listMatch[1])) return true; // allow:dynamic-regex — `name` is a hardcoded trigger literal, never operator/file input
|
|
79
79
|
// block list AND mapping forms both follow `on:\n` with indented
|
|
80
80
|
// continuation lines. Capture the block and inspect for either
|
|
81
81
|
// `- <name>` (list) or `<name>:` (mapping) within it.
|
|
82
82
|
const blockMatch = content.match(/^\s*on:\s*\n((?:[ \t]+[^\n]+\n?)+)/m);
|
|
83
83
|
if (blockMatch) {
|
|
84
|
-
if (new RegExp(`^[ \\t]+-\\s+['"]?${name}['"]?\\s*(?:#.*)?\\s*$`, "m").test(blockMatch[1])) return true;
|
|
85
|
-
if (new RegExp(`^[ \\t]+${name}:`, "m").test(blockMatch[1])) return true;
|
|
84
|
+
if (new RegExp(`^[ \\t]+-\\s+['"]?${name}['"]?\\s*(?:#.*)?\\s*$`, "m").test(blockMatch[1])) return true; // allow:dynamic-regex — `name` is a hardcoded trigger literal, never operator/file input
|
|
85
|
+
if (new RegExp(`^[ \\t]+${name}:`, "m").test(blockMatch[1])) return true; // allow:dynamic-regex — `name` is a hardcoded trigger literal, never operator/file input
|
|
86
86
|
}
|
|
87
87
|
return false;
|
|
88
88
|
}
|
package/lib/lint-skills.js
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
const fs = require('node:fs');
|
|
41
41
|
const path = require('node:path');
|
|
42
42
|
const process = require('node:process');
|
|
43
|
+
const { safeExit } = require('./exit-codes');
|
|
43
44
|
|
|
44
45
|
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
45
46
|
const MANIFEST_PATH = path.join(REPO_ROOT, 'manifest.json');
|
|
@@ -866,7 +867,8 @@ function main() {
|
|
|
866
867
|
if (strictFail) {
|
|
867
868
|
console.log(`[lint-skills] --strict: ${warned + (airGapWarnings ? airGapWarnings.length : 0)} warning(s) treated as failures.`);
|
|
868
869
|
}
|
|
869
|
-
|
|
870
|
+
safeExit(failed === 0 && orphans.length === 0 && !strictFail ? 0 : 1);
|
|
871
|
+
return;
|
|
870
872
|
}
|
|
871
873
|
|
|
872
874
|
// Export the minimal frontmatter parser for downstream consumers
|
package/lib/playbook-runner.js
CHANGED
|
@@ -3632,7 +3632,7 @@ function evalCondition(expr, ctx, playbook) {
|
|
|
3632
3632
|
// analyze() can surface analyze.runtime_errors[] without losing the
|
|
3633
3633
|
// diagnostic.
|
|
3634
3634
|
try {
|
|
3635
|
-
return new RegExp(m[2], 'i').test(val);
|
|
3635
|
+
return new RegExp(m[2], 'i').test(val); // allow:dynamic-regex — m[2] is the pattern from a signed-catalog playbook condition (/…/), and construction + .test() are already wrapped in this try/catch to neutralize a malformed/pathological pattern
|
|
3636
3636
|
} catch (e) {
|
|
3637
3637
|
const errorRec = { _regex_eval_error: { source: m[1], expr: m[2], message: e && e.message ? String(e.message) : String(e) } };
|
|
3638
3638
|
// Two sites where ctx may carry an accumulator: runOpts._runErrors
|
package/lib/sign.js
CHANGED
|
@@ -77,6 +77,7 @@ const fs = require('fs');
|
|
|
77
77
|
const path = require('path');
|
|
78
78
|
const crypto = require('crypto');
|
|
79
79
|
const { execFileSync } = require('child_process');
|
|
80
|
+
const { safeExit } = require('./exit-codes');
|
|
80
81
|
|
|
81
82
|
const ROOT = path.join(__dirname, '..');
|
|
82
83
|
const MANIFEST_PATH = path.join(ROOT, 'manifest.json');
|
|
@@ -219,7 +220,7 @@ function signAll() {
|
|
|
219
220
|
}
|
|
220
221
|
printFingerprintBanner();
|
|
221
222
|
|
|
222
|
-
if (errors > 0)
|
|
223
|
+
if (errors > 0) { safeExit(1); return; }
|
|
223
224
|
}
|
|
224
225
|
|
|
225
226
|
/**
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
const path = require("path");
|
|
25
25
|
const fs = require("fs");
|
|
26
|
+
const { safeExit } = require("./exit-codes");
|
|
26
27
|
|
|
27
28
|
const ROOT = path.resolve(__dirname, "..");
|
|
28
29
|
const { fetchLatestPublished, buildFreshnessReport } = require("./upstream-check.js");
|
|
@@ -65,7 +66,8 @@ function readManifest() {
|
|
|
65
66
|
reason: "registry probe disabled in air-gap mode",
|
|
66
67
|
source: "upstream-check",
|
|
67
68
|
}) + "\n");
|
|
68
|
-
|
|
69
|
+
safeExit(0);
|
|
70
|
+
return;
|
|
69
71
|
}
|
|
70
72
|
const registry = await fetchLatestPublished({ timeoutMs: opts.timeoutMs });
|
|
71
73
|
if (opts.raw) {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
const fs = require('node:fs');
|
|
32
32
|
const path = require('node:path');
|
|
33
33
|
const process = require('node:process');
|
|
34
|
+
const { safeExit } = require('./exit-codes');
|
|
34
35
|
|
|
35
36
|
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
36
37
|
const DATA_DIR = path.join(REPO_ROOT, 'data');
|
|
@@ -64,10 +65,12 @@ function parseArgs(argv) {
|
|
|
64
65
|
' --quiet Suppress per-catalog PASS output; show failures only.\n' +
|
|
65
66
|
' --strict Promote freshness warnings to errors (used by the predeploy gate).\n',
|
|
66
67
|
);
|
|
67
|
-
|
|
68
|
+
safeExit(0);
|
|
69
|
+
return null;
|
|
68
70
|
} else {
|
|
69
71
|
console.error(`Unknown argument: ${a}`);
|
|
70
|
-
|
|
72
|
+
safeExit(2);
|
|
73
|
+
return null;
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
return opts;
|
|
@@ -208,6 +211,7 @@ function validateMeta(catalogPath, opts) {
|
|
|
208
211
|
|
|
209
212
|
function main() {
|
|
210
213
|
const opts = parseArgs(process.argv);
|
|
214
|
+
if (opts === null) return; // parseArgs handled --help / bad-arg and set the exit code
|
|
211
215
|
const files = fs
|
|
212
216
|
.readdirSync(DATA_DIR)
|
|
213
217
|
.filter((f) => f.endsWith('.json'))
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
const fs = require('node:fs');
|
|
27
27
|
const path = require('node:path');
|
|
28
28
|
const process = require('node:process');
|
|
29
|
+
const { safeExit } = require('./exit-codes');
|
|
29
30
|
|
|
30
31
|
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
31
32
|
const SCHEMA_PATH = path.join(REPO_ROOT, 'lib', 'schemas', 'cve-catalog.schema.json');
|
|
@@ -82,10 +83,12 @@ function parseArgs(argv) {
|
|
|
82
83
|
' --quiet Suppress per-CVE PASS output; show failures only.\n' +
|
|
83
84
|
' --strict Promote advisory warnings to errors (used by the predeploy gate). Off by default.\n',
|
|
84
85
|
);
|
|
85
|
-
|
|
86
|
+
safeExit(0);
|
|
87
|
+
return null;
|
|
86
88
|
} else {
|
|
87
89
|
console.error(`Unknown argument: ${a}`);
|
|
88
|
-
|
|
90
|
+
safeExit(2);
|
|
91
|
+
return null;
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
94
|
return opts;
|
|
@@ -138,7 +141,7 @@ function validate(value, schema, schemaName, pathStr) {
|
|
|
138
141
|
errors.push(`${here}: string shorter than minLength ${schema.minLength}`);
|
|
139
142
|
}
|
|
140
143
|
if (schema.pattern !== undefined) {
|
|
141
|
-
const re = new RegExp(schema.pattern);
|
|
144
|
+
const re = new RegExp(schema.pattern); // allow:dynamic-regex — bundled schema.pattern, not operator input
|
|
142
145
|
if (!re.test(value)) {
|
|
143
146
|
errors.push(`${here}: string ${JSON.stringify(value)} does not match pattern /${schema.pattern}/`);
|
|
144
147
|
}
|
|
@@ -312,6 +315,7 @@ function additionalChecks(key, entry, ctx) {
|
|
|
312
315
|
|
|
313
316
|
function main() {
|
|
314
317
|
const opts = parseArgs(process.argv);
|
|
318
|
+
if (opts === null) return; // parseArgs handled --help / bad-arg and set the exit code
|
|
315
319
|
const schema = readJson(SCHEMA_PATH);
|
|
316
320
|
const catalog = readJson(CATALOG_PATH);
|
|
317
321
|
const lessons = readJson(LESSONS_PATH);
|
package/lib/validate-package.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
const fs = require("fs");
|
|
20
20
|
const path = require("path");
|
|
21
21
|
const { spawnSync } = require("child_process");
|
|
22
|
+
const { safeExit } = require("./exit-codes");
|
|
22
23
|
|
|
23
24
|
const ROOT = path.join(__dirname, "..");
|
|
24
25
|
const ABS = (p) => path.join(ROOT, p);
|
|
@@ -154,12 +155,14 @@ function main() {
|
|
|
154
155
|
`${packInfo.files.length} files, ` +
|
|
155
156
|
`${sizeMB} MB packed / ${unpackedMB} MB unpacked.\n`
|
|
156
157
|
);
|
|
157
|
-
|
|
158
|
+
safeExit(0);
|
|
159
|
+
return;
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
process.stderr.write(`[validate-package] FAILED — ${issues.length} issue(s):\n`);
|
|
161
163
|
for (const i of issues) process.stderr.write(` • ${i}\n`);
|
|
162
|
-
|
|
164
|
+
safeExit(1);
|
|
165
|
+
return;
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
if (require.main === module) main();
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
const fs = require('node:fs');
|
|
71
71
|
const path = require('node:path');
|
|
72
72
|
const process = require('node:process');
|
|
73
|
+
const { safeExit } = require('./exit-codes');
|
|
73
74
|
|
|
74
75
|
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
75
76
|
const SCHEMA_PATH = path.join(REPO_ROOT, 'lib', 'schemas', 'playbook.schema.json');
|
|
@@ -94,10 +95,12 @@ function parseArgs(argv) {
|
|
|
94
95
|
' --quiet Suppress per-playbook PASS output; show failures only.\n' +
|
|
95
96
|
' --strict Treat warnings as errors (used by the predeploy gate).\n',
|
|
96
97
|
);
|
|
97
|
-
|
|
98
|
+
safeExit(0);
|
|
99
|
+
return null;
|
|
98
100
|
} else {
|
|
99
101
|
console.error(`Unknown argument: ${a}`);
|
|
100
|
-
|
|
102
|
+
safeExit(2);
|
|
103
|
+
return null;
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
106
|
return opts;
|
|
@@ -161,7 +164,7 @@ function validate(value, schema, schemaName, pathStr) {
|
|
|
161
164
|
err(`${here}: string shorter than minLength ${schema.minLength}`);
|
|
162
165
|
}
|
|
163
166
|
if (schema.pattern !== undefined) {
|
|
164
|
-
const re = new RegExp(schema.pattern);
|
|
167
|
+
const re = new RegExp(schema.pattern); // allow:dynamic-regex — bundled schema.pattern, not operator input
|
|
165
168
|
if (!re.test(value)) {
|
|
166
169
|
err(`${here}: string ${JSON.stringify(value)} does not match pattern /${schema.pattern}/`);
|
|
167
170
|
}
|
|
@@ -559,6 +562,7 @@ function checkMutexReciprocity(playbooks) {
|
|
|
559
562
|
|
|
560
563
|
function main() {
|
|
561
564
|
const opts = parseArgs(process.argv);
|
|
565
|
+
if (opts === null) return; // parseArgs handled --help / bad-arg and set the exit code
|
|
562
566
|
const schema = readJson(SCHEMA_PATH);
|
|
563
567
|
const ctx = loadContext();
|
|
564
568
|
const playbooks = loadPlaybooks();
|
|
@@ -617,7 +621,8 @@ function main() {
|
|
|
617
621
|
(warned ? `, ${warned} with warnings` : '') +
|
|
618
622
|
(errored ? `, ${errored} failed` : '') + '.',
|
|
619
623
|
);
|
|
620
|
-
|
|
624
|
+
safeExit(errored === 0 ? 0 : 1);
|
|
625
|
+
return;
|
|
621
626
|
}
|
|
622
627
|
|
|
623
628
|
module.exports = {
|
package/lib/validate-vendor.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
const fs = require("fs");
|
|
21
21
|
const path = require("path");
|
|
22
22
|
const crypto = require("crypto");
|
|
23
|
+
const { safeExit } = require("./exit-codes");
|
|
23
24
|
|
|
24
25
|
const ROOT = path.join(__dirname, "..");
|
|
25
26
|
const PROV = path.join(ROOT, "vendor", "blamejs", "_PROVENANCE.json");
|
|
@@ -71,13 +72,15 @@ function main() {
|
|
|
71
72
|
if (issues.length === 0) {
|
|
72
73
|
const fileCount = Object.keys(prov.files || {}).length;
|
|
73
74
|
console.log(`[validate-vendor] vendor tree current — ${fileCount} file(s) validated against pin ${prov.pinned_commit?.slice(0, 12) || "?"}.`);
|
|
74
|
-
|
|
75
|
+
safeExit(0);
|
|
76
|
+
return;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
console.error("[validate-vendor] vendor tree DRIFT:");
|
|
78
80
|
for (const i of issues) console.error(" • " + i);
|
|
79
81
|
console.error("[validate-vendor] re-vendor instructions: vendor/blamejs/README.md");
|
|
80
|
-
|
|
82
|
+
safeExit(1);
|
|
83
|
+
return;
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
if (require.main === module) main();
|
package/lib/verify.js
CHANGED
|
@@ -520,7 +520,7 @@ function validateAgainstSchema(value, schema, here, root) {
|
|
|
520
520
|
errors.push(`${here}: string shorter than minLength ${effectiveSchema.minLength}`);
|
|
521
521
|
}
|
|
522
522
|
if (effectiveSchema.pattern !== undefined) {
|
|
523
|
-
const re = new RegExp(effectiveSchema.pattern);
|
|
523
|
+
const re = new RegExp(effectiveSchema.pattern); // allow:dynamic-regex — bundled schema.pattern, not operator input
|
|
524
524
|
if (!re.test(value)) {
|
|
525
525
|
errors.push(`${here}: string ${JSON.stringify(value)} does not match pattern /${effectiveSchema.pattern}/`);
|
|
526
526
|
}
|