@cosmicdrift/kumiko-guards 0.403.0 → 0.404.0
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-guards",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.404.0",
|
|
4
4
|
"description": "AST-based security guards for Kumiko repos: direct-fs/fetch, tenant escalation, admin-API, escape hatches and related checks, run over a shared ts-morph project.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"kumiko-pre-push": "./src/pre-push.sh"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
39
|
+
"@cosmicdrift/kumiko-types": "0.404.0",
|
|
40
40
|
"ts-morph": "^28.0.0",
|
|
41
41
|
"zod": "^4.6.7"
|
|
42
42
|
},
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"LICENSE"
|
|
53
53
|
],
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
55
|
+
"@cosmicdrift/kumiko-framework": "0.404.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependenciesMeta": {
|
|
58
58
|
"@cosmicdrift/kumiko-framework": {
|
|
@@ -60,6 +60,6 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
63
|
+
"@cosmicdrift/kumiko-framework": "0.404.0"
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -108,7 +108,7 @@ function readExceptionEntries(source: ExceptionSource, filename: string): unknow
|
|
|
108
108
|
export function loadExceptionEntries<T>(options: {
|
|
109
109
|
readonly filename: string;
|
|
110
110
|
readonly roots: readonly string[];
|
|
111
|
-
readonly parse: (field: (key: string) => string) => T;
|
|
111
|
+
readonly parse: (field: (key: string) => string, source: ExceptionSource) => T;
|
|
112
112
|
readonly bundledDir?: string;
|
|
113
113
|
}): T[] {
|
|
114
114
|
const merged: T[] = [];
|
|
@@ -121,7 +121,7 @@ export function loadExceptionEntries<T>(options: {
|
|
|
121
121
|
const value = entry[key];
|
|
122
122
|
if (typeof value !== "string" || value.trim() === "") throw new MissingFieldError(key);
|
|
123
123
|
return value;
|
|
124
|
-
}),
|
|
124
|
+
}, source),
|
|
125
125
|
);
|
|
126
126
|
} catch (err) {
|
|
127
127
|
if (!(err instanceof MissingFieldError)) throw err;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Parse and evaluate `bun audit --json` reports with exception support.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type ExceptionSource,
|
|
6
|
+
isExceptionValid,
|
|
7
|
+
matchesPackageName,
|
|
8
|
+
} from "./dependency-exceptions";
|
|
5
9
|
|
|
6
10
|
export type Severity = "critical" | "high" | "moderate" | "low" | "info";
|
|
7
11
|
|
|
@@ -29,6 +33,7 @@ export type SecurityException = {
|
|
|
29
33
|
readonly reason: string;
|
|
30
34
|
readonly reviewedBy: string;
|
|
31
35
|
readonly expires: string;
|
|
36
|
+
readonly source: ExceptionSource;
|
|
32
37
|
};
|
|
33
38
|
|
|
34
39
|
export type SecurityEvaluation = {
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.404.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Security check warns about unused repo exceptions",
|
|
6
|
+
"detail": "kumiko-guards security lists every exception from the repo's dependency-security-exceptions.json that suppressed no advisory in the run, with package, advisory and file. The warning does not block; exceptions bundled with the guards package never warn because they cover consumers that may not have the package. Remove the listed entries once the advisory is fixed or withdrawn."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.402.0",
|
|
4
10
|
"type": "improvement",
|
package/src/check-security.ts
CHANGED
|
@@ -34,12 +34,13 @@ export function loadSecurityExceptions(
|
|
|
34
34
|
filename: SECURITY_EXCEPTIONS_FILE,
|
|
35
35
|
roots,
|
|
36
36
|
bundledDir,
|
|
37
|
-
parse: (field) => ({
|
|
37
|
+
parse: (field, source) => ({
|
|
38
38
|
advisory: field("advisory"),
|
|
39
39
|
package: field("package"),
|
|
40
40
|
reason: field("reason"),
|
|
41
41
|
reviewedBy: field("reviewedBy"),
|
|
42
42
|
expires: field("expires"),
|
|
43
|
+
source,
|
|
43
44
|
}),
|
|
44
45
|
}).filter((exception) => {
|
|
45
46
|
const hasDate = expiryEndMs(exception.expires) !== undefined;
|
|
@@ -67,6 +68,42 @@ export function shouldBlock(
|
|
|
67
68
|
return evaluation.blocking.length > 0 || expiringUrgent.length > 0;
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
// Bundled exceptions apply to every consumer, including those without the package, so only repo-local ones can be stale.
|
|
72
|
+
function findUnusedRepoExceptions(
|
|
73
|
+
exceptions: readonly SecurityException[],
|
|
74
|
+
used: readonly SecurityException[],
|
|
75
|
+
): SecurityException[] {
|
|
76
|
+
return exceptions.filter((ex) => ex.source.origin === "repo" && !used.includes(ex));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function printExceptionWarnings(
|
|
80
|
+
evaluation: SecurityEvaluation,
|
|
81
|
+
exceptions: readonly SecurityException[],
|
|
82
|
+
expiringUrgent: readonly SecurityException[],
|
|
83
|
+
nowMs: number,
|
|
84
|
+
): void {
|
|
85
|
+
const urgentKeys = new Set(expiringUrgent.map((ex) => `${ex.package}|${ex.advisory}`));
|
|
86
|
+
const expiringSoon = findExpiringSoon(exceptions, 30, nowMs).filter(
|
|
87
|
+
(ex) => !urgentKeys.has(`${ex.package}|${ex.advisory}`),
|
|
88
|
+
);
|
|
89
|
+
if (expiringSoon.length > 0) {
|
|
90
|
+
console.log("\n WARN: security exceptions expire within 30 days — re-review:");
|
|
91
|
+
for (const ex of expiringSoon) {
|
|
92
|
+
console.log(` ${ex.package} ${ex.advisory} expires=${ex.expires}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const unusedRepoExceptions = findUnusedRepoExceptions(exceptions, evaluation.usedExceptions);
|
|
97
|
+
if (unusedRepoExceptions.length > 0) {
|
|
98
|
+
console.log(
|
|
99
|
+
"\n WARN: security exceptions suppressed no advisory in this run — remove the entry if it is obsolete:",
|
|
100
|
+
);
|
|
101
|
+
for (const ex of unusedRepoExceptions) {
|
|
102
|
+
console.log(` ${ex.package} ${ex.advisory} (${ex.source.file})`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
70
107
|
function printReport(
|
|
71
108
|
evaluation: SecurityEvaluation,
|
|
72
109
|
exceptions: readonly SecurityException[],
|
|
@@ -112,16 +149,7 @@ function printReport(
|
|
|
112
149
|
}
|
|
113
150
|
}
|
|
114
151
|
|
|
115
|
-
|
|
116
|
-
const expiringSoon = findExpiringSoon(exceptions, 30, nowMs).filter(
|
|
117
|
-
(ex) => !urgentKeys.has(`${ex.package}|${ex.advisory}`),
|
|
118
|
-
);
|
|
119
|
-
if (expiringSoon.length > 0) {
|
|
120
|
-
console.log("\n WARN: security exceptions expire within 30 days — re-review:");
|
|
121
|
-
for (const ex of expiringSoon) {
|
|
122
|
-
console.log(` ${ex.package} ${ex.advisory} expires=${ex.expires}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
152
|
+
printExceptionWarnings(evaluation, exceptions, expiringUrgent, nowMs);
|
|
125
153
|
|
|
126
154
|
if (shouldBlock(evaluation, expiringUrgent)) {
|
|
127
155
|
console.error(
|