cngkit 1.1.5 → 1.1.7
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 +7 -4
- package/dist/{chunk-TZRXQ6GR.js → chunk-EQEIX7N5.js} +3 -3
- package/dist/chunk-EQEIX7N5.js.map +1 -0
- package/dist/chunk-HUZZPV5E.js +411 -0
- package/dist/chunk-HUZZPV5E.js.map +1 -0
- package/dist/{chunk-YY2VGJ5N.js → chunk-MLKBG5YJ.js} +2 -2
- package/dist/{chunk-YY2VGJ5N.js.map → chunk-MLKBG5YJ.js.map} +1 -1
- package/dist/{chunk-UXMP5Z5P.js → chunk-QEZQGKFX.js} +13 -1364
- package/dist/{chunk-UXMP5Z5P.js.map → chunk-QEZQGKFX.js.map} +1 -1
- package/dist/chunk-QZEB4VMX.js +32 -0
- package/dist/chunk-QZEB4VMX.js.map +1 -0
- package/dist/chunk-TZKXST4G.js +291 -0
- package/dist/chunk-TZKXST4G.js.map +1 -0
- package/dist/{chunk-SSRUN6G5.js → chunk-VI5XQH3U.js} +3 -18
- package/dist/chunk-VI5XQH3U.js.map +1 -0
- package/dist/chunk-XDXRVTPK.js +18 -0
- package/dist/chunk-XDXRVTPK.js.map +1 -0
- package/dist/{chunk-SNTLRTQ2.js → chunk-Z4DDLEWR.js} +3 -3
- package/dist/chunk-Z4DDLEWR.js.map +1 -0
- package/dist/cli.js +8 -6
- package/dist/cli.js.map +1 -1
- package/dist/commands/coderoom/index.js +6 -5
- package/dist/commands/coderoom/index.js.map +1 -1
- package/dist/commands/coderoom/join.js +6 -5
- package/dist/commands/coderoom/join.js.map +1 -1
- package/dist/commands/coderoom/share.js +6 -5
- package/dist/commands/coderoom/share.js.map +1 -1
- package/dist/commands/index.js +5 -4
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/knowledges/audiences.js +8 -5
- package/dist/commands/knowledges/audiences.js.map +1 -1
- package/dist/commands/knowledges/files.js +8 -5
- package/dist/commands/knowledges/files.js.map +1 -1
- package/dist/commands/knowledges/glob.js +8 -5
- package/dist/commands/knowledges/glob.js.map +1 -1
- package/dist/commands/knowledges/grep.js +8 -5
- package/dist/commands/knowledges/grep.js.map +1 -1
- package/dist/commands/knowledges/index.js +6 -5
- package/dist/commands/knowledges/index.js.map +1 -1
- package/dist/commands/knowledges/list.js +8 -5
- package/dist/commands/knowledges/list.js.map +1 -1
- package/dist/commands/knowledges/read.js +8 -5
- package/dist/commands/knowledges/read.js.map +1 -1
- package/dist/commands/knowledges/search.js +8 -5
- package/dist/commands/knowledges/search.js.map +1 -1
- package/dist/commands/knowledges/status.js +8 -5
- package/dist/commands/knowledges/status.js.map +1 -1
- package/dist/commands/login.js +49 -6
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/scrub.js +256 -7
- package/dist/commands/scrub.js.map +1 -1
- package/dist/commands/transcripts.js +377 -6
- package/dist/commands/transcripts.js.map +1 -1
- package/package.json +15 -14
- package/dist/chunk-SNTLRTQ2.js.map +0 -1
- package/dist/chunk-SSRUN6G5.js.map +0 -1
- package/dist/chunk-TZRXQ6GR.js.map +0 -1
package/dist/commands/scrub.js
CHANGED
|
@@ -1,20 +1,269 @@
|
|
|
1
|
-
import {
|
|
2
|
-
runScrubCommand
|
|
3
|
-
} from "../chunk-UXMP5Z5P.js";
|
|
4
|
-
import "../chunk-TZRXQ6GR.js";
|
|
5
1
|
import {
|
|
6
2
|
GlobalOptionsSchema,
|
|
7
3
|
OptionalPathArgsSchema
|
|
8
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-MLKBG5YJ.js";
|
|
9
5
|
import {
|
|
10
6
|
CommandRunner
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import "../chunk-
|
|
7
|
+
} from "../chunk-Z4DDLEWR.js";
|
|
8
|
+
import "../chunk-XDXRVTPK.js";
|
|
13
9
|
import "../chunk-PZ5AY32C.js";
|
|
14
10
|
|
|
15
11
|
// src/commands/scrub.tsx
|
|
16
12
|
import { option } from "pastel";
|
|
17
13
|
import { z } from "zod";
|
|
14
|
+
|
|
15
|
+
// src/features/scrub/run-scrub-command.ts
|
|
16
|
+
import process from "process";
|
|
17
|
+
|
|
18
|
+
// src/features/scrub/masker.ts
|
|
19
|
+
import fs from "fs/promises";
|
|
20
|
+
import path from "path";
|
|
21
|
+
async function scrubFindingsInline(targetPath, findings) {
|
|
22
|
+
const target = await resolveScrubTarget(targetPath);
|
|
23
|
+
const findingsByFile = groupFindingsBySafePath(target, findings);
|
|
24
|
+
let filesChanged = 0;
|
|
25
|
+
let replacements = 0;
|
|
26
|
+
for (const [absolutePath, fileFindings] of findingsByFile.safeFindingsByPath) {
|
|
27
|
+
const originalContent = await fs.readFile(absolutePath, "utf8");
|
|
28
|
+
let scrubbedContent = originalContent;
|
|
29
|
+
let fileReplacements = 0;
|
|
30
|
+
for (const finding of fileFindings) {
|
|
31
|
+
const placeholder = formatSecretPlaceholder(finding);
|
|
32
|
+
const nextContent = scrubbedContent.split(finding.rawSecret).join(placeholder);
|
|
33
|
+
if (nextContent !== scrubbedContent) {
|
|
34
|
+
fileReplacements += countOccurrences(scrubbedContent, finding.rawSecret);
|
|
35
|
+
scrubbedContent = nextContent;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (scrubbedContent !== originalContent) {
|
|
39
|
+
await fs.writeFile(absolutePath, scrubbedContent);
|
|
40
|
+
filesChanged += 1;
|
|
41
|
+
replacements += fileReplacements;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
filesChanged,
|
|
46
|
+
replacements,
|
|
47
|
+
skippedFindings: findingsByFile.skippedFindings
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function formatSecretPlaceholder(finding) {
|
|
51
|
+
return `[CNGKIT_SECRET:${sanitizePlaceholderPart(finding.detectorName)}:${finding.verified ? "verified" : "unverified"}]`;
|
|
52
|
+
}
|
|
53
|
+
function groupFindingsBySafePath(target, findings) {
|
|
54
|
+
const safeFindingsByPath = /* @__PURE__ */ new Map();
|
|
55
|
+
let skippedFindings = 0;
|
|
56
|
+
for (const finding of findings) {
|
|
57
|
+
const absolutePath = resolveSafeFindingPath(target, finding.filePath);
|
|
58
|
+
if (!absolutePath) {
|
|
59
|
+
skippedFindings += 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const fileFindings = safeFindingsByPath.get(absolutePath) ?? [];
|
|
63
|
+
fileFindings.push(finding);
|
|
64
|
+
safeFindingsByPath.set(absolutePath, fileFindings);
|
|
65
|
+
}
|
|
66
|
+
return { safeFindingsByPath, skippedFindings };
|
|
67
|
+
}
|
|
68
|
+
async function resolveScrubTarget(targetPath) {
|
|
69
|
+
const absoluteTargetPath = path.resolve(targetPath);
|
|
70
|
+
const stat = await fs.stat(absoluteTargetPath);
|
|
71
|
+
if (stat.isFile()) {
|
|
72
|
+
return {
|
|
73
|
+
rootDir: path.dirname(absoluteTargetPath),
|
|
74
|
+
allowedFilePath: absoluteTargetPath
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
rootDir: absoluteTargetPath
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function resolveSafeFindingPath(target, findingFilePath) {
|
|
82
|
+
const absolutePath = path.isAbsolute(findingFilePath) ? path.resolve(findingFilePath) : path.resolve(target.rootDir, findingFilePath);
|
|
83
|
+
if (target.allowedFilePath) {
|
|
84
|
+
return absolutePath === target.allowedFilePath ? absolutePath : void 0;
|
|
85
|
+
}
|
|
86
|
+
const normalizedRoot = `${path.resolve(target.rootDir)}${path.sep}`;
|
|
87
|
+
if (absolutePath === path.resolve(target.rootDir) || absolutePath.startsWith(normalizedRoot)) {
|
|
88
|
+
return absolutePath;
|
|
89
|
+
}
|
|
90
|
+
return void 0;
|
|
91
|
+
}
|
|
92
|
+
function countOccurrences(value, search) {
|
|
93
|
+
if (!search) {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
return value.split(search).length - 1;
|
|
97
|
+
}
|
|
98
|
+
function sanitizePlaceholderPart(value) {
|
|
99
|
+
return value.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "Unknown";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/features/scrub/trufflehog.ts
|
|
103
|
+
import { spawn } from "child_process";
|
|
104
|
+
import path2 from "path";
|
|
105
|
+
|
|
106
|
+
// src/features/scrub/findings.ts
|
|
107
|
+
function parseTruffleHogJsonLines(output) {
|
|
108
|
+
const findings = [];
|
|
109
|
+
for (const line of output.split(/\r?\n/)) {
|
|
110
|
+
const trimmedLine = line.trim();
|
|
111
|
+
if (!trimmedLine.startsWith("{")) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const parsedLine = parseJsonRecord(trimmedLine);
|
|
115
|
+
if (!parsedLine) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const finding = parseTruffleHogFinding(parsedLine);
|
|
119
|
+
if (finding) {
|
|
120
|
+
findings.push(finding);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return findings;
|
|
124
|
+
}
|
|
125
|
+
function parseTruffleHogFinding(finding) {
|
|
126
|
+
const rawSecret = readString(finding.Raw) || readString(finding.RawV2);
|
|
127
|
+
const filePath = readFilesystemFilePath(finding);
|
|
128
|
+
if (!rawSecret || !filePath) {
|
|
129
|
+
return void 0;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
detectorName: readString(finding.DetectorName) || "Unknown",
|
|
133
|
+
filePath,
|
|
134
|
+
line: readFilesystemLine(finding),
|
|
135
|
+
rawSecret,
|
|
136
|
+
redactedSecret: readString(finding.Redacted) || "",
|
|
137
|
+
verified: finding.Verified === true
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function readFilesystemFilePath(finding) {
|
|
141
|
+
const filesystem = readFilesystemMetadata(finding);
|
|
142
|
+
if (!filesystem) {
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
return readString(filesystem.file) || readString(filesystem.path);
|
|
146
|
+
}
|
|
147
|
+
function readFilesystemLine(finding) {
|
|
148
|
+
const filesystem = readFilesystemMetadata(finding);
|
|
149
|
+
if (!filesystem) {
|
|
150
|
+
return void 0;
|
|
151
|
+
}
|
|
152
|
+
return typeof filesystem.line === "number" ? filesystem.line : void 0;
|
|
153
|
+
}
|
|
154
|
+
function readFilesystemMetadata(finding) {
|
|
155
|
+
const sourceMetadata = readRecord(finding.SourceMetadata);
|
|
156
|
+
const data = readRecord(sourceMetadata?.Data);
|
|
157
|
+
return readRecord(data?.Filesystem);
|
|
158
|
+
}
|
|
159
|
+
function parseJsonRecord(line) {
|
|
160
|
+
try {
|
|
161
|
+
return readRecord(JSON.parse(line));
|
|
162
|
+
} catch {
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function isJsonRecord(value) {
|
|
167
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const prototype = Object.getPrototypeOf(value);
|
|
171
|
+
return prototype === Object.prototype || prototype === null;
|
|
172
|
+
}
|
|
173
|
+
function readRecord(value) {
|
|
174
|
+
return isJsonRecord(value) ? value : void 0;
|
|
175
|
+
}
|
|
176
|
+
function readString(value) {
|
|
177
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/features/scrub/trufflehog.ts
|
|
181
|
+
async function scanFilesystemWithTruffleHog(targetPath, options2) {
|
|
182
|
+
const absoluteTargetPath = path2.resolve(options2.cwd, targetPath);
|
|
183
|
+
const { stdout } = await runTruffleHog([
|
|
184
|
+
"filesystem",
|
|
185
|
+
absoluteTargetPath,
|
|
186
|
+
"--json",
|
|
187
|
+
"--no-update",
|
|
188
|
+
"--force-skip-binaries",
|
|
189
|
+
"--force-skip-archives"
|
|
190
|
+
]);
|
|
191
|
+
return parseTruffleHogJsonLines(stdout);
|
|
192
|
+
}
|
|
193
|
+
function runTruffleHog(args2) {
|
|
194
|
+
return new Promise((resolve, reject) => {
|
|
195
|
+
const child = spawn("trufflehog", args2, {
|
|
196
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
197
|
+
});
|
|
198
|
+
const stdoutChunks = [];
|
|
199
|
+
const stderrChunks = [];
|
|
200
|
+
child.stdout.on("data", (chunk) => {
|
|
201
|
+
stdoutChunks.push(chunk);
|
|
202
|
+
});
|
|
203
|
+
child.stderr.on("data", (chunk) => {
|
|
204
|
+
stderrChunks.push(chunk);
|
|
205
|
+
});
|
|
206
|
+
child.on("error", (error) => {
|
|
207
|
+
if (error.code === "ENOENT") {
|
|
208
|
+
reject(
|
|
209
|
+
new Error(
|
|
210
|
+
"TruffleHog is not installed. Install it with `brew install trufflehog` or see https://github.com/trufflesecurity/trufflehog."
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
reject(error);
|
|
216
|
+
});
|
|
217
|
+
child.on("close", (exitCode) => {
|
|
218
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf8");
|
|
219
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8");
|
|
220
|
+
if (exitCode && exitCode !== 0) {
|
|
221
|
+
reject(new Error(`TruffleHog scan failed with exit code ${exitCode}: ${stderr.trim()}`));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
resolve({ stdout, stderr });
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/features/scrub/run-scrub-command.ts
|
|
230
|
+
async function runScrubCommand(targetPath, options2, output, dependencies) {
|
|
231
|
+
const scrubTargetPath = targetPath ?? ".";
|
|
232
|
+
const scanFilesystem = dependencies?.scanFilesystem ?? ((scanTargetPath) => scanFilesystemWithTruffleHog(scanTargetPath, {
|
|
233
|
+
cwd: process.cwd()
|
|
234
|
+
}));
|
|
235
|
+
const findings = await scanFilesystem(scrubTargetPath);
|
|
236
|
+
if (findings.length === 0) {
|
|
237
|
+
output.info("No secrets found.");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
output.info(formatScrubReport(findings));
|
|
241
|
+
if (!options2.yes) {
|
|
242
|
+
if (options2.mask) {
|
|
243
|
+
throw new Error("Inline scrubbing rewrites files. Re-run with --yes to continue.");
|
|
244
|
+
}
|
|
245
|
+
output.info("Report only. Re-run with --yes to scrub files inline.");
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const result = await scrubFindingsInline(scrubTargetPath, findings);
|
|
249
|
+
output.info(
|
|
250
|
+
`Masked ${result.replacements} secret${result.replacements === 1 ? "" : "s"} in ${result.filesChanged} file${result.filesChanged === 1 ? "" : "s"}.`
|
|
251
|
+
);
|
|
252
|
+
if (result.skippedFindings > 0) {
|
|
253
|
+
output.info(`Skipped ${result.skippedFindings} finding(s) outside the scrub root.`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function formatScrubReport(findings) {
|
|
257
|
+
const lines = [`Found ${findings.length} possible secret${findings.length === 1 ? "" : "s"}:`];
|
|
258
|
+
for (const finding of findings) {
|
|
259
|
+
const location = finding.line ? `${finding.filePath}:${finding.line}` : finding.filePath;
|
|
260
|
+
const status = finding.verified ? "verified" : "unverified";
|
|
261
|
+
lines.push(`- ${location} ${finding.detectorName} ${status}`);
|
|
262
|
+
}
|
|
263
|
+
return lines.join("\n");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/commands/scrub.tsx
|
|
18
267
|
import { jsx } from "react/jsx-runtime";
|
|
19
268
|
var description = "Scan for secrets with TruffleHog and optionally mask them inline";
|
|
20
269
|
var args = OptionalPathArgsSchema;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/scrub.tsx"],"sourcesContent":["import { option } from \"pastel\";\nimport { z } from \"zod\";\n\nimport { GlobalOptionsSchema, OptionalPathArgsSchema } from \"../cli-options.js\";\nimport { runScrubCommand, type ScrubCommandOptions } from \"../command-actions.js\";\nimport { CommandRunner } from \"../ink/command-runner.js\";\n\nexport const description = \"Scan for secrets with TruffleHog and optionally mask them inline\";\nexport const args = OptionalPathArgsSchema;\nexport const options = GlobalOptionsSchema.extend({\n mask: z\n .boolean()\n .optional()\n .describe(\n option({\n description: \"Compatibility alias for inline scrubbing; --yes is still required\",\n })\n ),\n yes: z\n .boolean()\n .optional()\n .describe(\n option({\n description: \"Confirm and run inline scrubbing\",\n })\n ),\n});\n\ntype ScrubCommandProps = {\n readonly args: z.infer<typeof args>;\n readonly options: ScrubCommandOptions;\n};\n\nexport default function ScrubCommand({ args, options }: ScrubCommandProps) {\n return <CommandRunner run={(output) => runScrubCommand(args[0], options, output)} />;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAAA,SAAS,cAAc;AACvB,SAAS,SAAS;AAiCT;AA3BF,IAAM,cAAc;AACpB,IAAM,OAAO;AACb,IAAM,UAAU,oBAAoB,OAAO;AAAA,EAChD,MAAM,EACH,QAAQ,EACR,SAAS,EACT;AAAA,IACC,OAAO;AAAA,MACL,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EACF,KAAK,EACF,QAAQ,EACR,SAAS,EACT;AAAA,IACC,OAAO;AAAA,MACL,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AACJ,CAAC;AAOc,SAAR,aAA8B,EAAE,MAAAA,OAAM,SAAAC,SAAQ,GAAsB;AACzE,SAAO,oBAAC,iBAAc,KAAK,CAAC,WAAW,gBAAgBD,MAAK,CAAC,GAAGC,UAAS,MAAM,GAAG;AACpF;","names":["args","options"]}
|
|
1
|
+
{"version":3,"sources":["../../src/commands/scrub.tsx","../../src/features/scrub/run-scrub-command.ts","../../src/features/scrub/masker.ts","../../src/features/scrub/trufflehog.ts","../../src/features/scrub/findings.ts"],"sourcesContent":["import { option } from \"pastel\";\nimport { z } from \"zod\";\n\nimport { GlobalOptionsSchema, OptionalPathArgsSchema } from \"../cli/options.js\";\nimport { runScrubCommand, type ScrubCommandOptions } from \"../features/scrub/run-scrub-command.js\";\nimport { CommandRunner } from \"../cli/command-runner.js\";\n\nexport const description = \"Scan for secrets with TruffleHog and optionally mask them inline\";\nexport const args = OptionalPathArgsSchema;\nexport const options = GlobalOptionsSchema.extend({\n mask: z\n .boolean()\n .optional()\n .describe(\n option({\n description: \"Compatibility alias for inline scrubbing; --yes is still required\",\n })\n ),\n yes: z\n .boolean()\n .optional()\n .describe(\n option({\n description: \"Confirm and run inline scrubbing\",\n })\n ),\n});\n\ntype ScrubCommandProps = {\n readonly args: z.infer<typeof args>;\n readonly options: ScrubCommandOptions;\n};\n\nexport default function ScrubCommand({ args, options }: ScrubCommandProps) {\n return <CommandRunner run={(output) => runScrubCommand(args[0], options, output)} />;\n}\n","import process from \"node:process\";\n\nimport type { GlobalCommandOptions } from \"../../shared/config.js\";\nimport type { CommandOutput } from \"../../shared/output.js\";\nimport type { ScrubFinding } from \"./findings.js\";\nimport { scrubFindingsInline } from \"./masker.js\";\nimport { scanFilesystemWithTruffleHog } from \"./trufflehog.js\";\n\nexport type ScrubCommandOptions = GlobalCommandOptions & {\n mask?: boolean;\n yes?: boolean;\n};\n\nexport type ScrubCommandDependencies = {\n scanFilesystem(targetPath: string): Promise<ScrubFinding[]>;\n};\n\nexport async function runScrubCommand(\n targetPath: string | undefined,\n options: ScrubCommandOptions,\n output: CommandOutput,\n dependencies?: ScrubCommandDependencies\n): Promise<void> {\n const scrubTargetPath = targetPath ?? \".\";\n const scanFilesystem =\n dependencies?.scanFilesystem ??\n ((scanTargetPath: string) =>\n scanFilesystemWithTruffleHog(scanTargetPath, {\n cwd: process.cwd(),\n }));\n const findings = await scanFilesystem(scrubTargetPath);\n\n if (findings.length === 0) {\n output.info(\"No secrets found.\");\n return;\n }\n\n output.info(formatScrubReport(findings));\n\n if (!options.yes) {\n if (options.mask) {\n throw new Error(\"Inline scrubbing rewrites files. Re-run with --yes to continue.\");\n }\n\n output.info(\"Report only. Re-run with --yes to scrub files inline.\");\n return;\n }\n\n const result = await scrubFindingsInline(scrubTargetPath, findings);\n output.info(\n `Masked ${result.replacements} secret${result.replacements === 1 ? \"\" : \"s\"} in ${\n result.filesChanged\n } file${result.filesChanged === 1 ? \"\" : \"s\"}.`\n );\n\n if (result.skippedFindings > 0) {\n output.info(`Skipped ${result.skippedFindings} finding(s) outside the scrub root.`);\n }\n}\n\nfunction formatScrubReport(findings: ScrubFinding[]): string {\n const lines = [`Found ${findings.length} possible secret${findings.length === 1 ? \"\" : \"s\"}:`];\n\n for (const finding of findings) {\n const location = finding.line ? `${finding.filePath}:${finding.line}` : finding.filePath;\n const status = finding.verified ? \"verified\" : \"unverified\";\n lines.push(`- ${location} ${finding.detectorName} ${status}`);\n }\n\n return lines.join(\"\\n\");\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { ScrubFinding } from \"./findings.js\";\n\nexport type InlineScrubResult = {\n filesChanged: number;\n replacements: number;\n skippedFindings: number;\n};\n\nexport async function scrubFindingsInline(\n targetPath: string,\n findings: ScrubFinding[]\n): Promise<InlineScrubResult> {\n const target = await resolveScrubTarget(targetPath);\n const findingsByFile = groupFindingsBySafePath(target, findings);\n let filesChanged = 0;\n let replacements = 0;\n\n for (const [absolutePath, fileFindings] of findingsByFile.safeFindingsByPath) {\n const originalContent = await fs.readFile(absolutePath, \"utf8\");\n let scrubbedContent = originalContent;\n let fileReplacements = 0;\n\n for (const finding of fileFindings) {\n const placeholder = formatSecretPlaceholder(finding);\n const nextContent = scrubbedContent.split(finding.rawSecret).join(placeholder);\n if (nextContent !== scrubbedContent) {\n fileReplacements += countOccurrences(scrubbedContent, finding.rawSecret);\n scrubbedContent = nextContent;\n }\n }\n\n if (scrubbedContent !== originalContent) {\n await fs.writeFile(absolutePath, scrubbedContent);\n filesChanged += 1;\n replacements += fileReplacements;\n }\n }\n\n return {\n filesChanged,\n replacements,\n skippedFindings: findingsByFile.skippedFindings,\n };\n}\n\nexport function formatSecretPlaceholder(finding: ScrubFinding): string {\n return `[CNGKIT_SECRET:${sanitizePlaceholderPart(finding.detectorName)}:${\n finding.verified ? \"verified\" : \"unverified\"\n }]`;\n}\n\nfunction groupFindingsBySafePath(\n target: ScrubTarget,\n findings: ScrubFinding[]\n): {\n safeFindingsByPath: Map<string, ScrubFinding[]>;\n skippedFindings: number;\n} {\n const safeFindingsByPath = new Map<string, ScrubFinding[]>();\n let skippedFindings = 0;\n\n for (const finding of findings) {\n const absolutePath = resolveSafeFindingPath(target, finding.filePath);\n if (!absolutePath) {\n skippedFindings += 1;\n continue;\n }\n\n const fileFindings = safeFindingsByPath.get(absolutePath) ?? [];\n fileFindings.push(finding);\n safeFindingsByPath.set(absolutePath, fileFindings);\n }\n\n return { safeFindingsByPath, skippedFindings };\n}\n\ntype ScrubTarget = {\n rootDir: string;\n allowedFilePath?: string;\n};\n\nasync function resolveScrubTarget(targetPath: string): Promise<ScrubTarget> {\n const absoluteTargetPath = path.resolve(targetPath);\n const stat = await fs.stat(absoluteTargetPath);\n\n if (stat.isFile()) {\n return {\n rootDir: path.dirname(absoluteTargetPath),\n allowedFilePath: absoluteTargetPath,\n };\n }\n\n return {\n rootDir: absoluteTargetPath,\n };\n}\n\nfunction resolveSafeFindingPath(\n target: ScrubTarget,\n findingFilePath: string\n): string | undefined {\n const absolutePath = path.isAbsolute(findingFilePath)\n ? path.resolve(findingFilePath)\n : path.resolve(target.rootDir, findingFilePath);\n\n if (target.allowedFilePath) {\n return absolutePath === target.allowedFilePath ? absolutePath : undefined;\n }\n\n const normalizedRoot = `${path.resolve(target.rootDir)}${path.sep}`;\n\n if (absolutePath === path.resolve(target.rootDir) || absolutePath.startsWith(normalizedRoot)) {\n return absolutePath;\n }\n\n return undefined;\n}\n\nfunction countOccurrences(value: string, search: string): number {\n if (!search) {\n return 0;\n }\n\n return value.split(search).length - 1;\n}\n\nfunction sanitizePlaceholderPart(value: string): string {\n return value.replace(/[^A-Za-z0-9_-]+/g, \"-\").replace(/^-+|-+$/g, \"\") || \"Unknown\";\n}\n","import { spawn } from \"node:child_process\";\nimport path from \"node:path\";\n\nimport { parseTruffleHogJsonLines, type ScrubFinding } from \"./findings.js\";\n\nexport type TruffleHogScanOptions = {\n cwd: string;\n};\n\nexport async function scanFilesystemWithTruffleHog(\n targetPath: string,\n options: TruffleHogScanOptions\n): Promise<ScrubFinding[]> {\n const absoluteTargetPath = path.resolve(options.cwd, targetPath);\n const { stdout } = await runTruffleHog([\n \"filesystem\",\n absoluteTargetPath,\n \"--json\",\n \"--no-update\",\n \"--force-skip-binaries\",\n \"--force-skip-archives\",\n ]);\n\n return parseTruffleHogJsonLines(stdout);\n}\n\nfunction runTruffleHog(args: string[]): Promise<{ stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n const child = spawn(\"trufflehog\", args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n\n child.stdout.on(\"data\", (chunk: Buffer) => {\n stdoutChunks.push(chunk);\n });\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk);\n });\n child.on(\"error\", (error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\") {\n reject(\n new Error(\n \"TruffleHog is not installed. Install it with `brew install trufflehog` or see https://github.com/trufflesecurity/trufflehog.\"\n )\n );\n return;\n }\n\n reject(error);\n });\n child.on(\"close\", (exitCode) => {\n const stdout = Buffer.concat(stdoutChunks).toString(\"utf8\");\n const stderr = Buffer.concat(stderrChunks).toString(\"utf8\");\n\n if (exitCode && exitCode !== 0) {\n reject(new Error(`TruffleHog scan failed with exit code ${exitCode}: ${stderr.trim()}`));\n return;\n }\n\n resolve({ stdout, stderr });\n });\n });\n}\n","export type ScrubFinding = {\n detectorName: string;\n filePath: string;\n line?: number;\n rawSecret: string;\n redactedSecret: string;\n verified: boolean;\n};\n\ntype JsonRecord = Record<string, unknown>;\n\nexport function parseTruffleHogJsonLines(output: string): ScrubFinding[] {\n const findings: ScrubFinding[] = [];\n\n for (const line of output.split(/\\r?\\n/)) {\n const trimmedLine = line.trim();\n if (!trimmedLine.startsWith(\"{\")) {\n continue;\n }\n\n const parsedLine = parseJsonRecord(trimmedLine);\n if (!parsedLine) {\n continue;\n }\n\n const finding = parseTruffleHogFinding(parsedLine);\n if (finding) {\n findings.push(finding);\n }\n }\n\n return findings;\n}\n\nfunction parseTruffleHogFinding(finding: JsonRecord): ScrubFinding | undefined {\n const rawSecret = readString(finding.Raw) || readString(finding.RawV2);\n const filePath = readFilesystemFilePath(finding);\n\n if (!rawSecret || !filePath) {\n return undefined;\n }\n\n return {\n detectorName: readString(finding.DetectorName) || \"Unknown\",\n filePath,\n line: readFilesystemLine(finding),\n rawSecret,\n redactedSecret: readString(finding.Redacted) || \"\",\n verified: finding.Verified === true,\n };\n}\n\nfunction readFilesystemFilePath(finding: JsonRecord): string | undefined {\n const filesystem = readFilesystemMetadata(finding);\n if (!filesystem) {\n return undefined;\n }\n\n return readString(filesystem.file) || readString(filesystem.path);\n}\n\nfunction readFilesystemLine(finding: JsonRecord): number | undefined {\n const filesystem = readFilesystemMetadata(finding);\n if (!filesystem) {\n return undefined;\n }\n\n return typeof filesystem.line === \"number\" ? filesystem.line : undefined;\n}\n\nfunction readFilesystemMetadata(finding: JsonRecord): JsonRecord | undefined {\n const sourceMetadata = readRecord(finding.SourceMetadata);\n const data = readRecord(sourceMetadata?.Data);\n return readRecord(data?.Filesystem);\n}\n\nfunction parseJsonRecord(line: string): JsonRecord | undefined {\n try {\n return readRecord(JSON.parse(line));\n } catch {\n return undefined;\n }\n}\n\nfunction isJsonRecord(value: unknown): value is JsonRecord {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction readRecord(value: unknown): JsonRecord | undefined {\n return isJsonRecord(value) ? value : undefined;\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n"],"mappings":"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","names":["path","options","path","args","options","args","options"]}
|