@vyuhlabs/dxkit 2.9.1 → 2.9.2
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 +41 -0
- package/README.md +3 -2
- package/dist/allowlist/cli.d.ts +38 -1
- package/dist/allowlist/cli.d.ts.map +1 -1
- package/dist/allowlist/cli.js +190 -3
- package/dist/allowlist/cli.js.map +1 -1
- package/dist/allowlist/file.d.ts +18 -0
- package/dist/allowlist/file.d.ts.map +1 -1
- package/dist/allowlist/file.js +10 -1
- package/dist/allowlist/file.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +23 -4
- package/dist/cli.js.map +1 -1
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +7 -1
- package/dist/generator.js.map +1 -1
- package/dist/ingest/env-file.d.ts +40 -0
- package/dist/ingest/env-file.d.ts.map +1 -0
- package/dist/ingest/env-file.js +163 -0
- package/dist/ingest/env-file.js.map +1 -0
- package/dist/ingest/snyk-policy.d.ts +60 -0
- package/dist/ingest/snyk-policy.d.ts.map +1 -0
- package/dist/ingest/snyk-policy.js +104 -0
- package/dist/ingest/snyk-policy.js.map +1 -0
- package/dist/ingest-cli.d.ts +4 -0
- package/dist/ingest-cli.d.ts.map +1 -1
- package/dist/ingest-cli.js +23 -4
- package/dist/ingest-cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/.claude/skills/dxkit-action/SKILL.md +5 -3
- package/templates/.claude/skills/dxkit-allowlist/SKILL.md +107 -0
- package/templates/.claude/skills/dxkit-config/SKILL.md +4 -4
- package/templates/.claude/skills/dxkit-fix/SKILL.md +1 -1
- package/templates/.claude/skills/dxkit-ingest/SKILL.md +2 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SNYK_ENV_PREFIX = void 0;
|
|
37
|
+
exports.parseSnykEnv = parseSnykEnv;
|
|
38
|
+
exports.loadSnykEnv = loadSnykEnv;
|
|
39
|
+
/**
|
|
40
|
+
* Opt-in `.env` loading for Snyk credentials — scoped strictly to the
|
|
41
|
+
* `SNYK_*` prefix.
|
|
42
|
+
*
|
|
43
|
+
* dxkit deliberately does NOT auto-load a whole `.env` into
|
|
44
|
+
* `process.env` (pulling a developer's entire secrets file — GitHub
|
|
45
|
+
* tokens, database URLs, unrelated API keys — into the process is a
|
|
46
|
+
* footgun). But a local developer who keeps their Snyk token in `.env`
|
|
47
|
+
* shouldn't have to re-`export` it before every `ingest --from-snyk`.
|
|
48
|
+
*
|
|
49
|
+
* The compromise: read the cwd's `.env` (or an explicit `--env-file`
|
|
50
|
+
* path), parse it, and lift ONLY keys beginning with `SNYK_` into the
|
|
51
|
+
* environment — and only when they aren't already set, so a real
|
|
52
|
+
* exported env / CI Actions secret always wins. CI, which sets the
|
|
53
|
+
* token via the environment and has no `.env`, is a no-op.
|
|
54
|
+
*
|
|
55
|
+
* This module is pure data-in/data-out: it mutates `process.env` for
|
|
56
|
+
* the lifted keys and returns a structured result (the keys it set +
|
|
57
|
+
* any advisory warnings). The CLI renders the one-line notice + the
|
|
58
|
+
* warnings; the module logs nothing itself, which keeps it testable.
|
|
59
|
+
*/
|
|
60
|
+
const fs = __importStar(require("fs"));
|
|
61
|
+
const path = __importStar(require("path"));
|
|
62
|
+
const child_process_1 = require("child_process");
|
|
63
|
+
/** Prefix that gates which keys may be lifted from the file. Nothing
|
|
64
|
+
* outside this prefix is ever read into the environment. */
|
|
65
|
+
exports.SNYK_ENV_PREFIX = 'SNYK_';
|
|
66
|
+
/**
|
|
67
|
+
* Parse a `.env`-style file body into key/value pairs, keeping ONLY
|
|
68
|
+
* keys that start with `SNYK_`. Tolerant of the common shapes:
|
|
69
|
+
* blank lines, `#` comments, an optional `export ` prefix, and
|
|
70
|
+
* single/double-quoted values. No variable interpolation — values are
|
|
71
|
+
* taken literally (after unquoting), which is correct for opaque
|
|
72
|
+
* tokens and ids.
|
|
73
|
+
*/
|
|
74
|
+
function parseSnykEnv(body) {
|
|
75
|
+
const out = {};
|
|
76
|
+
for (const rawLine of body.split(/\r?\n/)) {
|
|
77
|
+
const line = rawLine.trim();
|
|
78
|
+
if (!line || line.startsWith('#'))
|
|
79
|
+
continue;
|
|
80
|
+
const m = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
81
|
+
if (!m)
|
|
82
|
+
continue;
|
|
83
|
+
const key = m[1];
|
|
84
|
+
if (!key.startsWith(exports.SNYK_ENV_PREFIX))
|
|
85
|
+
continue;
|
|
86
|
+
out[key] = unquote(m[2]);
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
function unquote(value) {
|
|
91
|
+
const v = value.trim();
|
|
92
|
+
if (v.length >= 2) {
|
|
93
|
+
const first = v[0];
|
|
94
|
+
const last = v[v.length - 1];
|
|
95
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
96
|
+
return v.slice(1, -1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return v;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Lift `SNYK_*` keys from the cwd's `.env` (or `--env-file`) into
|
|
103
|
+
* `process.env`, unless `--no-env-file` is set or no file exists.
|
|
104
|
+
* Real environment values are never overwritten. Returns `null` when
|
|
105
|
+
* nothing was attempted (disabled, or no file present and none
|
|
106
|
+
* explicitly requested); otherwise a result describing what happened.
|
|
107
|
+
*/
|
|
108
|
+
function loadSnykEnv(cwd, opts = {}) {
|
|
109
|
+
if (opts.noEnvFile)
|
|
110
|
+
return null;
|
|
111
|
+
const explicit = opts.envFile !== undefined;
|
|
112
|
+
const filePath = explicit ? path.resolve(cwd, opts.envFile) : path.join(cwd, '.env');
|
|
113
|
+
if (!fs.existsSync(filePath)) {
|
|
114
|
+
if (explicit) {
|
|
115
|
+
return { path: filePath, loadedKeys: [], warnings: [`env-file not found: ${filePath}`] };
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
let body;
|
|
120
|
+
try {
|
|
121
|
+
body = fs.readFileSync(filePath, 'utf8');
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
return {
|
|
125
|
+
path: filePath,
|
|
126
|
+
loadedKeys: [],
|
|
127
|
+
warnings: [`could not read env-file ${filePath}: ${err.message}`],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const parsed = parseSnykEnv(body);
|
|
131
|
+
const loadedKeys = [];
|
|
132
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
133
|
+
// Real exported env / CI secret always wins. Treat an empty string
|
|
134
|
+
// as "unset" so a blank export doesn't shadow a populated .env.
|
|
135
|
+
const current = process.env[key];
|
|
136
|
+
if (current === undefined || current === '') {
|
|
137
|
+
process.env[key] = value;
|
|
138
|
+
loadedKeys.push(key);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const warnings = [];
|
|
142
|
+
if (isTrackedByGit(cwd, filePath)) {
|
|
143
|
+
warnings.push(`${path.basename(filePath)} appears to be committed to git — a secrets file ` +
|
|
144
|
+
`should be gitignored. Move SNYK_TOKEN out of version control.`);
|
|
145
|
+
}
|
|
146
|
+
return { path: filePath, loadedKeys, warnings };
|
|
147
|
+
}
|
|
148
|
+
/** Whether `filePath` is tracked in the git index at `cwd`. Best-effort
|
|
149
|
+
* — any failure (not a repo, git missing) is treated as "not tracked"
|
|
150
|
+
* so the advisory simply doesn't fire. */
|
|
151
|
+
function isTrackedByGit(cwd, filePath) {
|
|
152
|
+
try {
|
|
153
|
+
(0, child_process_1.execSync)(`git ls-files --error-unmatch ${JSON.stringify(filePath)}`, {
|
|
154
|
+
cwd,
|
|
155
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
156
|
+
});
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=env-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-file.js","sourceRoot":"","sources":["../../src/ingest/env-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writer for Snyk's `.snyk` policy file — the OUTBOUND half of the
|
|
3
|
+
* Snyk ↔ dxkit suppression sync.
|
|
4
|
+
*
|
|
5
|
+
* 2.9.1 wired the INBOUND direction: dxkit honors SARIF
|
|
6
|
+
* `result.suppressions` so a finding the team dismissed in Snyk is
|
|
7
|
+
* dropped at ingest time (`src/ingest/sarif.ts`). This module is the
|
|
8
|
+
* mirror: when the team allowlists a Snyk-originated finding in dxkit,
|
|
9
|
+
* `allowlist export --snyk` writes a `.snyk` ignore so the decision
|
|
10
|
+
* propagates back to Snyk's own gate (`snyk code test`, the Snyk UI).
|
|
11
|
+
*
|
|
12
|
+
* The `.snyk` file is YAML. dxkit carries no YAML dependency, so this
|
|
13
|
+
* is a small CONTROLLED serializer for exactly the policy shape Snyk's
|
|
14
|
+
* tooling reads — not a general YAML emitter. The structure is fixed:
|
|
15
|
+
*
|
|
16
|
+
* version: v1.25.0
|
|
17
|
+
* ignore:
|
|
18
|
+
* '<rule-id>':
|
|
19
|
+
* - '<path>':
|
|
20
|
+
* reason: "<reason>"
|
|
21
|
+
* expires: <ISO datetime> # omitted for a permanent ignore
|
|
22
|
+
* created: <ISO datetime>
|
|
23
|
+
* patch: {}
|
|
24
|
+
*
|
|
25
|
+
* Caveat surfaced to the user by the CLI: Snyk Code (SAST) honors
|
|
26
|
+
* `.snyk` ignores only when the org has Snyk's "consistent ignores"
|
|
27
|
+
* feature enabled; SCA/dependency ignores are standard. dxkit writes
|
|
28
|
+
* the file either way — the caller documents the prerequisite.
|
|
29
|
+
*/
|
|
30
|
+
/** One ignore directive — a single (rule, path) pair plus metadata. */
|
|
31
|
+
export interface SnykIgnore {
|
|
32
|
+
/** Snyk-native rule / issue id (e.g. `javascript/InsecureTLSConfig`). */
|
|
33
|
+
readonly ruleId: string;
|
|
34
|
+
/** Repo-relative path the ignore applies to. */
|
|
35
|
+
readonly path: string;
|
|
36
|
+
/** Human rationale carried over from the allowlist entry. */
|
|
37
|
+
readonly reason?: string;
|
|
38
|
+
/** ISO 8601 datetime after which the ignore lapses. Omitted →
|
|
39
|
+
* permanent ignore (Snyk treats a missing `expires` as no expiry). */
|
|
40
|
+
readonly expires?: string;
|
|
41
|
+
/** ISO 8601 datetime the ignore was written. */
|
|
42
|
+
readonly created: string;
|
|
43
|
+
}
|
|
44
|
+
/** Snyk policy schema version dxkit emits. Matches the version Snyk's
|
|
45
|
+
* own `snyk ignore` writes for the ignore/patch shape used here. */
|
|
46
|
+
export declare const SNYK_POLICY_VERSION: "v1.25.0";
|
|
47
|
+
/**
|
|
48
|
+
* Convert an allowlist entry's `expiresAt` (`YYYY-MM-DD`) into the ISO
|
|
49
|
+
* datetime Snyk's policy file expects. Returns `undefined` for a
|
|
50
|
+
* missing date so the caller emits a permanent ignore.
|
|
51
|
+
*/
|
|
52
|
+
export declare function expiryToSnykDatetime(expiresAt: string | undefined): string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Serialize ignores into `.snyk` policy YAML. Groups by rule id (the
|
|
55
|
+
* file's top-level ignore key), each carrying a list of per-path
|
|
56
|
+
* directives. Deterministic ordering (rules + paths sorted) so the
|
|
57
|
+
* committed file has stable diffs across runs.
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildSnykPolicy(ignores: ReadonlyArray<SnykIgnore>): string;
|
|
60
|
+
//# sourceMappingURL=snyk-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snyk-policy.d.ts","sourceRoot":"","sources":["../../src/ingest/snyk-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;2EACuE;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;qEACqE;AACrE,eAAO,MAAM,mBAAmB,EAAG,SAAkB,CAAC;AAEtD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGtF;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,MAAM,CAoC1E"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Writer for Snyk's `.snyk` policy file — the OUTBOUND half of the
|
|
4
|
+
* Snyk ↔ dxkit suppression sync.
|
|
5
|
+
*
|
|
6
|
+
* 2.9.1 wired the INBOUND direction: dxkit honors SARIF
|
|
7
|
+
* `result.suppressions` so a finding the team dismissed in Snyk is
|
|
8
|
+
* dropped at ingest time (`src/ingest/sarif.ts`). This module is the
|
|
9
|
+
* mirror: when the team allowlists a Snyk-originated finding in dxkit,
|
|
10
|
+
* `allowlist export --snyk` writes a `.snyk` ignore so the decision
|
|
11
|
+
* propagates back to Snyk's own gate (`snyk code test`, the Snyk UI).
|
|
12
|
+
*
|
|
13
|
+
* The `.snyk` file is YAML. dxkit carries no YAML dependency, so this
|
|
14
|
+
* is a small CONTROLLED serializer for exactly the policy shape Snyk's
|
|
15
|
+
* tooling reads — not a general YAML emitter. The structure is fixed:
|
|
16
|
+
*
|
|
17
|
+
* version: v1.25.0
|
|
18
|
+
* ignore:
|
|
19
|
+
* '<rule-id>':
|
|
20
|
+
* - '<path>':
|
|
21
|
+
* reason: "<reason>"
|
|
22
|
+
* expires: <ISO datetime> # omitted for a permanent ignore
|
|
23
|
+
* created: <ISO datetime>
|
|
24
|
+
* patch: {}
|
|
25
|
+
*
|
|
26
|
+
* Caveat surfaced to the user by the CLI: Snyk Code (SAST) honors
|
|
27
|
+
* `.snyk` ignores only when the org has Snyk's "consistent ignores"
|
|
28
|
+
* feature enabled; SCA/dependency ignores are standard. dxkit writes
|
|
29
|
+
* the file either way — the caller documents the prerequisite.
|
|
30
|
+
*/
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.SNYK_POLICY_VERSION = void 0;
|
|
33
|
+
exports.expiryToSnykDatetime = expiryToSnykDatetime;
|
|
34
|
+
exports.buildSnykPolicy = buildSnykPolicy;
|
|
35
|
+
/** Snyk policy schema version dxkit emits. Matches the version Snyk's
|
|
36
|
+
* own `snyk ignore` writes for the ignore/patch shape used here. */
|
|
37
|
+
exports.SNYK_POLICY_VERSION = 'v1.25.0';
|
|
38
|
+
/**
|
|
39
|
+
* Convert an allowlist entry's `expiresAt` (`YYYY-MM-DD`) into the ISO
|
|
40
|
+
* datetime Snyk's policy file expects. Returns `undefined` for a
|
|
41
|
+
* missing date so the caller emits a permanent ignore.
|
|
42
|
+
*/
|
|
43
|
+
function expiryToSnykDatetime(expiresAt) {
|
|
44
|
+
if (!expiresAt)
|
|
45
|
+
return undefined;
|
|
46
|
+
return `${expiresAt}T00:00:00.000Z`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Serialize ignores into `.snyk` policy YAML. Groups by rule id (the
|
|
50
|
+
* file's top-level ignore key), each carrying a list of per-path
|
|
51
|
+
* directives. Deterministic ordering (rules + paths sorted) so the
|
|
52
|
+
* committed file has stable diffs across runs.
|
|
53
|
+
*/
|
|
54
|
+
function buildSnykPolicy(ignores) {
|
|
55
|
+
const byRule = new Map();
|
|
56
|
+
for (const ig of ignores) {
|
|
57
|
+
const list = byRule.get(ig.ruleId) ?? [];
|
|
58
|
+
list.push(ig);
|
|
59
|
+
byRule.set(ig.ruleId, list);
|
|
60
|
+
}
|
|
61
|
+
const lines = [
|
|
62
|
+
'# Snyk (https://snyk.io) policy file, written by dxkit allowlist export.',
|
|
63
|
+
`version: ${exports.SNYK_POLICY_VERSION}`,
|
|
64
|
+
];
|
|
65
|
+
if (byRule.size === 0) {
|
|
66
|
+
lines.push('ignore: {}');
|
|
67
|
+
lines.push('patch: {}');
|
|
68
|
+
return lines.join('\n') + '\n';
|
|
69
|
+
}
|
|
70
|
+
lines.push('ignore:');
|
|
71
|
+
for (const ruleId of [...byRule.keys()].sort()) {
|
|
72
|
+
lines.push(` ${quoteKey(ruleId)}:`);
|
|
73
|
+
const perPath = byRule.get(ruleId);
|
|
74
|
+
// Stable order + one directive per unique path.
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
for (const ig of perPath.slice().sort((a, b) => a.path.localeCompare(b.path))) {
|
|
77
|
+
if (seen.has(ig.path))
|
|
78
|
+
continue;
|
|
79
|
+
seen.add(ig.path);
|
|
80
|
+
lines.push(` - ${quoteKey(ig.path)}:`);
|
|
81
|
+
lines.push(` reason: ${doubleQuote(ig.reason ?? '')}`);
|
|
82
|
+
if (ig.expires)
|
|
83
|
+
lines.push(` expires: ${ig.expires}`);
|
|
84
|
+
lines.push(` created: ${ig.created}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
lines.push('patch: {}');
|
|
88
|
+
return lines.join('\n') + '\n';
|
|
89
|
+
}
|
|
90
|
+
// ─── Minimal scalar quoting ───────────────────────────────────────────────
|
|
91
|
+
/** Single-quote a YAML key/scalar, escaping embedded single quotes by
|
|
92
|
+
* doubling (the YAML single-quote rule). Used for rule ids + paths,
|
|
93
|
+
* which never contain newlines. */
|
|
94
|
+
function quoteKey(value) {
|
|
95
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
96
|
+
}
|
|
97
|
+
/** Double-quote a YAML scalar. JSON's string encoding is a valid YAML
|
|
98
|
+
* double-quoted flow scalar (YAML is a JSON superset), so JSON.stringify
|
|
99
|
+
* gives correct escaping for reasons that may carry quotes, colons, or
|
|
100
|
+
* other special characters. */
|
|
101
|
+
function doubleQuote(value) {
|
|
102
|
+
return JSON.stringify(value);
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=snyk-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snyk-policy.js","sourceRoot":"","sources":["../../src/ingest/snyk-policy.ts"],"names":[],"mappings":"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"}
|
package/dist/ingest-cli.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ export interface IngestOptions {
|
|
|
13
13
|
project?: string;
|
|
14
14
|
generatedAt: string;
|
|
15
15
|
commitSha?: string;
|
|
16
|
+
/** Skip opt-in `.env` loading of `SNYK_*` credentials (`--no-env-file`). */
|
|
17
|
+
noEnvFile?: boolean;
|
|
18
|
+
/** Explicit `.env` path for `SNYK_*` credentials (`--env-file <path>`). */
|
|
19
|
+
envFile?: string;
|
|
16
20
|
}
|
|
17
21
|
export declare function runIngest(cwd: string, opts: IngestOptions): Promise<void>;
|
|
18
22
|
/** A REST failure that means "this plan can't read the API" — Enterprise-
|
package/dist/ingest-cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingest-cli.d.ts","sourceRoot":"","sources":["../src/ingest-cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ingest-cli.d.ts","sourceRoot":"","sources":["../src/ingest-cli.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;mEAE+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/E;AAmDD;0EAC0E;AAC1E,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEtD"}
|
package/dist/ingest-cli.js
CHANGED
|
@@ -47,6 +47,9 @@ exports.isNotEntitled = isNotEntitled;
|
|
|
47
47
|
* On plans without REST API access (Enterprise-only)
|
|
48
48
|
* this auto-falls-back to `snyk code test`; pass
|
|
49
49
|
* --snyk-cli to force that path and skip the REST try.
|
|
50
|
+
* SNYK_* credentials are read from the environment and,
|
|
51
|
+
* as a fallback, from a local `.env` (only SNYK_* keys;
|
|
52
|
+
* --no-env-file opts out, --env-file <path> overrides).
|
|
50
53
|
*
|
|
51
54
|
* Either way the result is written to `.dxkit/external/<engine>.json`,
|
|
52
55
|
* a committed snapshot every later scan reads — so the token is needed
|
|
@@ -60,6 +63,7 @@ const snyk_api_1 = require("./ingest/snyk-api");
|
|
|
60
63
|
const snyk_cli_1 = require("./ingest/snyk-cli");
|
|
61
64
|
const codeql_1 = require("./ingest/codeql");
|
|
62
65
|
const config_1 = require("./ingest/config");
|
|
66
|
+
const env_file_1 = require("./ingest/env-file");
|
|
63
67
|
const snapshot_1 = require("./ingest/snapshot");
|
|
64
68
|
const index_1 = require("./languages/index");
|
|
65
69
|
function isSourceEngine(s) {
|
|
@@ -83,6 +87,7 @@ async function runIngest(cwd, opts) {
|
|
|
83
87
|
logger.dim(' vyuh-dxkit ingest --sarif results.sarif');
|
|
84
88
|
logger.dim(' SNYK_TOKEN=… vyuh-dxkit ingest --from-snyk --org <id> --project <id>');
|
|
85
89
|
logger.dim(' SNYK_TOKEN=… vyuh-dxkit ingest --from-snyk --snyk-cli # free/team plans');
|
|
90
|
+
logger.dim(' vyuh-dxkit ingest --from-snyk # SNYK_* read from .env when present');
|
|
86
91
|
logger.dim(' vyuh-dxkit ingest --codeql # OSS / GitHub Advanced Security only');
|
|
87
92
|
process.exitCode = 1;
|
|
88
93
|
}
|
|
@@ -139,18 +144,32 @@ function isNotEntitled(message) {
|
|
|
139
144
|
return /\b403\b/.test(message) || /not entitled/i.test(message) || /api access/i.test(message);
|
|
140
145
|
}
|
|
141
146
|
async function ingestFromSnyk(cwd, opts) {
|
|
147
|
+
// Opt-in: lift ONLY SNYK_* keys from a local `.env` into the
|
|
148
|
+
// environment (real exported env / CI secret always wins). Disabled
|
|
149
|
+
// by --no-env-file; path overridable by --env-file. CI has no .env →
|
|
150
|
+
// no-op, so behavior there is unchanged.
|
|
151
|
+
const envLoad = (0, env_file_1.loadSnykEnv)(cwd, { noEnvFile: opts.noEnvFile, envFile: opts.envFile });
|
|
152
|
+
if (envLoad) {
|
|
153
|
+
for (const w of envLoad.warnings)
|
|
154
|
+
logger.warn(w);
|
|
155
|
+
if (envLoad.loadedKeys.length > 0) {
|
|
156
|
+
logger.dim(` Loaded ${envLoad.loadedKeys.join(', ')} from ${envLoad.path}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
142
159
|
const token = process.env.SNYK_TOKEN;
|
|
143
160
|
if (!token) {
|
|
144
161
|
logger.warn('SNYK_TOKEN is not set.');
|
|
145
|
-
logger.dim(' dxkit reads SNYK_TOKEN from the environment
|
|
146
|
-
|
|
162
|
+
logger.dim(' dxkit reads SNYK_TOKEN from the environment. It also auto-loads SNYK_* keys ' +
|
|
163
|
+
'from a local .env (only those keys, never the rest of the file).');
|
|
164
|
+
logger.dim(' Export it (`export SNYK_TOKEN=…`), put it in .env, or add it as a CI secret, then retry. ' +
|
|
165
|
+
'Use --no-env-file to skip .env, or --env-file <path> to point elsewhere.');
|
|
147
166
|
process.exitCode = 1;
|
|
148
167
|
return;
|
|
149
168
|
}
|
|
150
169
|
// Org/project resolve flag → persisted config (`.vyuh-dxkit.json:
|
|
151
170
|
// deepSast.snyk`) → environment, so a sourced shell or configured repo
|
|
152
|
-
// can run `ingest --from-snyk` with no flags.
|
|
153
|
-
//
|
|
171
|
+
// can run `ingest --from-snyk` with no flags. SNYK_ORG_ID / SNYK_PROJECT_ID
|
|
172
|
+
// also come from a local .env via loadSnykEnv above (SNYK_* keys only).
|
|
154
173
|
const cfg = (0, config_1.readDeepSastConfig)(cwd);
|
|
155
174
|
const orgId = opts.org ?? cfg.snyk?.orgId ?? process.env.SNYK_ORG_ID;
|
|
156
175
|
const projectId = opts.project ?? cfg.snyk?.projectId ?? process.env.SNYK_PROJECT_ID;
|
package/dist/ingest-cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ingest-cli.js","sourceRoot":"","sources":["../src/ingest-cli.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"ingest-cli.js","sourceRoot":"","sources":["../src/ingest-cli.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -202,7 +202,7 @@ npx vyuh-dxkit allowlist audit # expired / soon-to-expire / missing-r
|
|
|
202
202
|
npx vyuh-dxkit allowlist prune # remove expired entries
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
-
Run `audit` periodically — `accepted-risk` and `deferred` entries that pass their expiry should either be re-justified (renew expiry) or pruned (remove the entry; the underlying finding will re-flag on the next scan).
|
|
205
|
+
Run `audit` periodically — `accepted-risk` and `deferred` entries that pass their expiry should either be re-justified (renew expiry) or pruned (remove the entry; the underlying finding will re-flag on the next scan). For the full lifecycle — auditing orphaned entries after a re-baseline (`audit --against-baseline`), removing a single stale fingerprint (`allowlist remove`), and exporting Snyk-originated suppressions back to a `.snyk` (`allowlist export --snyk`) — hand off to the **dxkit-allowlist** skill.
|
|
206
206
|
|
|
207
207
|
### Stale annotations
|
|
208
208
|
|
|
@@ -235,13 +235,15 @@ Once a finding is processed (fixed, allowlisted, or accepted), the workflow depe
|
|
|
235
235
|
| Real risk neutralized externally (WAF, runtime guard) | `vyuh-dxkit allowlist add` with `category=mitigated-externally` + a reason describing the mitigation. Baseline unchanged. |
|
|
236
236
|
| Real risk, accepted by team, won't fix | `vyuh-dxkit allowlist add` with `category=accepted-risk` + `--expires=YYYY-MM-DD` (defaults 90 days). Acknowledged-severity required for high/critical. |
|
|
237
237
|
| Real risk, will fix later (tracked work) | `vyuh-dxkit allowlist add` with `category=deferred` + `--expires=YYYY-MM-DD`. The expiry forces re-review when the deadline passes. |
|
|
238
|
-
| Fix landed via a config change (e.g., new entry in `.dxkit-ignore`) | Re-baseline
|
|
238
|
+
| Fix landed via a config change (e.g., new entry in `.dxkit-ignore`) | Re-baseline through the `dxkit-baseline-refresh` CI workflow (NOT a local `baseline create --force` — see the note below). Commit the `.dxkit-ignore` change; let CI refresh + commit the baseline. |
|
|
239
239
|
| Brownfield acceptance (the whole CURRENT state is known mess; future regressions must be net-new) | Re-baseline with an explicit reason in the commit message. Reserve this for the deliberate "draw a line here" moment, not per-finding suppression. |
|
|
240
240
|
|
|
241
241
|
**Prefer the allowlist over re-baselining for per-finding decisions.** The allowlist carries a typed category + reason + (when relevant) expiry; the baseline carries only "this finding was here." Future maintainers reading `vyuh-dxkit allowlist show <fingerprint>` see WHY the suppression is in place; reading the baseline file shows only that the finding existed at capture time. Per-finding decisions belong in the allowlist; codebase-wide brownfield acceptance belongs in the baseline.
|
|
242
242
|
|
|
243
243
|
**Never** re-baseline a finding silently — the commit message should explain why the regression is accepted. Future maintainers reading `git log .dxkit/baselines/` should see the rationale.
|
|
244
244
|
|
|
245
|
+
**Refresh the baseline in CI, not locally.** When a re-baseline is the right call, run it through the bundled `dxkit-baseline-refresh` workflow (or a runner pinned to CI's scanner versions) — not a local `npx vyuh-dxkit baseline create --force`. A local refresh records your machine's semgrep / npm-audit / jscpd versions in the committed baseline; when they differ from CI's, the next PR's guardrail surfaces spurious `TOOLING-DRIFT` warnings and phantom "resolved" findings. A local `--force` is fine only for the very first capture or a throwaway experiment.
|
|
246
|
+
|
|
245
247
|
## Workflow guardrail
|
|
246
248
|
|
|
247
249
|
After fixing N findings, run the guardrail check before pushing:
|
|
@@ -270,4 +272,4 @@ In those cases: `vyuh-dxkit allowlist add` is the right tool for per-finding dec
|
|
|
270
272
|
- For hook-related issues during a fix push → `dxkit-hooks` skill
|
|
271
273
|
- For re-running reports between fixes → `dxkit-reports` skill
|
|
272
274
|
- For broken dxkit install (hooks not firing, vyuh-dxkit not on PATH) → `dxkit-fix` skill
|
|
273
|
-
- For allowlist management beyond the per-finding `add` path
|
|
275
|
+
- For allowlist management beyond the per-finding `add` path — auditing existing entries (including orphans after a re-baseline), removing stale fingerprints, pruning expired ones, exporting to a `.snyk`, or reviewing the team's overall suppression posture → **dxkit-allowlist** skill
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dxkit-allowlist
|
|
3
|
+
description: Manage the dxkit allowlist over its whole lifecycle — list, inspect, audit (including orphaned entries after a re-baseline), remove stale entries, prune expired ones, and export Snyk-originated suppressions to a .snyk policy. Use when the user says "review our allowlist", "what suppressions do we have", "this allowlist entry is stale", "remove this fingerprint", "the allowlist drifted after re-baselining", "audit our accepted-risk entries", or "push our Snyk ignores back to Snyk". For the fix-vs-suppress DECISION and adding a new entry, defer to dxkit-action.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dxkit-allowlist
|
|
7
|
+
|
|
8
|
+
The allowlist is dxkit's per-finding suppression surface: a reviewed finding that the team has categorized (`false-positive`, `test-fixture`, `mitigated-externally`, `accepted-risk`, `deferred`) with a reason, so the guardrail lets it pass on future runs. It's the single source of truth across every scanner — native semgrep/gitleaks and ingested Snyk Code / CodeQL findings alike, all keyed on one fingerprint.
|
|
9
|
+
|
|
10
|
+
This skill manages the allowlist's **lifecycle**: reviewing what's there, keeping it honest, and propagating decisions outward. For the upstream question — *should this be fixed instead of suppressed, and how do I add an entry* — that decision and the `add` path live in **dxkit-action**. Fix first; suppress second.
|
|
11
|
+
|
|
12
|
+
## The lifecycle at a glance
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
add ──▶ list / show ──▶ audit ──▶ { renew | remove | prune } ──▶ export --snyk
|
|
16
|
+
(dxkit-action) inspect keep honest clean up propagate to Snyk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Review what's suppressed
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx vyuh-dxkit allowlist list # every entry (text); --json for structured
|
|
23
|
+
npx vyuh-dxkit allowlist show <fingerprint> # one entry's full detail
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Reading is always safe — no mutation. Use these to brief the team on the overall suppression posture before a release or audit.
|
|
27
|
+
|
|
28
|
+
## Audit — keep the allowlist honest
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx vyuh-dxkit allowlist audit # expired / soon-to-expire / missing-rationale
|
|
32
|
+
npx vyuh-dxkit allowlist audit --soon-days=30 # widen the soon-to-expire window
|
|
33
|
+
npx vyuh-dxkit allowlist audit --against-baseline # ALSO flag orphaned entries
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`audit` partitions entries into actionable buckets:
|
|
37
|
+
|
|
38
|
+
- **expired** — past their `expiresAt`. The suppression no longer applies; the finding will re-flag on the next scan. Prune or renew.
|
|
39
|
+
- **soon-to-expire** — within the window (default 14 days). `accepted-risk` / `deferred` entries approaching expiry should be re-justified or removed.
|
|
40
|
+
- **missing-rationale** — no reason on the entry (only happens in sanitized mode when the gitignored reasons sidecar is absent).
|
|
41
|
+
- **orphaned** — *only with `--against-baseline`*. The entry's fingerprint matches no finding in the committed baseline.
|
|
42
|
+
|
|
43
|
+
### Orphaned entries — flag, never bulk-remove
|
|
44
|
+
|
|
45
|
+
`--against-baseline` reads the committed baseline and reports entries whose fingerprint isn't present in the current finding set (it counts both each finding's own fingerprint and any cross-tool fingerprints absorbed into it, so an entry keyed on a collapsed contributor is *not* falsely flagged).
|
|
46
|
+
|
|
47
|
+
**An orphan is not automatically stale.** Two things produce orphans:
|
|
48
|
+
|
|
49
|
+
1. **The finding is genuinely gone** (fixed, file deleted) → the entry is dead weight; remove it.
|
|
50
|
+
2. **Re-baselining churned the fingerprint** — semgrep is nondeterministic run-to-run, and cross-tool dedup can shift which tool's fingerprint represents a merged finding. The suppressed finding may still exist intermittently. Removing the entry would let it block a future PR.
|
|
51
|
+
|
|
52
|
+
So treat the orphaned bucket as a **review queue**: confirm each finding is truly gone (re-run the analyzer and check the fingerprint is absent), *then* remove. Never script a bulk-remove of the orphaned set.
|
|
53
|
+
|
|
54
|
+
## Remove a single entry
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx vyuh-dxkit allowlist remove <fingerprint>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Deletes one file-level entry. Use this for a confirmed-orphaned entry, or any stale-but-unexpired entry (which `prune` won't touch — `prune` removes only *expired* entries). No more hand-editing `.dxkit/allowlist.json`.
|
|
61
|
+
|
|
62
|
+
## Prune expired entries
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx vyuh-dxkit allowlist prune --dry-run # preview what would go
|
|
66
|
+
npx vyuh-dxkit allowlist prune # remove all expired entries
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`prune` is the bulk counterpart to `remove`, scoped to expired entries only (those are unambiguously inactive). Run it periodically; renew anything still relevant before pruning.
|
|
70
|
+
|
|
71
|
+
## The re-baseline → re-point flow (self-serve)
|
|
72
|
+
|
|
73
|
+
After a baseline refresh, some fingerprints churn and a few valid suppressions orphan. The self-serve recovery:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx vyuh-dxkit allowlist audit --against-baseline # 1. discover orphans
|
|
77
|
+
# 2. for each orphan: re-run the analyzer, confirm the finding is truly gone
|
|
78
|
+
npx vyuh-dxkit vulnerabilities # (grep output for the fingerprint)
|
|
79
|
+
npx vyuh-dxkit allowlist remove <fingerprint> # 3a. gone → remove
|
|
80
|
+
npx vyuh-dxkit allowlist add --fingerprint=<new> … # 3b. churned → re-point to the new fp (see dxkit-action)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
> **Refresh the baseline in CI, not on your laptop.** A local `baseline create --force` bakes your machine's scanner versions into the committed baseline, which produces spurious tooling-drift warnings and phantom "resolved" findings on the next PR — and *causes* exactly this fingerprint churn. Use the bundled `dxkit-baseline-refresh` workflow (workflow_dispatch) so the canonical baseline is captured with CI's scanner versions. See **dxkit-ingest** for the refresh-job pattern.
|
|
84
|
+
|
|
85
|
+
## Export to Snyk — propagate suppressions outward
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npx vyuh-dxkit allowlist export --snyk # writes ./.snyk
|
|
89
|
+
npx vyuh-dxkit allowlist export --snyk --out=path/to/.snyk
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
When the team allowlists a **Snyk-originated** finding (one ingested via `dxkit-ingest`, `tool: snyk-code`), the decision lives only in dxkit by default — Snyk's own gate (`snyk code test`, the Snyk UI) still reports it as open. `export --snyk` closes that loop: it writes a `.snyk` policy ignoring every Snyk Code finding that maps to an *active* allowlist entry, keyed on the Snyk rule id + path, carrying the entry's reason + expiry. Expired entries are skipped; native semgrep/gitleaks findings don't export (no Snyk equivalent).
|
|
93
|
+
|
|
94
|
+
This is the outbound mirror of the inbound sync dxkit already does (it honors Snyk's SARIF `result.suppressions` at ingest). The two are round-trip stable — an exported ignore re-read from Snyk's SARIF is suppressed, not double-counted.
|
|
95
|
+
|
|
96
|
+
**Prerequisite:** Snyk Code (SAST) honors `.snyk` ignores only when the org has Snyk's "consistent ignores" feature enabled; SCA/dependency ignores are standard. Commit the `.snyk` so it applies in CI. It's opt-in — if dxkit is the only gate, you don't need it.
|
|
97
|
+
|
|
98
|
+
## Stale inline annotations
|
|
99
|
+
|
|
100
|
+
Inline `dxkit-allow:` annotations are a different surface (source-anchored, managed by `dxkit-action`'s `add` path). If the underlying finding is fixed but the annotation lingers, the next scan emits a `stale-allow` finding pointing at the orphaned comment. The fix is always to delete the comment — dxkit refuses to allowlist a stale-allow.
|
|
101
|
+
|
|
102
|
+
## Hand-offs
|
|
103
|
+
|
|
104
|
+
- For the **fix-vs-suppress decision** and **adding** a new entry (inline or file-level, the typed-category table, the canonical `add` path) → **dxkit-action**.
|
|
105
|
+
- For **ingesting** Snyk/CodeQL findings in the first place, and the **CI baseline/deep-SAST refresh** jobs → **dxkit-ingest**.
|
|
106
|
+
- For **ignore-file** (`.dxkit-ignore`) edits and policy tuning → **dxkit-config**.
|
|
107
|
+
- For a **broken install** (guardrail not firing, command not found) → **dxkit-fix**.
|
|
@@ -155,7 +155,7 @@ A `npx vyuh-dxkit doctor` or `tools list` showing tools as missing **when they a
|
|
|
155
155
|
1. Create / edit `.dxkit/tools.json` and add that directory to `probePaths`.
|
|
156
156
|
2. Re-run `npx vyuh-dxkit tools list` — the tool should now show as available.
|
|
157
157
|
3. If the user wants dxkit to *install* tools into a specific dir, set `installDir`, then `npx vyuh-dxkit tools install`.
|
|
158
|
-
4. Regenerate the baseline so it reflects the now-available scanners
|
|
158
|
+
4. Regenerate the baseline so it reflects the now-available scanners — through the `dxkit-baseline-refresh` CI workflow, not a local `baseline create --force` (see "What NOT to do" for why).
|
|
159
159
|
|
|
160
160
|
## Workflow
|
|
161
161
|
|
|
@@ -163,11 +163,11 @@ When the user asks for a config change:
|
|
|
163
163
|
|
|
164
164
|
1. Identify which file owns the concern (path exclusion → `.dxkit-ignore`; severity routing → `.dxkit/policy.json`; detection override → `.npx vyuh-dxkit.json`).
|
|
165
165
|
2. Open the file, propose the edit, confirm.
|
|
166
|
-
3.
|
|
167
|
-
4. Commit
|
|
166
|
+
3. If exclusions changed, refresh the baseline so it doesn't carry stale findings from the now-excluded paths — via the `dxkit-baseline-refresh` CI workflow, not a local `baseline create --force` (see "What NOT to do").
|
|
167
|
+
4. Commit the config file; let CI refresh + commit the baseline.
|
|
168
168
|
|
|
169
169
|
## What NOT to do
|
|
170
170
|
|
|
171
171
|
- Don't edit `.dxkit/cache/` or `.dxkit/reports/` — they're regenerated on every run (gitignored).
|
|
172
|
-
- Don't manually mutate `.dxkit/baselines/main.json` —
|
|
172
|
+
- Don't manually mutate `.dxkit/baselines/main.json` — regenerate it. And regenerate it in CI (the `dxkit-baseline-refresh` workflow), NOT with a local `baseline create --force`: a local refresh bakes your machine's scanner versions into the committed baseline, so the next PR's guardrail emits spurious `TOOLING-DRIFT` warnings and phantom "resolved" findings when CI's versions differ. A local `--force` is fine only for the first capture or a throwaway experiment.
|
|
173
173
|
- Don't add `.dxkit/` to `.dxkit-ignore` — dxkit itself doesn't scan its own outputs.
|