codex-plugin-doctor 1.26.0 → 1.27.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/README.md +23 -12
- package/dist/core/doctor-config.d.ts +4 -1
- package/dist/core/doctor-config.js +118 -5
- package/dist/domain/types.d.ts +16 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/reporting/render-json-report.js +7 -1
- package/dist/reporting/render-markdown-report.js +35 -15
- package/dist/reporting/render-sarif-report.js +9 -1
- package/dist/reporting/render-text-report.js +14 -1
- package/dist/rules/rule-catalog.d.ts +1 -1
- package/dist/rules/rule-catalog.js +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -336,16 +336,27 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
|
|
|
336
336
|
|
|
337
337
|
`fix --dry-run` renders safe automatic fix plans without changing files. `fix --interactive --backup` shows the same numbered plan, then applies everything after `yes` or only selected action numbers such as `1,3`. `fix --apply --backup` applies supported safe fixes, such as manifest defaults and missing skills directories, after creating backups.
|
|
338
338
|
|
|
339
|
-
Optional local policy file:
|
|
340
|
-
|
|
341
|
-
```json
|
|
342
|
-
{
|
|
343
|
-
"ignoreRules": ["plugin.heuristic.description.too_long"],
|
|
344
|
-
"
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
339
|
+
Optional local policy file:
|
|
340
|
+
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"ignoreRules": ["plugin.heuristic.description.too_long"],
|
|
344
|
+
"suppressions": [
|
|
345
|
+
{
|
|
346
|
+
"fingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
347
|
+
"reason": "Accepted until the upstream package layout is corrected.",
|
|
348
|
+
"expiresAt": "2026-07-31"
|
|
349
|
+
}
|
|
350
|
+
],
|
|
351
|
+
"failOnWarnings": true
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
`ignoreRules` disables every finding with a matching rule ID. Prefer `suppressions` when accepting one specific finding instance: each record requires the exact 64-character lowercase SHA-256 fingerprint shown in Doctor reports, a non-empty review reason, and a real `YYYY-MM-DD` expiration date.
|
|
356
|
+
|
|
357
|
+
Active suppressions remove only the matching finding from the active result and preserve it under `suppressedFindings` for audit output. Expired or invalid records do not suppress findings; Doctor emits `suppression.expired` or `suppression.invalid` warnings instead. Duplicate active records use the first valid match.
|
|
358
|
+
|
|
359
|
+
Run these when you want Codex Plugin Doctor to find plugins from the local Codex installation:
|
|
349
360
|
|
|
350
361
|
```bash
|
|
351
362
|
codex-plugin-doctor list --installed
|
|
@@ -370,9 +381,9 @@ jobs:
|
|
|
370
381
|
runs-on: ubuntu-latest
|
|
371
382
|
steps:
|
|
372
383
|
- uses: actions/checkout@v5
|
|
373
|
-
- uses: Esquetta/CodexPluginDoctor@v1.
|
|
384
|
+
- uses: Esquetta/CodexPluginDoctor@v1.27.0
|
|
374
385
|
with:
|
|
375
|
-
version: "1.
|
|
386
|
+
version: "1.27.0"
|
|
376
387
|
path: .
|
|
377
388
|
runtime: "true"
|
|
378
389
|
policy: codex-publish
|
|
@@ -2,6 +2,9 @@ import type { CheckResult } from "../domain/types.js";
|
|
|
2
2
|
export interface DoctorConfig {
|
|
3
3
|
ignoreRules: string[];
|
|
4
4
|
failOnWarnings: boolean;
|
|
5
|
+
suppressions: unknown[];
|
|
5
6
|
}
|
|
6
7
|
export declare function loadDoctorConfig(targetPath: string, explicitConfigPath?: string | null): Promise<DoctorConfig>;
|
|
7
|
-
export declare function applyDoctorConfig(result: CheckResult, config: DoctorConfig
|
|
8
|
+
export declare function applyDoctorConfig(result: CheckResult, config: DoctorConfig, options?: {
|
|
9
|
+
now?: Date;
|
|
10
|
+
}): CheckResult;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { withFindingFingerprint } from "../reporting/finding-fingerprint.js";
|
|
3
4
|
const defaultConfig = {
|
|
4
5
|
ignoreRules: [],
|
|
5
|
-
failOnWarnings: false
|
|
6
|
+
failOnWarnings: false,
|
|
7
|
+
suppressions: []
|
|
6
8
|
};
|
|
7
9
|
function isStringArray(value) {
|
|
8
10
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
@@ -19,15 +21,69 @@ export async function loadDoctorConfig(targetPath, explicitConfigPath) {
|
|
|
19
21
|
: defaultConfig.ignoreRules,
|
|
20
22
|
failOnWarnings: typeof parsed.failOnWarnings === "boolean"
|
|
21
23
|
? parsed.failOnWarnings
|
|
22
|
-
: defaultConfig.failOnWarnings
|
|
24
|
+
: defaultConfig.failOnWarnings,
|
|
25
|
+
suppressions: Array.isArray(parsed.suppressions)
|
|
26
|
+
? parsed.suppressions
|
|
27
|
+
: defaultConfig.suppressions
|
|
23
28
|
};
|
|
24
29
|
}
|
|
25
30
|
catch {
|
|
26
31
|
return defaultConfig;
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
|
-
export function applyDoctorConfig(result, config) {
|
|
30
|
-
const
|
|
34
|
+
export function applyDoctorConfig(result, config, options = {}) {
|
|
35
|
+
const configuredFindings = result.findings.filter((finding) => !config.ignoreRules.includes(finding.id));
|
|
36
|
+
const now = options.now ?? new Date();
|
|
37
|
+
const activeSuppressions = new Map();
|
|
38
|
+
const governanceWarnings = [];
|
|
39
|
+
const summary = {
|
|
40
|
+
applied: 0,
|
|
41
|
+
expired: 0,
|
|
42
|
+
invalid: 0
|
|
43
|
+
};
|
|
44
|
+
for (const [index, suppression] of config.suppressions.entries()) {
|
|
45
|
+
const validation = validateSuppression(suppression);
|
|
46
|
+
if (!validation.valid) {
|
|
47
|
+
summary.invalid += 1;
|
|
48
|
+
governanceWarnings.push(buildGovernanceWarning(result.targetPath, "suppression.invalid", "A targeted suppression record is invalid and was not applied.", "Invalid suppression records can create false confidence because they do not match or expire as intended.", "Fix or remove the invalid suppression record in `.codex-doctor.json`.", {
|
|
49
|
+
suppressionIndex: index,
|
|
50
|
+
field: validation.field
|
|
51
|
+
}));
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (isExpired(validation.expiresAt, now)) {
|
|
55
|
+
summary.expired += 1;
|
|
56
|
+
governanceWarnings.push(buildGovernanceWarning(result.targetPath, "suppression.expired", "A targeted suppression has expired and was not applied.", "Expired risk acceptance must be reviewed again before the finding can be suppressed.", "Remove the expired suppression or replace it with a newly reviewed expiration date.", {
|
|
57
|
+
suppressionIndex: index,
|
|
58
|
+
fingerprint: validation.fingerprint,
|
|
59
|
+
expiresAt: validation.expiresAt
|
|
60
|
+
}));
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (!activeSuppressions.has(validation.fingerprint)) {
|
|
64
|
+
activeSuppressions.set(validation.fingerprint, {
|
|
65
|
+
reason: validation.reason,
|
|
66
|
+
expiresAt: validation.expiresAt
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const findings = [];
|
|
71
|
+
const suppressedFindings = [];
|
|
72
|
+
for (const finding of configuredFindings) {
|
|
73
|
+
const suppression = finding.fingerprint
|
|
74
|
+
? activeSuppressions.get(finding.fingerprint)
|
|
75
|
+
: undefined;
|
|
76
|
+
if (!suppression) {
|
|
77
|
+
findings.push(finding);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
summary.applied += 1;
|
|
81
|
+
suppressedFindings.push({
|
|
82
|
+
...finding,
|
|
83
|
+
suppression
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
findings.push(...governanceWarnings);
|
|
31
87
|
const hasFailures = findings.some((finding) => finding.severity === "fail");
|
|
32
88
|
const hasWarnings = findings.some((finding) => finding.severity === "warn");
|
|
33
89
|
const warningFailure = config.failOnWarnings && hasWarnings;
|
|
@@ -40,6 +96,63 @@ export function applyDoctorConfig(result, config) {
|
|
|
40
96
|
...result,
|
|
41
97
|
status,
|
|
42
98
|
exitCode: status === "fail" ? 1 : 0,
|
|
43
|
-
findings
|
|
99
|
+
findings,
|
|
100
|
+
...(config.suppressions.length > 0
|
|
101
|
+
? {
|
|
102
|
+
suppressedFindings,
|
|
103
|
+
suppressionSummary: summary
|
|
104
|
+
}
|
|
105
|
+
: {})
|
|
44
106
|
};
|
|
45
107
|
}
|
|
108
|
+
function isPlainObject(value) {
|
|
109
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
110
|
+
}
|
|
111
|
+
function isRealDate(value) {
|
|
112
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
113
|
+
if (!match) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const year = Number(match[1]);
|
|
117
|
+
const month = Number(match[2]);
|
|
118
|
+
const day = Number(match[3]);
|
|
119
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
120
|
+
return (date.getUTCFullYear() === year &&
|
|
121
|
+
date.getUTCMonth() === month - 1 &&
|
|
122
|
+
date.getUTCDate() === day);
|
|
123
|
+
}
|
|
124
|
+
function validateSuppression(value) {
|
|
125
|
+
if (!isPlainObject(value)) {
|
|
126
|
+
return { valid: false, field: "record" };
|
|
127
|
+
}
|
|
128
|
+
if (typeof value.fingerprint !== "string" ||
|
|
129
|
+
!/^[a-f0-9]{64}$/.test(value.fingerprint)) {
|
|
130
|
+
return { valid: false, field: "fingerprint" };
|
|
131
|
+
}
|
|
132
|
+
if (typeof value.reason !== "string" || value.reason.trim().length === 0) {
|
|
133
|
+
return { valid: false, field: "reason" };
|
|
134
|
+
}
|
|
135
|
+
if (typeof value.expiresAt !== "string" || !isRealDate(value.expiresAt)) {
|
|
136
|
+
return { valid: false, field: "expiresAt" };
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
valid: true,
|
|
140
|
+
fingerprint: value.fingerprint,
|
|
141
|
+
reason: value.reason.trim(),
|
|
142
|
+
expiresAt: value.expiresAt
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function isExpired(expiresAt, now) {
|
|
146
|
+
const expirationBoundary = Date.parse(`${expiresAt}T00:00:00.000Z`) + 86_400_000;
|
|
147
|
+
return now.getTime() >= expirationBoundary;
|
|
148
|
+
}
|
|
149
|
+
function buildGovernanceWarning(targetPath, id, message, impact, suggestedFix, evidence) {
|
|
150
|
+
return withFindingFingerprint({
|
|
151
|
+
id,
|
|
152
|
+
severity: "warn",
|
|
153
|
+
message,
|
|
154
|
+
impact,
|
|
155
|
+
suggestedFix,
|
|
156
|
+
evidence
|
|
157
|
+
}, targetPath);
|
|
158
|
+
}
|
package/dist/domain/types.d.ts
CHANGED
|
@@ -10,11 +10,25 @@ export interface Finding {
|
|
|
10
10
|
evidence?: FindingEvidence;
|
|
11
11
|
fingerprint?: string;
|
|
12
12
|
}
|
|
13
|
+
export interface FindingSuppression {
|
|
14
|
+
reason: string;
|
|
15
|
+
expiresAt: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SuppressedFinding extends Finding {
|
|
18
|
+
suppression: FindingSuppression;
|
|
19
|
+
}
|
|
20
|
+
export interface SuppressionSummary {
|
|
21
|
+
applied: number;
|
|
22
|
+
expired: number;
|
|
23
|
+
invalid: number;
|
|
24
|
+
}
|
|
13
25
|
export interface CheckResult {
|
|
14
26
|
targetPath: string;
|
|
15
27
|
status: "pass" | "warn" | "fail";
|
|
16
28
|
exitCode: 0 | 1;
|
|
17
29
|
findings: Finding[];
|
|
30
|
+
suppressedFindings?: SuppressedFinding[];
|
|
31
|
+
suppressionSummary?: SuppressionSummary;
|
|
18
32
|
runtimeScorecard?: RuntimeScorecard;
|
|
19
33
|
}
|
|
20
34
|
export interface CheckOptions {
|
|
@@ -50,6 +64,8 @@ export interface JsonReport {
|
|
|
50
64
|
generatedAt: string;
|
|
51
65
|
summary: JsonReportSummary;
|
|
52
66
|
findings: Finding[];
|
|
67
|
+
suppressedFindings?: SuppressedFinding[];
|
|
68
|
+
suppressionSummary?: SuppressionSummary;
|
|
53
69
|
}
|
|
54
70
|
export type RuntimeCapabilityStatus = "pass" | "fail" | "warn" | "skipped" | "unsupported";
|
|
55
71
|
export interface RuntimeScorecard {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CheckOptions, CheckResult } from "./domain/types.js";
|
|
2
|
+
export { applyDoctorConfig, loadDoctorConfig, type DoctorConfig } from "./core/doctor-config.js";
|
|
2
3
|
export { buildFindingFingerprint, formatFindingFingerprintLine, withFindingFingerprint, withFindingFingerprints } from "./reporting/finding-fingerprint.js";
|
|
3
4
|
export { buildSecurityAudit, renderSecurityAuditJson, renderSecurityScorecard, type SecurityAudit } from "./security/security-audit.js";
|
|
4
5
|
export { buildTrustScore, renderTrustScore, renderTrustScoreJson, type BuildTrustScoreOptions, type TrustScoreReport } from "./security/trust-score.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { validatePlugin } from "./core/validate-plugin.js";
|
|
2
|
+
export { applyDoctorConfig, loadDoctorConfig } from "./core/doctor-config.js";
|
|
2
3
|
export { buildFindingFingerprint, formatFindingFingerprintLine, withFindingFingerprint, withFindingFingerprints } from "./reporting/finding-fingerprint.js";
|
|
3
4
|
export { buildSecurityAudit, renderSecurityAuditJson, renderSecurityScorecard } from "./security/security-audit.js";
|
|
4
5
|
export { buildTrustScore, renderTrustScore, renderTrustScoreJson } from "./security/trust-score.js";
|
|
@@ -18,7 +18,13 @@ export function buildJsonReport(result, options) {
|
|
|
18
18
|
total: result.findings.length
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
-
findings: result.findings
|
|
21
|
+
findings: result.findings,
|
|
22
|
+
...(result.suppressedFindings
|
|
23
|
+
? { suppressedFindings: result.suppressedFindings }
|
|
24
|
+
: {}),
|
|
25
|
+
...(result.suppressionSummary
|
|
26
|
+
? { suppressionSummary: result.suppressionSummary }
|
|
27
|
+
: {})
|
|
22
28
|
};
|
|
23
29
|
}
|
|
24
30
|
export function renderJsonReport(result, options) {
|
|
@@ -16,7 +16,10 @@ export function buildMarkdownReport(result, options) {
|
|
|
16
16
|
`| Warn Findings | ${warnCount} |`,
|
|
17
17
|
`| Total Findings | ${result.findings.length} |`
|
|
18
18
|
];
|
|
19
|
-
if (result.
|
|
19
|
+
if (result.suppressionSummary) {
|
|
20
|
+
lines.push(`| Suppressions Applied | ${result.suppressionSummary.applied} |`, `| Suppressions Expired | ${result.suppressionSummary.expired} |`, `| Suppressions Invalid | ${result.suppressionSummary.invalid} |`);
|
|
21
|
+
}
|
|
22
|
+
if (result.findings.length === 0 && !result.suppressedFindings?.length) {
|
|
20
23
|
lines.push("", "No findings.");
|
|
21
24
|
return lines.join("\n");
|
|
22
25
|
}
|
|
@@ -33,22 +36,39 @@ export function buildMarkdownReport(result, options) {
|
|
|
33
36
|
lines.push(`| prompts/list | ${result.runtimeScorecard.promptsList.toUpperCase()} |`);
|
|
34
37
|
lines.push(`| prompts/get | ${result.runtimeScorecard.promptGet.toUpperCase()} |`);
|
|
35
38
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
if (result.findings.length > 0) {
|
|
40
|
+
lines.push("", "## Findings", "");
|
|
41
|
+
for (const finding of result.findings) {
|
|
42
|
+
lines.push(`### ${finding.severity.toUpperCase()} \`${finding.id}\``);
|
|
43
|
+
lines.push("");
|
|
44
|
+
lines.push(`- Message: ${finding.message}`);
|
|
45
|
+
lines.push(`- Impact: ${finding.impact}`);
|
|
46
|
+
lines.push(`- Suggested fix: ${finding.suggestedFix}`);
|
|
47
|
+
const fingerprint = formatFindingFingerprintLine(finding);
|
|
48
|
+
if (fingerprint) {
|
|
49
|
+
lines.push(`- Fingerprint: \`${fingerprint}\``);
|
|
50
|
+
}
|
|
51
|
+
const evidence = formatFindingEvidenceLine(finding);
|
|
52
|
+
if (evidence) {
|
|
53
|
+
lines.push(`- Evidence: ${evidence}`);
|
|
54
|
+
}
|
|
55
|
+
lines.push("");
|
|
46
56
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
lines.push("", "No active findings.");
|
|
60
|
+
}
|
|
61
|
+
if (result.suppressedFindings?.length) {
|
|
62
|
+
lines.push("", "## Suppressed Findings", "");
|
|
63
|
+
for (const finding of result.suppressedFindings) {
|
|
64
|
+
lines.push(`### ${finding.severity.toUpperCase()} \`${finding.id}\``);
|
|
65
|
+
lines.push("");
|
|
66
|
+
lines.push(`- Message: ${finding.message}`);
|
|
67
|
+
lines.push(`- Fingerprint: \`${finding.fingerprint ?? "unavailable"}\``);
|
|
68
|
+
lines.push(`- Reason: ${finding.suppression.reason}`);
|
|
69
|
+
lines.push(`- Expires: ${finding.suppression.expiresAt}`);
|
|
70
|
+
lines.push("");
|
|
50
71
|
}
|
|
51
|
-
lines.push("");
|
|
52
72
|
}
|
|
53
73
|
return lines.join("\n");
|
|
54
74
|
}
|
|
@@ -55,7 +55,15 @@ export function renderSarifReport(result) {
|
|
|
55
55
|
}
|
|
56
56
|
: {}),
|
|
57
57
|
...(finding.evidence ? { properties: { evidence: finding.evidence } } : {})
|
|
58
|
-
}))
|
|
58
|
+
})),
|
|
59
|
+
...(result.suppressionSummary
|
|
60
|
+
? {
|
|
61
|
+
properties: {
|
|
62
|
+
suppressionSummary: result.suppressionSummary,
|
|
63
|
+
suppressedFindings: result.suppressedFindings ?? []
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
: {})
|
|
59
67
|
}
|
|
60
68
|
]
|
|
61
69
|
};
|
|
@@ -35,7 +35,10 @@ export function renderTextReport(result, options = {}) {
|
|
|
35
35
|
`Target: ${result.targetPath}`,
|
|
36
36
|
`Summary: ${failCount} fail, ${warnCount} warn, ${totalCount} total`
|
|
37
37
|
];
|
|
38
|
-
if (result.
|
|
38
|
+
if (result.suppressionSummary) {
|
|
39
|
+
lines.push(`Suppressions: ${result.suppressionSummary.applied} applied, ${result.suppressionSummary.expired} expired, ${result.suppressionSummary.invalid} invalid`);
|
|
40
|
+
}
|
|
41
|
+
if (result.findings.length === 0 && !result.suppressedFindings?.length) {
|
|
39
42
|
if (result.runtimeScorecard) {
|
|
40
43
|
lines.push("", "Runtime Scorecard", "----------------");
|
|
41
44
|
lines.push(`initialize: ${result.runtimeScorecard.initialize}`);
|
|
@@ -82,6 +85,16 @@ export function renderTextReport(result, options = {}) {
|
|
|
82
85
|
};
|
|
83
86
|
appendSection("Failures", failures, glyphs.fail);
|
|
84
87
|
appendSection("Warnings", warnings, glyphs.warn);
|
|
88
|
+
if (result.suppressedFindings?.length) {
|
|
89
|
+
lines.push("", "Suppressed Findings", "-------------------");
|
|
90
|
+
for (const finding of result.suppressedFindings) {
|
|
91
|
+
lines.push(`- ${finding.id}`);
|
|
92
|
+
lines.push(` Message: ${finding.message}`);
|
|
93
|
+
lines.push(` Fingerprint: ${finding.fingerprint ?? "unavailable"}`);
|
|
94
|
+
lines.push(` Reason: ${finding.suppression.reason}`);
|
|
95
|
+
lines.push(` Expires: ${finding.suppression.expiresAt}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
85
98
|
if (result.runtimeScorecard) {
|
|
86
99
|
lines.push("", "Runtime Scorecard", "----------------");
|
|
87
100
|
lines.push(`initialize: ${result.runtimeScorecard.initialize}`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type RuleCategory = "package" | "skill" | "mcp" | "runtime" | "security";
|
|
1
|
+
export type RuleCategory = "package" | "skill" | "mcp" | "runtime" | "security" | "configuration";
|
|
2
2
|
export type RuleSeverity = "fail" | "warn";
|
|
3
3
|
export interface RuleDefinition {
|
|
4
4
|
id: string;
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
export const ruleCatalog = [
|
|
2
|
+
{
|
|
3
|
+
id: "suppression.invalid",
|
|
4
|
+
category: "configuration",
|
|
5
|
+
defaultSeverity: "warn",
|
|
6
|
+
summary: "A targeted suppression record is invalid.",
|
|
7
|
+
why: "Invalid suppression records are not applied and can create false confidence about accepted findings.",
|
|
8
|
+
fix: "Fix or remove the invalid suppression record in `.codex-doctor.json`.",
|
|
9
|
+
example: '{ "fingerprint": "<64 lowercase hex characters>", "reason": "Reviewed exception.", "expiresAt": "2026-07-31" }'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: "suppression.expired",
|
|
13
|
+
category: "configuration",
|
|
14
|
+
defaultSeverity: "warn",
|
|
15
|
+
summary: "A targeted suppression has expired.",
|
|
16
|
+
why: "Expired risk acceptance must be reviewed again before the finding can be suppressed.",
|
|
17
|
+
fix: "Remove the expired record or replace it with a newly reviewed expiration date.",
|
|
18
|
+
example: '{ "fingerprint": "<fingerprint>", "reason": "Reviewed again.", "expiresAt": "2026-08-31" }'
|
|
19
|
+
},
|
|
2
20
|
{
|
|
3
21
|
id: "plugin.manifest.missing",
|
|
4
22
|
category: "package",
|
package/package.json
CHANGED