@blamejs/exceptd-skills 0.19.22 → 0.19.24
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 +14 -0
- package/data/_indexes/_meta.json +4 -4
- package/data/cve-catalog.json +1 -1
- package/data/zeroday-lessons.json +982 -53
- package/lib/validate-package.js +2 -1
- package/manifest.json +53 -53
- package/package.json +1 -1
- package/sbom.cdx.json +23 -23
- package/scripts/check-ttp-upstream.js +34 -3
- package/scripts/release.js +47 -8
|
@@ -65,14 +65,45 @@ function assertPinShape(kind, value) {
|
|
|
65
65
|
return value;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* The only hosts this checker may ever contact. `assertPinShape` already bounds
|
|
70
|
+
* the catalog-sourced version to a digit-and-dot shape before it reaches a URL,
|
|
71
|
+
* but that guard sits a long way from the request — a later edit could add a
|
|
72
|
+
* caller that skips it, and static analysis reasonably flags file data reaching
|
|
73
|
+
* an outbound request when the barrier is that distant. Re-check the resolved
|
|
74
|
+
* origin at the sink so the property is enforced where it matters and is
|
|
75
|
+
* verifiable by reading ten lines rather than tracing the whole module.
|
|
76
|
+
*/
|
|
77
|
+
const ALLOWED_ORIGINS = new Set(["https://raw.githubusercontent.com"]);
|
|
78
|
+
|
|
79
|
+
function assertAllowedUrl(rawUrl) {
|
|
80
|
+
let parsed;
|
|
81
|
+
try {
|
|
82
|
+
parsed = new URL(rawUrl);
|
|
83
|
+
} catch (_e) {
|
|
84
|
+
throw new Error(`refusing to fetch a malformed URL: ${JSON.stringify(rawUrl)}`);
|
|
85
|
+
}
|
|
86
|
+
if (!ALLOWED_ORIGINS.has(parsed.origin)) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`refusing to fetch ${parsed.origin} — this checker may only contact ` +
|
|
89
|
+
`${[...ALLOWED_ORIGINS].join(", ")}`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
// Reject anything that could smuggle credentials or redirect the path.
|
|
93
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash) {
|
|
94
|
+
throw new Error(`refusing to fetch a URL carrying credentials or a query/fragment: ${parsed.origin}${parsed.pathname}`);
|
|
95
|
+
}
|
|
96
|
+
return parsed.toString();
|
|
97
|
+
}
|
|
98
|
+
|
|
68
99
|
async function fetchJson(url) {
|
|
69
|
-
const res = await fetch(url, { headers: { "user-agent": UA } });
|
|
100
|
+
const res = await fetch(assertAllowedUrl(url), { headers: { "user-agent": UA }, redirect: "error" });
|
|
70
101
|
if (!res.ok) throw new Error(`${res.status} ${url}`);
|
|
71
102
|
return JSON.parse(await res.text());
|
|
72
103
|
}
|
|
73
104
|
|
|
74
105
|
async function fetchText(url) {
|
|
75
|
-
const res = await fetch(url, { headers: { "user-agent": UA } });
|
|
106
|
+
const res = await fetch(assertAllowedUrl(url), { headers: { "user-agent": UA }, redirect: "error" });
|
|
76
107
|
if (!res.ok) throw new Error(`${res.status} ${url}`);
|
|
77
108
|
return res.text();
|
|
78
109
|
}
|
|
@@ -199,4 +230,4 @@ if (require.main === module) main().catch((err) => {
|
|
|
199
230
|
process.exitCode = 2;
|
|
200
231
|
});
|
|
201
232
|
|
|
202
|
-
module.exports = { VERSION_SHAPES, assertPinShape };
|
|
233
|
+
module.exports = { VERSION_SHAPES, assertPinShape, ALLOWED_ORIGINS, assertAllowedUrl };
|
package/scripts/release.js
CHANGED
|
@@ -224,12 +224,30 @@ function _unresolvedThreads(prNum) {
|
|
|
224
224
|
// scanning unavailable, API error, or the GET-param form not supported) so a
|
|
225
225
|
// transient failure fails OPEN rather than blocking a release — the pre-flight
|
|
226
226
|
// checklist is the human backstop.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
227
|
+
// Two refs, and BOTH are load-bearing — each alone has a blind spot that has
|
|
228
|
+
// already bitten:
|
|
229
|
+
//
|
|
230
|
+
// refs/pull/<N>/merge answers "did this PR introduce an alert". Scoping to
|
|
231
|
+
// it ALONE let two medium alerts sitting on refs/heads/main since
|
|
232
|
+
// 2026-08-08 return zero, riding through eight consecutive releases while
|
|
233
|
+
// the phase printed "zero open CodeQL alerts" — GitHub only annotates a PR
|
|
234
|
+
// with alerts that PR introduces.
|
|
235
|
+
// (no ref) answers against the DEFAULT BRANCH, catching exactly
|
|
236
|
+
// those pre-existing findings — but on its own it misses an alert this PR
|
|
237
|
+
// introduces that is not on main yet, which is the case the PR-scoped query
|
|
238
|
+
// was there for.
|
|
239
|
+
//
|
|
240
|
+
// So the gate blocks on the union. Replacing one query with the other just
|
|
241
|
+
// trades which blind spot you have.
|
|
242
|
+
//
|
|
243
|
+
// Filtered to tool_name=CodeQL deliberately: OpenSSF Scorecard writes to the
|
|
244
|
+
// same code-scanning surface, and its accepted-out-of-scope policy alerts would
|
|
245
|
+
// otherwise make this list permanently non-empty and therefore useless.
|
|
246
|
+
function _codeqlAlertsForRef(ref) {
|
|
247
|
+
var args = ["api", "repos/:owner/:repo/code-scanning/alerts", "-X", "GET",
|
|
248
|
+
"-f", "state=open", "-f", "tool_name=CodeQL", "-f", "per_page=100"];
|
|
249
|
+
if (ref) args.push("-f", "ref=" + ref);
|
|
250
|
+
var rv = _capture("gh", args);
|
|
233
251
|
if (rv.status !== 0) return null;
|
|
234
252
|
try {
|
|
235
253
|
var arr = JSON.parse(rv.stdout || "[]");
|
|
@@ -237,6 +255,23 @@ function _openCodeqlAlerts(prNum) {
|
|
|
237
255
|
} catch (_e) { return null; }
|
|
238
256
|
}
|
|
239
257
|
|
|
258
|
+
function _openCodeqlAlerts(prNum) {
|
|
259
|
+
var onDefault = _codeqlAlertsForRef(null);
|
|
260
|
+
var onPr = _codeqlAlertsForRef("refs/pull/" + prNum + "/merge");
|
|
261
|
+
// Either query failing means the question is unanswered. Never let a failed
|
|
262
|
+
// lookup read as "no alerts" — that is the same absent-input-passes shape
|
|
263
|
+
// this whole gate exists to close.
|
|
264
|
+
if (onDefault === null || onPr === null) return null;
|
|
265
|
+
|
|
266
|
+
var byNumber = new Map();
|
|
267
|
+
onDefault.forEach(function (a) { byNumber.set(a.number, { alert: a, scope: "default branch" }); });
|
|
268
|
+
onPr.forEach(function (a) {
|
|
269
|
+
if (byNumber.has(a.number)) byNumber.get(a.number).scope = "default branch + this PR";
|
|
270
|
+
else byNumber.set(a.number, { alert: a, scope: "introduced by this PR" });
|
|
271
|
+
});
|
|
272
|
+
return [...byNumber.values()];
|
|
273
|
+
}
|
|
274
|
+
|
|
240
275
|
// ---- Subcommands ---------------------------------------------------------
|
|
241
276
|
|
|
242
277
|
// The derived-artifact regeneration, in the one order that is correct. Shared
|
|
@@ -499,12 +534,16 @@ function cmdWatch() {
|
|
|
499
534
|
"verify manually per the pre-flight checklist before merge.");
|
|
500
535
|
} else if (codeqlAlerts.length > 0) {
|
|
501
536
|
console.log("\nopen CodeQL alerts (" + codeqlAlerts.length + "):");
|
|
502
|
-
codeqlAlerts.forEach(function (
|
|
537
|
+
codeqlAlerts.forEach(function (e) {
|
|
538
|
+
var a = e.alert;
|
|
503
539
|
var loc = (a.most_recent_instance && a.most_recent_instance.location) || {};
|
|
504
540
|
var sev = (a.rule && (a.rule.security_severity_level || a.rule.severity)) || "?";
|
|
505
541
|
console.log(" ⚠ " + (a.rule && a.rule.id) + " [" + sev + "] " +
|
|
506
|
-
(loc.path ? loc.path + ":" + loc.start_line : "") + " " +
|
|
542
|
+
(loc.path ? loc.path + ":" + loc.start_line : "") + " " +
|
|
543
|
+
"(" + e.scope + ") " + (a.html_url || ""));
|
|
507
544
|
});
|
|
545
|
+
console.log("\nThe union of the default branch and this PR's merge ref: an alert already on main " +
|
|
546
|
+
"blocks the release as surely as one this PR introduces.");
|
|
508
547
|
console.log("\nFix real findings in code (push, let CodeQL re-scan) OR dismiss by-design FPs with a " +
|
|
509
548
|
"written reason, then re-run: node scripts/release.js watch");
|
|
510
549
|
process.exit(3); // allow:process-exit-after-stdout-write — maintainer-run release orchestrator; the guidance line above is human-read on a TTY, not a piped result channel
|