agentmask 0.1.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/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-F7TMT2OH.js +96 -0
- package/dist/chunk-F7TMT2OH.js.map +1 -0
- package/dist/chunk-P7BRPZBB.js +211 -0
- package/dist/chunk-P7BRPZBB.js.map +1 -0
- package/dist/chunk-Q7ZBIDBL.js +58 -0
- package/dist/chunk-Q7ZBIDBL.js.map +1 -0
- package/dist/chunk-YASOHGJL.js +44 -0
- package/dist/chunk-YASOHGJL.js.map +1 -0
- package/dist/cli.js +433 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.js +410 -0
- package/dist/index.js.map +1 -0
- package/dist/post-scan-PZBRRZS6.js +50 -0
- package/dist/post-scan-PZBRRZS6.js.map +1 -0
- package/dist/pre-bash-CQ6UYBND.js +75 -0
- package/dist/pre-bash-CQ6UYBND.js.map +1 -0
- package/dist/pre-read-4YE6QMWV.js +49 -0
- package/dist/pre-read-4YE6QMWV.js.map +1 -0
- package/dist/pre-write-EBMADS22.js +53 -0
- package/dist/pre-write-EBMADS22.js.map +1 -0
- package/dist/server-3SUDWIDY.js +13944 -0
- package/dist/server-3SUDWIDY.js.map +1 -0
- package/package.json +63 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
// src/gitleaks/binary.ts
|
|
2
|
+
import { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { pipeline } from "stream/promises";
|
|
6
|
+
import { extract } from "tar";
|
|
7
|
+
var GITLEAKS_VERSION = "8.22.1";
|
|
8
|
+
var CACHE_DIR = join(
|
|
9
|
+
process.env.HOME ?? process.env.USERPROFILE ?? "/tmp",
|
|
10
|
+
".agentmask",
|
|
11
|
+
"bin"
|
|
12
|
+
);
|
|
13
|
+
async function getGitleaksBinary() {
|
|
14
|
+
const systemBin = findInPath();
|
|
15
|
+
if (systemBin) return systemBin;
|
|
16
|
+
const cachedBin = join(CACHE_DIR, "gitleaks");
|
|
17
|
+
if (existsSync(cachedBin)) return cachedBin;
|
|
18
|
+
console.log(`gitleaks not found. Downloading v${GITLEAKS_VERSION}...`);
|
|
19
|
+
await downloadGitleaks(cachedBin);
|
|
20
|
+
return cachedBin;
|
|
21
|
+
}
|
|
22
|
+
function isGitleaksAvailable() {
|
|
23
|
+
if (findInPath()) return true;
|
|
24
|
+
const cachedBin = join(CACHE_DIR, "gitleaks");
|
|
25
|
+
return existsSync(cachedBin);
|
|
26
|
+
}
|
|
27
|
+
function findInPath() {
|
|
28
|
+
try {
|
|
29
|
+
const path = execSync("which gitleaks", {
|
|
30
|
+
encoding: "utf-8",
|
|
31
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
32
|
+
}).trim();
|
|
33
|
+
if (path && existsSync(path)) return path;
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
async function downloadGitleaks(destPath) {
|
|
39
|
+
const { platform, arch } = getPlatformArch();
|
|
40
|
+
const filename = `gitleaks_${GITLEAKS_VERSION}_${platform}_${arch}.tar.gz`;
|
|
41
|
+
const url = `https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${filename}`;
|
|
42
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
43
|
+
const tmpTarball = join(CACHE_DIR, `gitleaks-download.tar.gz`);
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
46
|
+
if (!response.ok || !response.body) {
|
|
47
|
+
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
48
|
+
}
|
|
49
|
+
const fileStream = createWriteStream(tmpTarball);
|
|
50
|
+
await pipeline(response.body, fileStream);
|
|
51
|
+
await extract({
|
|
52
|
+
file: tmpTarball,
|
|
53
|
+
cwd: CACHE_DIR,
|
|
54
|
+
filter: (path) => path === "gitleaks"
|
|
55
|
+
});
|
|
56
|
+
chmodSync(destPath, 493);
|
|
57
|
+
console.log(` Downloaded gitleaks v${GITLEAKS_VERSION} \u2192 ${destPath}`);
|
|
58
|
+
} finally {
|
|
59
|
+
try {
|
|
60
|
+
unlinkSync(tmpTarball);
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function getPlatformArch() {
|
|
66
|
+
const platform = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : process.platform === "win32" ? "windows" : process.platform;
|
|
67
|
+
const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : process.arch;
|
|
68
|
+
return { platform, arch };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/gitleaks/runner.ts
|
|
72
|
+
import { execFileSync } from "child_process";
|
|
73
|
+
import { readFileSync, writeFileSync, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, rmSync } from "fs";
|
|
74
|
+
import { join as join2 } from "path";
|
|
75
|
+
import { tmpdir } from "os";
|
|
76
|
+
import { randomBytes } from "crypto";
|
|
77
|
+
async function scanDir(dirPath, options) {
|
|
78
|
+
const bin = await getGitleaksBinary();
|
|
79
|
+
const reportPath = tempPath("agentmask-report", ".json");
|
|
80
|
+
try {
|
|
81
|
+
const args = [
|
|
82
|
+
"dir",
|
|
83
|
+
dirPath,
|
|
84
|
+
"--report-format",
|
|
85
|
+
"json",
|
|
86
|
+
"--report-path",
|
|
87
|
+
reportPath,
|
|
88
|
+
"--no-banner",
|
|
89
|
+
"--exit-code",
|
|
90
|
+
"0"
|
|
91
|
+
];
|
|
92
|
+
if (options?.configPath) {
|
|
93
|
+
args.push("--config", options.configPath);
|
|
94
|
+
}
|
|
95
|
+
execFileSync(bin, args, {
|
|
96
|
+
encoding: "utf-8",
|
|
97
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
98
|
+
timeout: 6e4
|
|
99
|
+
});
|
|
100
|
+
return readReport(reportPath);
|
|
101
|
+
} finally {
|
|
102
|
+
tryUnlink(reportPath);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function scanFile(filePath) {
|
|
106
|
+
const bin = await getGitleaksBinary();
|
|
107
|
+
const reportPath = tempPath("agentmask-report", ".json");
|
|
108
|
+
try {
|
|
109
|
+
execFileSync(bin, [
|
|
110
|
+
"dir",
|
|
111
|
+
filePath,
|
|
112
|
+
"--report-format",
|
|
113
|
+
"json",
|
|
114
|
+
"--report-path",
|
|
115
|
+
reportPath,
|
|
116
|
+
"--no-banner",
|
|
117
|
+
"--exit-code",
|
|
118
|
+
"0"
|
|
119
|
+
], {
|
|
120
|
+
encoding: "utf-8",
|
|
121
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
122
|
+
timeout: 1e4
|
|
123
|
+
});
|
|
124
|
+
return readReport(reportPath);
|
|
125
|
+
} finally {
|
|
126
|
+
tryUnlink(reportPath);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function scanContent(content, filename) {
|
|
130
|
+
const bin = await getGitleaksBinary();
|
|
131
|
+
const scanDir2 = join2(tmpdir(), `agentmask-scan-${randomBytes(4).toString("hex")}`);
|
|
132
|
+
const scanFile2 = join2(scanDir2, filename ?? "content.txt");
|
|
133
|
+
const reportPath = tempPath("agentmask-report", ".json");
|
|
134
|
+
try {
|
|
135
|
+
mkdirSync2(scanDir2, { recursive: true });
|
|
136
|
+
writeFileSync(scanFile2, content);
|
|
137
|
+
execFileSync(bin, [
|
|
138
|
+
"dir",
|
|
139
|
+
scanDir2,
|
|
140
|
+
"--report-format",
|
|
141
|
+
"json",
|
|
142
|
+
"--report-path",
|
|
143
|
+
reportPath,
|
|
144
|
+
"--no-banner",
|
|
145
|
+
"--exit-code",
|
|
146
|
+
"0"
|
|
147
|
+
], {
|
|
148
|
+
encoding: "utf-8",
|
|
149
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
150
|
+
timeout: 1e4
|
|
151
|
+
});
|
|
152
|
+
return readReport(reportPath);
|
|
153
|
+
} finally {
|
|
154
|
+
tryUnlink(reportPath);
|
|
155
|
+
try {
|
|
156
|
+
rmSync(scanDir2, { recursive: true, force: true });
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function scanStaged(cwd) {
|
|
162
|
+
const bin = await getGitleaksBinary();
|
|
163
|
+
const reportPath = tempPath("agentmask-report", ".json");
|
|
164
|
+
try {
|
|
165
|
+
execFileSync(bin, [
|
|
166
|
+
"git",
|
|
167
|
+
"--staged",
|
|
168
|
+
"--report-format",
|
|
169
|
+
"json",
|
|
170
|
+
"--report-path",
|
|
171
|
+
reportPath,
|
|
172
|
+
"--no-banner",
|
|
173
|
+
"--exit-code",
|
|
174
|
+
"0"
|
|
175
|
+
], {
|
|
176
|
+
cwd,
|
|
177
|
+
encoding: "utf-8",
|
|
178
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
179
|
+
timeout: 3e4
|
|
180
|
+
});
|
|
181
|
+
return readReport(reportPath);
|
|
182
|
+
} finally {
|
|
183
|
+
tryUnlink(reportPath);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function readReport(reportPath) {
|
|
187
|
+
try {
|
|
188
|
+
const raw = readFileSync(reportPath, "utf-8");
|
|
189
|
+
const findings = JSON.parse(raw);
|
|
190
|
+
return Array.isArray(findings) ? findings : [];
|
|
191
|
+
} catch {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function tempPath(prefix, ext) {
|
|
196
|
+
return join2(tmpdir(), `${prefix}-${randomBytes(4).toString("hex")}${ext}`);
|
|
197
|
+
}
|
|
198
|
+
function tryUnlink(path) {
|
|
199
|
+
try {
|
|
200
|
+
unlinkSync2(path);
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/hooks/blocklist.ts
|
|
206
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
207
|
+
import { join as join3, dirname, resolve } from "path";
|
|
208
|
+
var BLOCKLIST_FILENAME = "agentmask-blocklist.json";
|
|
209
|
+
function getBlocklistPath(cwd) {
|
|
210
|
+
return join3(cwd, ".claude", BLOCKLIST_FILENAME);
|
|
211
|
+
}
|
|
212
|
+
function loadBlocklist(cwd) {
|
|
213
|
+
const filePath = getBlocklistPath(cwd);
|
|
214
|
+
try {
|
|
215
|
+
if (existsSync2(filePath)) {
|
|
216
|
+
return JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
}
|
|
220
|
+
return { files: {} };
|
|
221
|
+
}
|
|
222
|
+
function saveBlocklist(cwd, data) {
|
|
223
|
+
const filePath = getBlocklistPath(cwd);
|
|
224
|
+
mkdirSync3(dirname(filePath), { recursive: true });
|
|
225
|
+
writeFileSync2(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
226
|
+
}
|
|
227
|
+
function isInBlocklist(filePath, cwd) {
|
|
228
|
+
const data = loadBlocklist(cwd);
|
|
229
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
230
|
+
if (data.files[normalized]) return data.files[normalized];
|
|
231
|
+
const resolved = resolve(cwd, filePath).replace(/\\/g, "/");
|
|
232
|
+
const cwdNorm = cwd.replace(/\\/g, "/");
|
|
233
|
+
const relative = resolved.startsWith(cwdNorm + "/") ? resolved.slice(cwdNorm.length + 1) : null;
|
|
234
|
+
if (relative && data.files[relative]) return data.files[relative];
|
|
235
|
+
for (const [blockedPath, entry] of Object.entries(data.files)) {
|
|
236
|
+
if (normalized.endsWith("/" + blockedPath) || normalized === blockedPath) {
|
|
237
|
+
return entry;
|
|
238
|
+
}
|
|
239
|
+
if (resolved.endsWith("/" + blockedPath) || resolved === blockedPath) {
|
|
240
|
+
return entry;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return void 0;
|
|
244
|
+
}
|
|
245
|
+
function addToBlocklist(filePath, secretDescriptions, cwd) {
|
|
246
|
+
const data = loadBlocklist(cwd);
|
|
247
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
248
|
+
const resolved = resolve(cwd, normalized).replace(/\\/g, "/");
|
|
249
|
+
const cwdNorm = cwd.replace(/\\/g, "/");
|
|
250
|
+
const key = resolved.startsWith(cwdNorm + "/") ? resolved.slice(cwdNorm.length + 1) : normalized;
|
|
251
|
+
const existing = data.files[key];
|
|
252
|
+
if (existing) {
|
|
253
|
+
const newSecrets = secretDescriptions.filter(
|
|
254
|
+
(s) => !existing.secrets.includes(s)
|
|
255
|
+
);
|
|
256
|
+
existing.secrets.push(...newSecrets);
|
|
257
|
+
existing.addedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
258
|
+
} else {
|
|
259
|
+
data.files[key] = {
|
|
260
|
+
secrets: secretDescriptions,
|
|
261
|
+
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
saveBlocklist(cwd, data);
|
|
265
|
+
}
|
|
266
|
+
function removeFromBlocklist(filePath, cwd) {
|
|
267
|
+
const data = loadBlocklist(cwd);
|
|
268
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
269
|
+
if (data.files[normalized]) {
|
|
270
|
+
delete data.files[normalized];
|
|
271
|
+
saveBlocklist(cwd, data);
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
const resolved = resolve(cwd, filePath).replace(/\\/g, "/");
|
|
275
|
+
const cwdNorm = cwd.replace(/\\/g, "/");
|
|
276
|
+
const relative = resolved.startsWith(cwdNorm + "/") ? resolved.slice(cwdNorm.length + 1) : null;
|
|
277
|
+
if (relative && data.files[relative]) {
|
|
278
|
+
delete data.files[relative];
|
|
279
|
+
saveBlocklist(cwd, data);
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
for (const key of Object.keys(data.files)) {
|
|
283
|
+
if (normalized.endsWith("/" + key) || normalized === key) {
|
|
284
|
+
delete data.files[key];
|
|
285
|
+
saveBlocklist(cwd, data);
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/scanner/file-patterns.ts
|
|
293
|
+
import { minimatch } from "minimatch";
|
|
294
|
+
import { basename } from "path";
|
|
295
|
+
var DEFAULT_BLOCKED_PATTERNS = [
|
|
296
|
+
// Environment files
|
|
297
|
+
".env",
|
|
298
|
+
".env.*",
|
|
299
|
+
"**/.env",
|
|
300
|
+
"**/.env.*",
|
|
301
|
+
// Credential files
|
|
302
|
+
"**/credentials.json",
|
|
303
|
+
"**/serviceAccountKey.json",
|
|
304
|
+
"**/service-account*.json",
|
|
305
|
+
// Key files
|
|
306
|
+
"**/*.pem",
|
|
307
|
+
"**/*.key",
|
|
308
|
+
"**/*.p12",
|
|
309
|
+
"**/*.pfx",
|
|
310
|
+
// SSH keys
|
|
311
|
+
"**/id_rsa",
|
|
312
|
+
"**/id_ed25519",
|
|
313
|
+
"**/id_ecdsa",
|
|
314
|
+
"**/id_dsa",
|
|
315
|
+
// Auth config files
|
|
316
|
+
"**/.netrc",
|
|
317
|
+
"**/.npmrc",
|
|
318
|
+
"**/.pypirc",
|
|
319
|
+
"**/.docker/config.json",
|
|
320
|
+
"**/.kube/config",
|
|
321
|
+
"**/kubeconfig",
|
|
322
|
+
// Cloud provider credentials
|
|
323
|
+
"**/.aws/credentials",
|
|
324
|
+
"**/.aws/config",
|
|
325
|
+
"**/.azure/credentials",
|
|
326
|
+
"**/.gcloud/*.json",
|
|
327
|
+
// Other
|
|
328
|
+
"**/.htpasswd",
|
|
329
|
+
"**/secrets.yml",
|
|
330
|
+
"**/secrets.yaml",
|
|
331
|
+
"**/secrets.json"
|
|
332
|
+
];
|
|
333
|
+
function isBlockedPath(filePath, blockedPatterns = DEFAULT_BLOCKED_PATTERNS) {
|
|
334
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
335
|
+
const base = basename(normalized);
|
|
336
|
+
for (const pattern of blockedPatterns) {
|
|
337
|
+
if (minimatch(normalized, pattern, { dot: true })) return true;
|
|
338
|
+
if (minimatch(base, pattern, { dot: true })) return true;
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
function isAllowlistedPath(filePath, allowlistPatterns) {
|
|
343
|
+
if (allowlistPatterns.length === 0) return false;
|
|
344
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
345
|
+
for (const pattern of allowlistPatterns) {
|
|
346
|
+
if (minimatch(normalized, pattern, { dot: true })) return true;
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/config/loader.ts
|
|
352
|
+
import { readFile } from "fs/promises";
|
|
353
|
+
import { join as join4 } from "path";
|
|
354
|
+
import { parse as parseTOML } from "smol-toml";
|
|
355
|
+
var CONFIG_FILENAMES = [".agentmask.toml", "agentmask.toml"];
|
|
356
|
+
async function loadConfig(projectDir) {
|
|
357
|
+
const projectConfig = await loadConfigFrom(projectDir);
|
|
358
|
+
const homeConfig = await loadConfigFrom(
|
|
359
|
+
join4(process.env.HOME ?? "~", ".config", "agentmask")
|
|
360
|
+
);
|
|
361
|
+
return mergeConfigs(homeConfig, projectConfig);
|
|
362
|
+
}
|
|
363
|
+
async function loadConfigFrom(dir) {
|
|
364
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
365
|
+
try {
|
|
366
|
+
const content = await readFile(join4(dir, filename), "utf-8");
|
|
367
|
+
return parseTOML(content);
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return {};
|
|
372
|
+
}
|
|
373
|
+
function mergeConfigs(base, override) {
|
|
374
|
+
return {
|
|
375
|
+
scan: {
|
|
376
|
+
blocked_paths: [
|
|
377
|
+
...base.scan?.blocked_paths ?? [],
|
|
378
|
+
...override.scan?.blocked_paths ?? []
|
|
379
|
+
]
|
|
380
|
+
},
|
|
381
|
+
rules: [...base.rules ?? [], ...override.rules ?? []],
|
|
382
|
+
allowlists: [...base.allowlists ?? [], ...override.allowlists ?? []]
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function getAllowlistedPaths(config) {
|
|
386
|
+
return (config.allowlists ?? []).flatMap((a) => a.paths ?? []);
|
|
387
|
+
}
|
|
388
|
+
function getStopwords(config) {
|
|
389
|
+
return (config.allowlists ?? []).flatMap((a) => a.stopwords ?? []);
|
|
390
|
+
}
|
|
391
|
+
export {
|
|
392
|
+
DEFAULT_BLOCKED_PATTERNS,
|
|
393
|
+
addToBlocklist,
|
|
394
|
+
getAllowlistedPaths,
|
|
395
|
+
getGitleaksBinary,
|
|
396
|
+
getStopwords,
|
|
397
|
+
isAllowlistedPath,
|
|
398
|
+
isBlockedPath,
|
|
399
|
+
isGitleaksAvailable,
|
|
400
|
+
isInBlocklist,
|
|
401
|
+
loadBlocklist,
|
|
402
|
+
loadConfig,
|
|
403
|
+
removeFromBlocklist,
|
|
404
|
+
saveBlocklist,
|
|
405
|
+
scanContent,
|
|
406
|
+
scanDir,
|
|
407
|
+
scanFile,
|
|
408
|
+
scanStaged
|
|
409
|
+
};
|
|
410
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/gitleaks/binary.ts","../src/gitleaks/runner.ts","../src/hooks/blocklist.ts","../src/scanner/file-patterns.ts","../src/config/loader.ts"],"sourcesContent":["import { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { execSync } from \"node:child_process\";\nimport { pipeline } from \"node:stream/promises\";\nimport { createGunzip } from \"node:zlib\";\nimport { extract } from \"tar\";\n\n/**\n * Pinned gitleaks version. Update this when testing against a new release.\n */\nconst GITLEAKS_VERSION = \"8.22.1\";\n\n/**\n * Where we cache the downloaded binary.\n */\nconst CACHE_DIR = join(\n process.env.HOME ?? process.env.USERPROFILE ?? \"/tmp\",\n \".agentmask\",\n \"bin\",\n);\n\n/**\n * Find the gitleaks binary. Checks in order:\n * 1. System PATH (user already has it installed)\n * 2. Our cache directory (previously downloaded)\n * 3. Downloads it automatically\n */\nexport async function getGitleaksBinary(): Promise<string> {\n // 1. Check system PATH\n const systemBin = findInPath();\n if (systemBin) return systemBin;\n\n // 2. Check cache\n const cachedBin = join(CACHE_DIR, \"gitleaks\");\n if (existsSync(cachedBin)) return cachedBin;\n\n // 3. Download\n console.log(`gitleaks not found. Downloading v${GITLEAKS_VERSION}...`);\n await downloadGitleaks(cachedBin);\n return cachedBin;\n}\n\n/**\n * Check if gitleaks is available without downloading.\n */\nexport function isGitleaksAvailable(): boolean {\n if (findInPath()) return true;\n const cachedBin = join(CACHE_DIR, \"gitleaks\");\n return existsSync(cachedBin);\n}\n\nfunction findInPath(): string | null {\n try {\n const path = execSync(\"which gitleaks\", {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n }).trim();\n if (path && existsSync(path)) return path;\n } catch {\n // not in PATH\n }\n return null;\n}\n\nasync function downloadGitleaks(destPath: string): Promise<void> {\n const { platform, arch } = getPlatformArch();\n const filename = `gitleaks_${GITLEAKS_VERSION}_${platform}_${arch}.tar.gz`;\n const url = `https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${filename}`;\n\n mkdirSync(CACHE_DIR, { recursive: true });\n\n // Download to temp file\n const tmpTarball = join(CACHE_DIR, `gitleaks-download.tar.gz`);\n\n try {\n const response = await fetch(url, { redirect: \"follow\" });\n if (!response.ok || !response.body) {\n throw new Error(`Download failed: ${response.status} ${response.statusText}`);\n }\n\n // Write tarball to disk\n const fileStream = createWriteStream(tmpTarball);\n // @ts-ignore - Node.js ReadableStream from fetch is compatible\n await pipeline(response.body, fileStream);\n\n // Extract the gitleaks binary from the tarball\n await extract({\n file: tmpTarball,\n cwd: CACHE_DIR,\n filter: (path) => path === \"gitleaks\",\n });\n\n // Make executable\n chmodSync(destPath, 0o755);\n\n console.log(` Downloaded gitleaks v${GITLEAKS_VERSION} → ${destPath}`);\n } finally {\n // Clean up tarball\n try {\n unlinkSync(tmpTarball);\n } catch {}\n }\n}\n\nfunction getPlatformArch(): { platform: string; arch: string } {\n const platform =\n process.platform === \"darwin\"\n ? \"darwin\"\n : process.platform === \"linux\"\n ? \"linux\"\n : process.platform === \"win32\"\n ? \"windows\"\n : process.platform;\n\n const arch =\n process.arch === \"arm64\"\n ? \"arm64\"\n : process.arch === \"x64\"\n ? \"x64\"\n : process.arch;\n\n return { platform, arch };\n}\n","import { execSync, execFileSync } from \"node:child_process\";\nimport { readFileSync, writeFileSync, mkdirSync, unlinkSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { randomBytes } from \"node:crypto\";\nimport { getGitleaksBinary } from \"./binary.js\";\n\n/**\n * A single finding from gitleaks JSON output.\n */\nexport interface GitleaksFinding {\n RuleID: string;\n Description: string;\n StartLine: number;\n EndLine: number;\n StartColumn: number;\n EndColumn: number;\n Match: string;\n Secret: string;\n File: string;\n Entropy: number;\n Fingerprint: string;\n}\n\n/**\n * Scan a directory for secrets.\n */\nexport async function scanDir(\n dirPath: string,\n options?: { configPath?: string },\n): Promise<GitleaksFinding[]> {\n const bin = await getGitleaksBinary();\n const reportPath = tempPath(\"agentmask-report\", \".json\");\n\n try {\n const args = [\n \"dir\",\n dirPath,\n \"--report-format\", \"json\",\n \"--report-path\", reportPath,\n \"--no-banner\",\n \"--exit-code\", \"0\",\n ];\n if (options?.configPath) {\n args.push(\"--config\", options.configPath);\n }\n\n execFileSync(bin, args, {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n timeout: 60_000,\n });\n\n return readReport(reportPath);\n } finally {\n tryUnlink(reportPath);\n }\n}\n\n/**\n * Scan a single file for secrets.\n */\nexport async function scanFile(filePath: string): Promise<GitleaksFinding[]> {\n const bin = await getGitleaksBinary();\n const reportPath = tempPath(\"agentmask-report\", \".json\");\n\n try {\n execFileSync(bin, [\n \"dir\",\n filePath,\n \"--report-format\", \"json\",\n \"--report-path\", reportPath,\n \"--no-banner\",\n \"--exit-code\", \"0\",\n ], {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n timeout: 10_000,\n });\n\n return readReport(reportPath);\n } finally {\n tryUnlink(reportPath);\n }\n}\n\n/**\n * Scan arbitrary content by writing to a temp file.\n * Used by pre-write hook (scan content before it's written)\n * and post-scan hook (scan tool output).\n */\nexport async function scanContent(\n content: string,\n filename?: string,\n): Promise<GitleaksFinding[]> {\n const bin = await getGitleaksBinary();\n const scanDir = join(tmpdir(), `agentmask-scan-${randomBytes(4).toString(\"hex\")}`);\n const scanFile = join(scanDir, filename ?? \"content.txt\");\n const reportPath = tempPath(\"agentmask-report\", \".json\");\n\n try {\n mkdirSync(scanDir, { recursive: true });\n writeFileSync(scanFile, content);\n\n execFileSync(bin, [\n \"dir\",\n scanDir,\n \"--report-format\", \"json\",\n \"--report-path\", reportPath,\n \"--no-banner\",\n \"--exit-code\", \"0\",\n ], {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n timeout: 10_000,\n });\n\n return readReport(reportPath);\n } finally {\n tryUnlink(reportPath);\n try { rmSync(scanDir, { recursive: true, force: true }); } catch {}\n }\n}\n\n/**\n * Scan git staged files for secrets.\n */\nexport async function scanStaged(cwd: string): Promise<GitleaksFinding[]> {\n const bin = await getGitleaksBinary();\n const reportPath = tempPath(\"agentmask-report\", \".json\");\n\n try {\n execFileSync(bin, [\n \"git\",\n \"--staged\",\n \"--report-format\", \"json\",\n \"--report-path\", reportPath,\n \"--no-banner\",\n \"--exit-code\", \"0\",\n ], {\n cwd,\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n timeout: 30_000,\n });\n\n return readReport(reportPath);\n } finally {\n tryUnlink(reportPath);\n }\n}\n\nfunction readReport(reportPath: string): GitleaksFinding[] {\n try {\n const raw = readFileSync(reportPath, \"utf-8\");\n const findings = JSON.parse(raw);\n return Array.isArray(findings) ? findings : [];\n } catch {\n return [];\n }\n}\n\nfunction tempPath(prefix: string, ext: string): string {\n return join(tmpdir(), `${prefix}-${randomBytes(4).toString(\"hex\")}${ext}`);\n}\n\nfunction tryUnlink(path: string): void {\n try { unlinkSync(path); } catch {}\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from \"node:fs\";\nimport { join, dirname, resolve } from \"node:path\";\n\n/**\n * Dynamic blocklist — files where secrets have been detected.\n *\n * Built at init time by scanning the entire repo (Tier 1 rules only).\n * Updated at runtime by post-scan when secrets are found in new files.\n * Checked by pre-read to block files before they enter context.\n */\n\nexport interface BlocklistEntry {\n secrets: string[];\n addedAt: string;\n}\n\nexport interface BlocklistData {\n /** Map of relative file path → entry */\n files: Record<string, BlocklistEntry>;\n}\n\nconst BLOCKLIST_FILENAME = \"agentmask-blocklist.json\";\n\nexport function getBlocklistPath(cwd: string): string {\n return join(cwd, \".claude\", BLOCKLIST_FILENAME);\n}\n\nexport function loadBlocklist(cwd: string): BlocklistData {\n const filePath = getBlocklistPath(cwd);\n try {\n if (existsSync(filePath)) {\n return JSON.parse(readFileSync(filePath, \"utf-8\"));\n }\n } catch {\n // Corrupted — start fresh\n }\n return { files: {} };\n}\n\nexport function saveBlocklist(cwd: string, data: BlocklistData): void {\n const filePath = getBlocklistPath(cwd);\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, JSON.stringify(data, null, 2) + \"\\n\");\n}\n\n/**\n * Check if a file path is in the dynamic blocklist.\n * Matches by exact relative path or by filename suffix.\n */\nexport function isInBlocklist(\n filePath: string,\n cwd: string,\n): BlocklistEntry | undefined {\n const data = loadBlocklist(cwd);\n const normalized = filePath.replace(/\\\\/g, \"/\");\n\n // Try exact match\n if (data.files[normalized]) return data.files[normalized];\n\n // Try relative to cwd\n const resolved = resolve(cwd, filePath).replace(/\\\\/g, \"/\");\n const cwdNorm = cwd.replace(/\\\\/g, \"/\");\n const relative = resolved.startsWith(cwdNorm + \"/\")\n ? resolved.slice(cwdNorm.length + 1)\n : null;\n if (relative && data.files[relative]) return data.files[relative];\n\n // Try matching by suffix (handles absolute vs relative path differences)\n for (const [blockedPath, entry] of Object.entries(data.files)) {\n if (normalized.endsWith(\"/\" + blockedPath) || normalized === blockedPath) {\n return entry;\n }\n if (resolved.endsWith(\"/\" + blockedPath) || resolved === blockedPath) {\n return entry;\n }\n }\n\n return undefined;\n}\n\n/**\n * Add a file to the blocklist.\n */\nexport function addToBlocklist(\n filePath: string,\n secretDescriptions: string[],\n cwd: string,\n): void {\n const data = loadBlocklist(cwd);\n const normalized = filePath.replace(/\\\\/g, \"/\");\n\n // Make path relative to cwd if possible\n const resolved = resolve(cwd, normalized).replace(/\\\\/g, \"/\");\n const cwdNorm = cwd.replace(/\\\\/g, \"/\");\n const key = resolved.startsWith(cwdNorm + \"/\")\n ? resolved.slice(cwdNorm.length + 1)\n : normalized;\n\n const existing = data.files[key];\n if (existing) {\n const newSecrets = secretDescriptions.filter(\n (s) => !existing.secrets.includes(s),\n );\n existing.secrets.push(...newSecrets);\n existing.addedAt = new Date().toISOString();\n } else {\n data.files[key] = {\n secrets: secretDescriptions,\n addedAt: new Date().toISOString(),\n };\n }\n\n saveBlocklist(cwd, data);\n}\n\n/**\n * Remove a file from the blocklist.\n */\nexport function removeFromBlocklist(filePath: string, cwd: string): boolean {\n const data = loadBlocklist(cwd);\n const normalized = filePath.replace(/\\\\/g, \"/\");\n\n // Try exact key\n if (data.files[normalized]) {\n delete data.files[normalized];\n saveBlocklist(cwd, data);\n return true;\n }\n\n // Try relative resolution\n const resolved = resolve(cwd, filePath).replace(/\\\\/g, \"/\");\n const cwdNorm = cwd.replace(/\\\\/g, \"/\");\n const relative = resolved.startsWith(cwdNorm + \"/\")\n ? resolved.slice(cwdNorm.length + 1)\n : null;\n if (relative && data.files[relative]) {\n delete data.files[relative];\n saveBlocklist(cwd, data);\n return true;\n }\n\n // Try suffix match\n for (const key of Object.keys(data.files)) {\n if (normalized.endsWith(\"/\" + key) || normalized === key) {\n delete data.files[key];\n saveBlocklist(cwd, data);\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Delete the blocklist file entirely.\n */\nexport function deleteBlocklist(cwd: string): boolean {\n const filePath = getBlocklistPath(cwd);\n if (existsSync(filePath)) {\n unlinkSync(filePath);\n return true;\n }\n return false;\n}\n","import { minimatch } from \"minimatch\";\nimport { basename, resolve } from \"node:path\";\n\n/**\n * Default file patterns that should always be blocked from direct reading.\n * These files are known to contain secrets.\n */\nexport const DEFAULT_BLOCKED_PATTERNS: string[] = [\n // Environment files\n \".env\",\n \".env.*\",\n \"**/.env\",\n \"**/.env.*\",\n\n // Credential files\n \"**/credentials.json\",\n \"**/serviceAccountKey.json\",\n \"**/service-account*.json\",\n\n // Key files\n \"**/*.pem\",\n \"**/*.key\",\n \"**/*.p12\",\n \"**/*.pfx\",\n\n // SSH keys\n \"**/id_rsa\",\n \"**/id_ed25519\",\n \"**/id_ecdsa\",\n \"**/id_dsa\",\n\n // Auth config files\n \"**/.netrc\",\n \"**/.npmrc\",\n \"**/.pypirc\",\n \"**/.docker/config.json\",\n \"**/.kube/config\",\n \"**/kubeconfig\",\n\n // Cloud provider credentials\n \"**/.aws/credentials\",\n \"**/.aws/config\",\n \"**/.azure/credentials\",\n \"**/.gcloud/*.json\",\n\n // Other\n \"**/.htpasswd\",\n \"**/secrets.yml\",\n \"**/secrets.yaml\",\n \"**/secrets.json\",\n];\n\n/**\n * Check if a file path matches any of the blocked patterns.\n */\nexport function isBlockedPath(\n filePath: string,\n blockedPatterns: string[] = DEFAULT_BLOCKED_PATTERNS,\n): boolean {\n const normalized = filePath.replace(/\\\\/g, \"/\");\n const base = basename(normalized);\n\n for (const pattern of blockedPatterns) {\n // Check against full path\n if (minimatch(normalized, pattern, { dot: true })) return true;\n // Check against just the filename (for patterns like \".env\")\n if (minimatch(base, pattern, { dot: true })) return true;\n }\n\n return false;\n}\n\n/**\n * Check if a file path is in an allowlisted path.\n */\nexport function isAllowlistedPath(\n filePath: string,\n allowlistPatterns: string[],\n): boolean {\n if (allowlistPatterns.length === 0) return false;\n const normalized = filePath.replace(/\\\\/g, \"/\");\n\n for (const pattern of allowlistPatterns) {\n if (minimatch(normalized, pattern, { dot: true })) return true;\n }\n\n return false;\n}\n\n/**\n * Check if a file is likely binary (should be skipped during scanning).\n */\nexport function isBinaryFile(filePath: string): boolean {\n const ext = filePath.split(\".\").pop()?.toLowerCase();\n return BINARY_EXTENSIONS.has(ext ?? \"\");\n}\n\nconst BINARY_EXTENSIONS = new Set([\n \"png\", \"jpg\", \"jpeg\", \"gif\", \"bmp\", \"ico\", \"webp\", \"svg\",\n \"mp3\", \"mp4\", \"avi\", \"mov\", \"mkv\", \"wav\", \"flac\",\n \"zip\", \"tar\", \"gz\", \"bz2\", \"xz\", \"7z\", \"rar\",\n \"exe\", \"dll\", \"so\", \"dylib\", \"bin\",\n \"pdf\", \"doc\", \"docx\", \"xls\", \"xlsx\",\n \"woff\", \"woff2\", \"ttf\", \"eot\", \"otf\",\n \"pyc\", \"pyo\", \"class\", \"o\", \"obj\",\n \"sqlite\", \"db\", \"sqlite3\",\n]);\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { parse as parseTOML } from \"smol-toml\";\nimport type { AgentmaskConfig, AllowlistEntry } from \"../scanner/types.js\";\n\nconst CONFIG_FILENAMES = [\".agentmask.toml\", \"agentmask.toml\"];\n\n/**\n * Load and merge configuration from project root and user home.\n */\nexport async function loadConfig(\n projectDir: string,\n): Promise<AgentmaskConfig> {\n const projectConfig = await loadConfigFrom(projectDir);\n const homeConfig = await loadConfigFrom(\n join(process.env.HOME ?? \"~\", \".config\", \"agentmask\"),\n );\n\n return mergeConfigs(homeConfig, projectConfig);\n}\n\nasync function loadConfigFrom(dir: string): Promise<AgentmaskConfig> {\n for (const filename of CONFIG_FILENAMES) {\n try {\n const content = await readFile(join(dir, filename), \"utf-8\");\n return parseTOML(content) as unknown as AgentmaskConfig;\n } catch {\n // File doesn't exist or isn't valid TOML — skip\n }\n }\n return {};\n}\n\nfunction mergeConfigs(\n base: AgentmaskConfig,\n override: AgentmaskConfig,\n): AgentmaskConfig {\n return {\n scan: {\n blocked_paths: [\n ...(base.scan?.blocked_paths ?? []),\n ...(override.scan?.blocked_paths ?? []),\n ],\n },\n rules: [...(base.rules ?? []), ...(override.rules ?? [])],\n allowlists: [...(base.allowlists ?? []), ...(override.allowlists ?? [])],\n };\n}\n\n/**\n * Get all allowlisted paths from config.\n */\nexport function getAllowlistedPaths(config: AgentmaskConfig): string[] {\n return (config.allowlists ?? []).flatMap((a: AllowlistEntry) => a.paths ?? []);\n}\n\n/**\n * Get all stopwords from config.\n */\nexport function getStopwords(config: AgentmaskConfig): string[] {\n return (config.allowlists ?? []).flatMap((a: AllowlistEntry) => a.stopwords ?? []);\n}\n"],"mappings":"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","names":["mkdirSync","unlinkSync","join","scanDir","join","scanFile","mkdirSync","unlinkSync","existsSync","readFileSync","writeFileSync","mkdirSync","unlinkSync","join","join"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
addToBlocklist
|
|
4
|
+
} from "./chunk-F7TMT2OH.js";
|
|
5
|
+
import {
|
|
6
|
+
allow,
|
|
7
|
+
readStdin,
|
|
8
|
+
startSafetyTimer
|
|
9
|
+
} from "./chunk-YASOHGJL.js";
|
|
10
|
+
import {
|
|
11
|
+
scanContent
|
|
12
|
+
} from "./chunk-P7BRPZBB.js";
|
|
13
|
+
import "./chunk-2H7UOFLK.js";
|
|
14
|
+
|
|
15
|
+
// src/hooks/post-scan.ts
|
|
16
|
+
startSafetyTimer();
|
|
17
|
+
var MAX_SCAN_LENGTH = 100 * 1024;
|
|
18
|
+
async function main() {
|
|
19
|
+
const input = await readStdin();
|
|
20
|
+
const response = input.tool_response;
|
|
21
|
+
if (!response || typeof response !== "string") {
|
|
22
|
+
allow();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const toScan = response.length > MAX_SCAN_LENGTH ? response.slice(0, MAX_SCAN_LENGTH) : response;
|
|
26
|
+
const filePath = input.tool_input?.file_path ?? "";
|
|
27
|
+
try {
|
|
28
|
+
const findings = await scanContent(toScan);
|
|
29
|
+
if (findings.length > 0) {
|
|
30
|
+
const types = [...new Set(findings.map((f) => f.Description))];
|
|
31
|
+
const cwd = input.cwd ?? process.cwd();
|
|
32
|
+
if (filePath) {
|
|
33
|
+
try {
|
|
34
|
+
addToBlocklist(filePath, types, cwd);
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
allow(
|
|
39
|
+
`[agentmask] WARNING: The output above contains ${findings.length} detected secret(s): ${types.join(", ")}.
|
|
40
|
+
Do NOT repeat these values in your response, code, or commits. Reference by variable name only.
|
|
41
|
+
This file has been added to the blocklist \u2014 future reads will be blocked and redirected to safe_read.`
|
|
42
|
+
);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
allow();
|
|
48
|
+
}
|
|
49
|
+
main().catch(() => process.exit(1));
|
|
50
|
+
//# sourceMappingURL=post-scan-PZBRRZS6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/post-scan.ts"],"sourcesContent":["import { readStdin, allow, startSafetyTimer } from \"./common.js\";\nimport { scanContent } from \"../gitleaks/runner.js\";\nimport { addToBlocklist } from \"./blocklist.js\";\n\nstartSafetyTimer();\n\nconst MAX_SCAN_LENGTH = 100 * 1024; // 100KB max for output scanning\n\nasync function main() {\n const input = await readStdin();\n const response = input.tool_response;\n\n if (!response || typeof response !== \"string\") {\n allow();\n return;\n }\n\n const toScan = response.length > MAX_SCAN_LENGTH\n ? response.slice(0, MAX_SCAN_LENGTH)\n : response;\n\n const filePath = (input.tool_input?.file_path as string) ?? \"\";\n\n try {\n const findings = await scanContent(toScan);\n\n if (findings.length > 0) {\n const types = [...new Set(findings.map((f) => f.Description))];\n const cwd = input.cwd ?? process.cwd();\n\n // Add to blocklist so future reads are blocked\n if (filePath) {\n try {\n addToBlocklist(filePath, types, cwd);\n } catch {\n // Non-critical\n }\n }\n\n allow(\n `[agentmask] WARNING: The output above contains ${findings.length} detected secret(s): ${types.join(\", \")}.\\n` +\n `Do NOT repeat these values in your response, code, or commits. Reference by variable name only.\\n` +\n `This file has been added to the blocklist — future reads will be blocked and redirected to safe_read.`,\n );\n return;\n }\n } catch {\n // gitleaks failed — degrade gracefully\n }\n\n allow();\n}\n\nmain().catch(() => process.exit(1));\n"],"mappings":";;;;;;;;;;;;;;;AAIA,iBAAiB;AAEjB,IAAM,kBAAkB,MAAM;AAE9B,eAAe,OAAO;AACpB,QAAM,QAAQ,MAAM,UAAU;AAC9B,QAAM,WAAW,MAAM;AAEvB,MAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,UAAM;AACN;AAAA,EACF;AAEA,QAAM,SAAS,SAAS,SAAS,kBAC7B,SAAS,MAAM,GAAG,eAAe,IACjC;AAEJ,QAAM,WAAY,MAAM,YAAY,aAAwB;AAE5D,MAAI;AACF,UAAM,WAAW,MAAM,YAAY,MAAM;AAEzC,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAC7D,YAAM,MAAM,MAAM,OAAO,QAAQ,IAAI;AAGrC,UAAI,UAAU;AACZ,YAAI;AACF,yBAAe,UAAU,OAAO,GAAG;AAAA,QACrC,QAAQ;AAAA,QAER;AAAA,MACF;AAEA;AAAA,QACE,kDAAkD,SAAS,MAAM,wBAAwB,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA,MAG3G;AACA;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,QAAM;AACR;AAEA,KAAK,EAAE,MAAM,MAAM,QAAQ,KAAK,CAAC,CAAC;","names":[]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
allow,
|
|
4
|
+
block,
|
|
5
|
+
readStdin,
|
|
6
|
+
startSafetyTimer
|
|
7
|
+
} from "./chunk-YASOHGJL.js";
|
|
8
|
+
import {
|
|
9
|
+
scanStaged
|
|
10
|
+
} from "./chunk-P7BRPZBB.js";
|
|
11
|
+
import "./chunk-2H7UOFLK.js";
|
|
12
|
+
|
|
13
|
+
// src/hooks/pre-bash.ts
|
|
14
|
+
startSafetyTimer();
|
|
15
|
+
var FILE_READ_PATTERNS = [
|
|
16
|
+
/\b(?:cat|head|tail|less|more|bat)\s+.*\.env\b/,
|
|
17
|
+
/\b(?:cat|head|tail|less|more|bat)\s+.*credentials\.json\b/,
|
|
18
|
+
/\b(?:cat|head|tail|less|more|bat)\s+.*\.pem\b/,
|
|
19
|
+
/\b(?:cat|head|tail|less|more|bat)\s+.*\.key\b/,
|
|
20
|
+
/\b(?:cat|head|tail|less|more|bat)\s+.*id_rsa\b/,
|
|
21
|
+
/\b(?:cat|head|tail|less|more|bat)\s+.*id_ed25519\b/,
|
|
22
|
+
/\bsource\s+.*\.env\b/,
|
|
23
|
+
/\.\s+.*\.env\b/
|
|
24
|
+
];
|
|
25
|
+
var ENV_DUMP_PATTERNS = [
|
|
26
|
+
/\bprintenv\b/,
|
|
27
|
+
/^env$/,
|
|
28
|
+
/^env\s/,
|
|
29
|
+
/^set$/,
|
|
30
|
+
/^export$/
|
|
31
|
+
];
|
|
32
|
+
var GIT_COMMIT_PATTERN = /\bgit\s+commit\b/;
|
|
33
|
+
async function main() {
|
|
34
|
+
const input = await readStdin();
|
|
35
|
+
const command = input.tool_input?.command;
|
|
36
|
+
if (!command) {
|
|
37
|
+
allow();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
for (const pattern of FILE_READ_PATTERNS) {
|
|
41
|
+
if (pattern.test(command)) {
|
|
42
|
+
block(
|
|
43
|
+
`[agentmask] BLOCKED: This command would read a protected secret file.
|
|
44
|
+
Use mcp__agentmask__safe_read for a redacted view instead.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const pattern of ENV_DUMP_PATTERNS) {
|
|
49
|
+
if (pattern.test(command.trim())) {
|
|
50
|
+
block(
|
|
51
|
+
`[agentmask] BLOCKED: This command would expose environment variables that may contain secrets.
|
|
52
|
+
Use mcp__agentmask__env_names to see variable names without values.`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (GIT_COMMIT_PATTERN.test(command)) {
|
|
57
|
+
const cwd = input.cwd ?? process.cwd();
|
|
58
|
+
try {
|
|
59
|
+
const findings = await scanStaged(cwd);
|
|
60
|
+
if (findings.length > 0) {
|
|
61
|
+
const details = findings.map((f) => ` ${f.File}:${f.StartLine} \u2014 ${f.Description}`).join("\n");
|
|
62
|
+
block(
|
|
63
|
+
`[agentmask] BLOCKED: Secrets detected in staged files. Fix before committing.
|
|
64
|
+
${details}
|
|
65
|
+
|
|
66
|
+
Remove the hardcoded secrets and use environment variable references instead.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
allow();
|
|
73
|
+
}
|
|
74
|
+
main().catch(() => process.exit(1));
|
|
75
|
+
//# sourceMappingURL=pre-bash-CQ6UYBND.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/pre-bash.ts"],"sourcesContent":["import { readStdin, block, allow, startSafetyTimer } from \"./common.js\";\nimport { scanStaged } from \"../gitleaks/runner.js\";\n\nstartSafetyTimer();\n\n/**\n * Patterns that indicate direct reading of secret files via bash.\n */\nconst FILE_READ_PATTERNS = [\n /\\b(?:cat|head|tail|less|more|bat)\\s+.*\\.env\\b/,\n /\\b(?:cat|head|tail|less|more|bat)\\s+.*credentials\\.json\\b/,\n /\\b(?:cat|head|tail|less|more|bat)\\s+.*\\.pem\\b/,\n /\\b(?:cat|head|tail|less|more|bat)\\s+.*\\.key\\b/,\n /\\b(?:cat|head|tail|less|more|bat)\\s+.*id_rsa\\b/,\n /\\b(?:cat|head|tail|less|more|bat)\\s+.*id_ed25519\\b/,\n /\\bsource\\s+.*\\.env\\b/,\n /\\.\\s+.*\\.env\\b/,\n];\n\n/**\n * Patterns that indicate environment variable dump.\n */\nconst ENV_DUMP_PATTERNS = [\n /\\bprintenv\\b/,\n /^env$/,\n /^env\\s/,\n /^set$/,\n /^export$/,\n];\n\n/**\n * Detect git commit commands so we can scan staged files.\n */\nconst GIT_COMMIT_PATTERN = /\\bgit\\s+commit\\b/;\n\nasync function main() {\n const input = await readStdin();\n const command = input.tool_input?.command as string | undefined;\n\n if (!command) {\n allow();\n return;\n }\n\n // Category A: File reads of secret files\n for (const pattern of FILE_READ_PATTERNS) {\n if (pattern.test(command)) {\n block(\n `[agentmask] BLOCKED: This command would read a protected secret file.\\n` +\n `Use mcp__agentmask__safe_read for a redacted view instead.`,\n );\n }\n }\n\n // Category B: Environment variable dumps\n for (const pattern of ENV_DUMP_PATTERNS) {\n if (pattern.test(command.trim())) {\n block(\n `[agentmask] BLOCKED: This command would expose environment variables that may contain secrets.\\n` +\n `Use mcp__agentmask__env_names to see variable names without values.`,\n );\n }\n }\n\n // Category C: Git commit — scan staged files with gitleaks\n if (GIT_COMMIT_PATTERN.test(command)) {\n const cwd = input.cwd ?? process.cwd();\n try {\n const findings = await scanStaged(cwd);\n if (findings.length > 0) {\n const details = findings\n .map((f) => ` ${f.File}:${f.StartLine} — ${f.Description}`)\n .join(\"\\n\");\n block(\n `[agentmask] BLOCKED: Secrets detected in staged files. Fix before committing.\\n${details}\\n\\n` +\n `Remove the hardcoded secrets and use environment variable references instead.`,\n );\n }\n } catch {\n // gitleaks failed — allow the commit (graceful degradation)\n }\n }\n\n allow();\n}\n\nmain().catch(() => process.exit(1));\n"],"mappings":";;;;;;;;;;;;;AAGA,iBAAiB;AAKjB,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKA,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKA,IAAM,qBAAqB;AAE3B,eAAe,OAAO;AACpB,QAAM,QAAQ,MAAM,UAAU;AAC9B,QAAM,UAAU,MAAM,YAAY;AAElC,MAAI,CAAC,SAAS;AACZ,UAAM;AACN;AAAA,EACF;AAGA,aAAW,WAAW,oBAAoB;AACxC,QAAI,QAAQ,KAAK,OAAO,GAAG;AACzB;AAAA,QACE;AAAA;AAAA,MAEF;AAAA,IACF;AAAA,EACF;AAGA,aAAW,WAAW,mBAAmB;AACvC,QAAI,QAAQ,KAAK,QAAQ,KAAK,CAAC,GAAG;AAChC;AAAA,QACE;AAAA;AAAA,MAEF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,mBAAmB,KAAK,OAAO,GAAG;AACpC,UAAM,MAAM,MAAM,OAAO,QAAQ,IAAI;AACrC,QAAI;AACF,YAAM,WAAW,MAAM,WAAW,GAAG;AACrC,UAAI,SAAS,SAAS,GAAG;AACvB,cAAM,UAAU,SACb,IAAI,CAAC,MAAM,KAAK,EAAE,IAAI,IAAI,EAAE,SAAS,WAAM,EAAE,WAAW,EAAE,EAC1D,KAAK,IAAI;AACZ;AAAA,UACE;AAAA,EAAkF,OAAO;AAAA;AAAA;AAAA,QAE3F;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM;AACR;AAEA,KAAK,EAAE,MAAM,MAAM,QAAQ,KAAK,CAAC,CAAC;","names":[]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_BLOCKED_PATTERNS,
|
|
4
|
+
isBlockedPath
|
|
5
|
+
} from "./chunk-Q7ZBIDBL.js";
|
|
6
|
+
import {
|
|
7
|
+
isInBlocklist
|
|
8
|
+
} from "./chunk-F7TMT2OH.js";
|
|
9
|
+
import {
|
|
10
|
+
allow,
|
|
11
|
+
block,
|
|
12
|
+
readStdin,
|
|
13
|
+
startSafetyTimer
|
|
14
|
+
} from "./chunk-YASOHGJL.js";
|
|
15
|
+
import "./chunk-2H7UOFLK.js";
|
|
16
|
+
|
|
17
|
+
// src/hooks/pre-read.ts
|
|
18
|
+
import { resolve, basename } from "path";
|
|
19
|
+
startSafetyTimer();
|
|
20
|
+
async function main() {
|
|
21
|
+
const input = await readStdin();
|
|
22
|
+
const filePath = input.tool_input?.file_path;
|
|
23
|
+
if (!filePath) {
|
|
24
|
+
allow();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const cwd = input.cwd ?? process.cwd();
|
|
28
|
+
const resolved = resolve(cwd, filePath);
|
|
29
|
+
const name = basename(resolved);
|
|
30
|
+
if (isBlockedPath(resolved, DEFAULT_BLOCKED_PATTERNS)) {
|
|
31
|
+
block(
|
|
32
|
+
`[agentmask] BLOCKED: "${name}" is a protected secret file.
|
|
33
|
+
Use mcp__agentmask__safe_read to get a redacted view of this file.
|
|
34
|
+
Use mcp__agentmask__env_names to see variable names without values.`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const entry = isInBlocklist(filePath, cwd);
|
|
38
|
+
if (entry) {
|
|
39
|
+
const types = entry.secrets.join(", ");
|
|
40
|
+
block(
|
|
41
|
+
`[agentmask] BLOCKED: "${name}" contains detected secrets (${types}).
|
|
42
|
+
Use mcp__agentmask__safe_read to get a redacted view of this file.
|
|
43
|
+
To unblock after fixing: agentmask allow-path "${filePath}"`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
allow();
|
|
47
|
+
}
|
|
48
|
+
main().catch(() => process.exit(1));
|
|
49
|
+
//# sourceMappingURL=pre-read-4YE6QMWV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/pre-read.ts"],"sourcesContent":["import { resolve, basename } from \"node:path\";\nimport { readStdin, block, allow, startSafetyTimer } from \"./common.js\";\nimport { isBlockedPath, DEFAULT_BLOCKED_PATTERNS } from \"../scanner/file-patterns.js\";\nimport { isInBlocklist } from \"./blocklist.js\";\n\nstartSafetyTimer();\n\nasync function main() {\n const input = await readStdin();\n const filePath = input.tool_input?.file_path as string | undefined;\n\n if (!filePath) {\n allow();\n return;\n }\n\n const cwd = input.cwd ?? process.cwd();\n const resolved = resolve(cwd, filePath);\n const name = basename(resolved);\n\n // Check 1: Static patterns (.env, *.pem, credentials.json, etc.)\n if (isBlockedPath(resolved, DEFAULT_BLOCKED_PATTERNS)) {\n block(\n `[agentmask] BLOCKED: \"${name}\" is a protected secret file.\\n` +\n `Use mcp__agentmask__safe_read to get a redacted view of this file.\\n` +\n `Use mcp__agentmask__env_names to see variable names without values.`,\n );\n }\n\n // Check 2: Dynamic blocklist (files where secrets were detected)\n const entry = isInBlocklist(filePath, cwd);\n if (entry) {\n const types = entry.secrets.join(\", \");\n block(\n `[agentmask] BLOCKED: \"${name}\" contains detected secrets (${types}).\\n` +\n `Use mcp__agentmask__safe_read to get a redacted view of this file.\\n` +\n `To unblock after fixing: agentmask allow-path \"${filePath}\"`,\n );\n }\n\n allow();\n}\n\nmain().catch(() => process.exit(1));\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,gBAAgB;AAKlC,iBAAiB;AAEjB,eAAe,OAAO;AACpB,QAAM,QAAQ,MAAM,UAAU;AAC9B,QAAM,WAAW,MAAM,YAAY;AAEnC,MAAI,CAAC,UAAU;AACb,UAAM;AACN;AAAA,EACF;AAEA,QAAM,MAAM,MAAM,OAAO,QAAQ,IAAI;AACrC,QAAM,WAAW,QAAQ,KAAK,QAAQ;AACtC,QAAM,OAAO,SAAS,QAAQ;AAG9B,MAAI,cAAc,UAAU,wBAAwB,GAAG;AACrD;AAAA,MACE,yBAAyB,IAAI;AAAA;AAAA;AAAA,IAG/B;AAAA,EACF;AAGA,QAAM,QAAQ,cAAc,UAAU,GAAG;AACzC,MAAI,OAAO;AACT,UAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI;AACrC;AAAA,MACE,yBAAyB,IAAI,gCAAgC,KAAK;AAAA;AAAA,iDAEd,QAAQ;AAAA,IAC9D;AAAA,EACF;AAEA,QAAM;AACR;AAEA,KAAK,EAAE,MAAM,MAAM,QAAQ,KAAK,CAAC,CAAC;","names":[]}
|