@blogic-cz/agent-tools 1.4.1 → 1.4.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/README.md +16 -2
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/types.d.ts +2 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/credential-guard/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/schemas/agent-tools.schema.json +8 -0
- package/src/config/loader.ts +1 -0
- package/src/config/types.ts +2 -0
- package/src/credential-guard/claude-hook.ts +8 -2
- package/src/credential-guard/index.ts +212 -3
package/README.md
CHANGED
|
@@ -634,6 +634,14 @@ Claude Code uses shell command hooks. The package ships a ready-made wrapper scr
|
|
|
634
634
|
|
|
635
635
|
That's it. The hook reads tool input from stdin, runs the guard, and exits with code 2 (blocked + reason on stderr) or 0 (allowed).
|
|
636
636
|
|
|
637
|
+
The guard permits static `printenv` reads only for exact names in `credentialGuard.allowedEnvironmentVariables`. The default list is empty. Multiple names are allowed only when each name is approved.
|
|
638
|
+
It recognizes `rtk` and `rtk proxy` prefixes and checks each command in a pipeline or chain.
|
|
639
|
+
Literal environment-command text in `rg`, `grep`, `git grep`, `echo`, and `printf` is allowed. In pipelines, every other command must be a known passive text command or an approved static environment read.
|
|
640
|
+
Each pipeline is checked separately from statements joined by `;`, `&&`, or `||`.
|
|
641
|
+
Execution options, other environment reads, and environment reads in dynamic or unsupported shell syntax stay blocked. Custom dangerous-command patterns still apply to the complete input.
|
|
642
|
+
Active brace expansion is refused, including path expansions such as `src/{a,b}`. Fully quoted literal brace text remains usable in static searches.
|
|
643
|
+
Single literal `echo`/`printf` writes with output redirection and single `cat`/`tee` heredoc writes are allowed. Heredoc braces are data. Unquoted heredocs with `$`, backticks, or backslashes, interpreter heredocs, and writes followed by more commands do not receive this exception. Use a quoted delimiter for literal code.
|
|
644
|
+
|
|
637
645
|
### Setup for OpenCode
|
|
638
646
|
|
|
639
647
|
OpenCode loads plugins automatically from `.opencode/plugins/`. Create a plugin file:
|
|
@@ -641,10 +649,12 @@ OpenCode loads plugins automatically from `.opencode/plugins/`. Create a plugin
|
|
|
641
649
|
**`.opencode/plugins/credential-guard.ts`**
|
|
642
650
|
|
|
643
651
|
```typescript
|
|
644
|
-
import {
|
|
652
|
+
import { createCredentialGuard } from "@blogic-cz/agent-tools/credential-guard";
|
|
653
|
+
import { loadConfig } from "@blogic-cz/agent-tools/config";
|
|
645
654
|
|
|
646
655
|
export const CredentialGuard = async () => ({
|
|
647
|
-
"tool.execute.before":
|
|
656
|
+
"tool.execute.before": createCredentialGuard((await loadConfig())?.credentialGuard)
|
|
657
|
+
.handleToolExecuteBefore,
|
|
648
658
|
});
|
|
649
659
|
```
|
|
650
660
|
|
|
@@ -667,6 +677,8 @@ Use the `credentialGuard` config section to extend built-in defaults (arrays are
|
|
|
667
677
|
```json5
|
|
668
678
|
{
|
|
669
679
|
credentialGuard: {
|
|
680
|
+
// Approve names only after checking that their values cannot contain secrets.
|
|
681
|
+
allowedEnvironmentVariables: ["TEST_FLAG", "WORKSPACE_LABEL"],
|
|
670
682
|
additionalBlockedPaths: ["private/secrets/"],
|
|
671
683
|
additionalAllowedPaths: ["apps/web-app/.env.test"],
|
|
672
684
|
additionalBlockedCliTools: [{ tool: "helm", suggestion: "Use agent-tools-k8s instead" }],
|
|
@@ -675,6 +687,8 @@ Use the `credentialGuard` config section to extend built-in defaults (arrays are
|
|
|
675
687
|
}
|
|
676
688
|
```
|
|
677
689
|
|
|
690
|
+
The packaged Claude hook loads this configuration from the current directory and its parent directories. Custom adapters must load the configuration and pass `credentialGuard` to `createCredentialGuard`, as the OpenCode example does. Invalid configuration blocks the tool call.
|
|
691
|
+
|
|
678
692
|
### Extending the guard
|
|
679
693
|
|
|
680
694
|
The guard source is at [`src/credential-guard/index.ts`](./src/credential-guard/index.ts). Fork the repo, adjust patterns, submit a PR: https://github.com/blogic-cz/agent-tools
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAgB,KAAK,EAAU,MAAM,QAAQ,CAAC;AAE9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAgB,KAAK,EAAU,MAAM,QAAQ,CAAC;AAE9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAuOlE,wBAAgB,YAAY,CAC1B,MAAM,EAAE,OAAO,EACf,UAAU,GAAE,MAA4B,GACvC,gBAAgB,CAmClB;AAmFD,wBAAsB,UAAU,IAAI,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAcxE;;AAED,qBAAa,aAAc,SAAQ,kBAElC;CAAG;;;;AAEJ,qBAAa,eAAgB,SAAQ,qBAAoC;IACvE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,CAAC;CAAG;AAEL,eAAO,MAAM,kBAAkB,oDAM9B,CAAC;AAEF,KAAK,eAAe,GAAG,MAAM,IAAI,CAC/B,gBAAgB,EAChB,OAAO,GAAG,eAAe,GAAG,YAAY,GAAG,UAAU,GAAG,eAAe,GAAG,MAAM,CACjF,CAAC;AAEF,wBAAgB,aAAa,CAAC,CAAC,EAC7B,MAAM,EAAE,gBAAgB,GAAG,SAAS,EACpC,OAAO,EAAE,eAAe,EACxB,OAAO,CAAC,EAAE,MAAM,GACf,CAAC,GAAG,SAAS,CAkCf;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAE9F;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,gBAAgB,GAAG,SAAS,EACpC,OAAO,CAAC,EAAE,MAAM,GACf,gBAAgB,GAAG,SAAS,CAc9B;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,gBAAgB,GAAG,SAAS,EACpC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACrB,MAAM,GAAG,SAAS,CAqBpB"}
|
package/dist/config/types.d.ts
CHANGED
|
@@ -163,6 +163,8 @@ export type CliToolOverride = {
|
|
|
163
163
|
};
|
|
164
164
|
/** Credential guard config - merged with built-in defaults */
|
|
165
165
|
export type CredentialGuardConfig = {
|
|
166
|
+
/** Exact, case-sensitive names approved for static printenv reads. Empty by default. */
|
|
167
|
+
allowedEnvironmentVariables?: string[];
|
|
166
168
|
additionalBlockedPaths?: string[];
|
|
167
169
|
additionalAllowedPaths?: string[];
|
|
168
170
|
additionalBlockedCliTools?: CliToolOverride[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/credential-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAM5E,8CAA8C;AAC9C,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,+CAA+C;AAC/C,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,CAAC;AAQF,+CAA+C;AAC/C,MAAM,MAAM,eAAe,GAAG;IAC5B,uBAAuB,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IACxE,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3E,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,sBAAsB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACrD,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjF,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACxD,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/credential-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAM5E,8CAA8C;AAC9C,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,+CAA+C;AAC/C,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,CAAC;AAQF,+CAA+C;AAC/C,MAAM,MAAM,eAAe,GAAG;IAC5B,uBAAuB,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IACxE,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3E,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,sBAAsB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACrD,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjF,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACxD,CAAC;AAwYF,6CAA6C;AAC7C,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAErE;AAED,2CAA2C;AAC3C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAEpE;AAED,2CAA2C;AAC3C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAEpE;AAMD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,eAAe,CAqNrF;AAQD,kEAAkE;AAClE,eAAO,MAAM,uBAAuB,UAvoBD,SAAS,UAAU,UAAU,KAAK,IAuoBM,CAAC;AAE5E,oDAAoD;AACpD,eAAO,MAAM,aAAa,YAzoBC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAyoBjB,CAAC;AAExD,yEAAyE;AACzE,eAAO,MAAM,aAAa,aA3oBE,MAAM,KAAK,OA2oBgB,CAAC;AAExD,yDAAyD;AACzD,eAAO,MAAM,aAAa,aA7oBE,MAAM,KAAK,OA6oBgB,CAAC;AAExD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,YA/oBC,MAAM,KAAK,OA+oB0B,CAAC;AAE1E,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,YAjpBC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAipBf,CAAC;AAEhE,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,YAnpBC,MAAM,KAAK,OAmpBsB,CAAC;AAElE,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,YArpBC,MAAM,KAAK,MAAM,GAAG,IAqpBa,CAAC"}
|
package/package.json
CHANGED
|
@@ -463,6 +463,14 @@
|
|
|
463
463
|
"type": "object",
|
|
464
464
|
"additionalProperties": false,
|
|
465
465
|
"properties": {
|
|
466
|
+
"allowedEnvironmentVariables": {
|
|
467
|
+
"description": "Exact, case-sensitive environment names approved for static printenv reads. Empty by default.",
|
|
468
|
+
"type": "array",
|
|
469
|
+
"items": {
|
|
470
|
+
"type": "string",
|
|
471
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
472
|
+
}
|
|
473
|
+
},
|
|
466
474
|
"additionalBlockedPaths": {
|
|
467
475
|
"description": "Additional blocked file path patterns.",
|
|
468
476
|
"type": "array",
|
package/src/config/loader.ts
CHANGED
|
@@ -11,6 +11,7 @@ const CliToolOverrideSchema = Schema.Struct({
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
const CredentialGuardConfigSchema = Schema.Struct({
|
|
14
|
+
allowedEnvironmentVariables: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
14
15
|
additionalBlockedPaths: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
15
16
|
additionalAllowedPaths: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
16
17
|
additionalBlockedCliTools: Schema.optionalKey(Schema.Array(CliToolOverrideSchema)),
|
package/src/config/types.ts
CHANGED
|
@@ -185,6 +185,8 @@ export type CliToolOverride = {
|
|
|
185
185
|
|
|
186
186
|
/** Credential guard config - merged with built-in defaults */
|
|
187
187
|
export type CredentialGuardConfig = {
|
|
188
|
+
/** Exact, case-sensitive names approved for static printenv reads. Empty by default. */
|
|
189
|
+
allowedEnvironmentVariables?: string[];
|
|
188
190
|
additionalBlockedPaths?: string[];
|
|
189
191
|
additionalAllowedPaths?: string[];
|
|
190
192
|
additionalBlockedCliTools?: CliToolOverride[];
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* "command": "bun node_modules/@blogic-cz/agent-tools/src/credential-guard/claude-hook.ts" }] }] } }
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { loadConfig } from "#config/loader";
|
|
14
|
+
|
|
15
|
+
import { createCredentialGuard } from "./index";
|
|
14
16
|
|
|
15
17
|
const stdin = await Bun.stdin.text();
|
|
16
18
|
|
|
@@ -20,7 +22,11 @@ try {
|
|
|
20
22
|
tool_input?: Record<string, unknown>;
|
|
21
23
|
} = JSON.parse(stdin);
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
const config = await loadConfig();
|
|
26
|
+
createCredentialGuard(config?.credentialGuard).handleToolExecuteBefore(
|
|
27
|
+
{ tool: data.tool_name },
|
|
28
|
+
{ args: data.tool_input ?? {} },
|
|
29
|
+
);
|
|
24
30
|
} catch (error: unknown) {
|
|
25
31
|
const message = error instanceof Error ? error.message : String(error);
|
|
26
32
|
process.stderr.write(message);
|
|
@@ -143,8 +143,6 @@ const SECRET_PATTERNS = [
|
|
|
143
143
|
* Dangerous bash patterns that might expose secrets.
|
|
144
144
|
*/
|
|
145
145
|
const DEFAULT_DANGEROUS_BASH_PATTERNS: RegExp[] = [
|
|
146
|
-
/printenv/i,
|
|
147
|
-
/(?:^|&&|\||;)\s*env(?:\s|$)/i,
|
|
148
146
|
/\bcat\s+\S*\.env/i,
|
|
149
147
|
/\bcat\s+\S*\.pem/i,
|
|
150
148
|
/\bcat\s+\S*\.key/i,
|
|
@@ -245,6 +243,201 @@ function escapeRegex(s: string): string {
|
|
|
245
243
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
246
244
|
}
|
|
247
245
|
|
|
246
|
+
/** Static shell words only. Never expand variables or execute a command. */
|
|
247
|
+
function parseStaticShellCommands(
|
|
248
|
+
command: string,
|
|
249
|
+
): { pipelines: string[][][] } | "brace-expansion" | undefined {
|
|
250
|
+
const pipelines: string[][][] = [];
|
|
251
|
+
let commands: string[][] = [];
|
|
252
|
+
let argv: string[] = [];
|
|
253
|
+
let word = "";
|
|
254
|
+
let started = false;
|
|
255
|
+
let quote: "'" | '"' | undefined;
|
|
256
|
+
|
|
257
|
+
const finishWord = () => {
|
|
258
|
+
if (started) argv.push(word);
|
|
259
|
+
word = "";
|
|
260
|
+
started = false;
|
|
261
|
+
};
|
|
262
|
+
const finishCommand = () => {
|
|
263
|
+
finishWord();
|
|
264
|
+
if (argv.length) commands.push(argv);
|
|
265
|
+
argv = [];
|
|
266
|
+
};
|
|
267
|
+
const finishPipeline = () => {
|
|
268
|
+
finishCommand();
|
|
269
|
+
if (commands.length) pipelines.push(commands);
|
|
270
|
+
commands = [];
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
for (let i = 0; i < command.length; i++) {
|
|
274
|
+
const char = command.charAt(i);
|
|
275
|
+
if (quote === "'") {
|
|
276
|
+
if (char === quote) quote = undefined;
|
|
277
|
+
else word += char;
|
|
278
|
+
} else if (char === "\\") {
|
|
279
|
+
const next = command[++i];
|
|
280
|
+
if (next === undefined || next === "\n" || next === "\r") return undefined;
|
|
281
|
+
if (quote === '"' && !/[\\$`"]/.test(next)) word += "\\";
|
|
282
|
+
word += next;
|
|
283
|
+
started = true;
|
|
284
|
+
} else if (char === "$" || char === "`") {
|
|
285
|
+
return undefined;
|
|
286
|
+
} else if (quote === '"') {
|
|
287
|
+
if (char === quote) quote = undefined;
|
|
288
|
+
else word += char;
|
|
289
|
+
} else if (char === "'" || char === '"') {
|
|
290
|
+
quote = char;
|
|
291
|
+
started = true;
|
|
292
|
+
} else if (char === "{" && /^\{[^{}]*(?:,|\.\.)[^{}]*\}/.test(command.slice(i))) {
|
|
293
|
+
return "brace-expansion";
|
|
294
|
+
} else if (/[<>()]/.test(char) || (char === "#" && !started)) {
|
|
295
|
+
return undefined;
|
|
296
|
+
} else if (char === "|" && command[i - 1] !== "|" && command[i + 1] !== "|") {
|
|
297
|
+
finishCommand();
|
|
298
|
+
if (command[i + 1] === "&") i++;
|
|
299
|
+
} else if (/[;&|\r\n]/.test(char)) {
|
|
300
|
+
finishPipeline();
|
|
301
|
+
} else if (/\s/.test(char)) {
|
|
302
|
+
finishWord();
|
|
303
|
+
} else {
|
|
304
|
+
word += char;
|
|
305
|
+
started = true;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (quote) return undefined;
|
|
310
|
+
finishPipeline();
|
|
311
|
+
return { pipelines };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function mentionsEnvironmentRead(text: string): boolean {
|
|
315
|
+
return /printenv|\benv\b/i.test(text.replace(/['"\\]/g, ""));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function hasBraceExpansion(text: string): boolean {
|
|
319
|
+
return /\{[^{}]*(?:,|\.\.)[^{}]*\}/.test(text);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function unwrapStaticCommand(words: string[]): string[] {
|
|
323
|
+
const argv = [...words];
|
|
324
|
+
const executable = () => argv[0]?.split("/").at(-1);
|
|
325
|
+
while (executable() === "rtk" || executable() === "command") {
|
|
326
|
+
const wrapper = executable();
|
|
327
|
+
argv.shift();
|
|
328
|
+
if (wrapper === "rtk" && argv[0] === "proxy") argv.shift();
|
|
329
|
+
if (wrapper === "command" && argv[0] === "--") argv.shift();
|
|
330
|
+
}
|
|
331
|
+
return argv;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function isAllowedEnvironmentRead(argv: string[], allowedNames: Set<string>): boolean {
|
|
335
|
+
if (argv[0]?.split("/").at(-1) !== "printenv") return false;
|
|
336
|
+
const args = argv.slice(1);
|
|
337
|
+
if (args[0] === "--") args.shift();
|
|
338
|
+
return args.length > 0 && args.every((name) => allowedNames.has(name));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function hasExecutionOption(args: string[], longOptions: string[], shortOption?: string): boolean {
|
|
342
|
+
return args.some((arg) => {
|
|
343
|
+
const flag = arg.split("=", 1)[0] ?? "";
|
|
344
|
+
if (flag.startsWith("--") && flag.length > 2) {
|
|
345
|
+
// Git supports long-option abbreviations. Refuse ambiguous prefixes too.
|
|
346
|
+
return longOptions.some((option) => option.startsWith(flag.slice(2)));
|
|
347
|
+
}
|
|
348
|
+
return shortOption !== undefined && /^-[^-]/.test(flag) && flag.includes(shortOption);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function isPassiveTextCommand(argv: string[]): boolean {
|
|
353
|
+
const name = argv[0]?.split("/").at(-1) ?? "";
|
|
354
|
+
const args = argv.slice(1);
|
|
355
|
+
// These commands consume literal text. Execution options are not exceptions.
|
|
356
|
+
if (["echo", "printf", "grep", "head"].includes(name)) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
if (name === "rg") return !hasExecutionOption(args, ["pre", "hostname-bin"]);
|
|
360
|
+
return (
|
|
361
|
+
name === "git" &&
|
|
362
|
+
args[0] === "grep" &&
|
|
363
|
+
!hasExecutionOption(args, ["open-files-in-pager", "textconv"], "O")
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function hasStaticEnvironmentRead(argv: string[], allowedNames: Set<string>): boolean {
|
|
368
|
+
const name = argv[0]?.split("/").at(-1);
|
|
369
|
+
if (name === "printenv") return !isAllowedEnvironmentRead(argv, allowedNames);
|
|
370
|
+
if (name === "env") return true;
|
|
371
|
+
if (isPassiveTextCommand(argv)) return false;
|
|
372
|
+
if (
|
|
373
|
+
argv.some((arg) =>
|
|
374
|
+
["sh", "bash", "zsh", "dash", "ksh", "fish", "csh", "tcsh", "eval"].includes(
|
|
375
|
+
arg.split("/").at(-1) ?? "",
|
|
376
|
+
),
|
|
377
|
+
) &&
|
|
378
|
+
hasBraceExpansion(argv.slice(1).join(" "))
|
|
379
|
+
) {
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
return mentionsEnvironmentRead(argv.join(" "));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function hasEnvironmentRead(command: string, allowedNames: Set<string>): boolean {
|
|
386
|
+
if (isLiteralTextWrite(command)) return false;
|
|
387
|
+
// ponytail: complex shell syntax stays conservative; use a shell AST if more exceptions are needed.
|
|
388
|
+
const parsed = parseStaticShellCommands(command);
|
|
389
|
+
if (parsed === "brace-expansion") return true;
|
|
390
|
+
if (!parsed) return mentionsEnvironmentRead(command) || hasBraceExpansion(command);
|
|
391
|
+
const pipelines = parsed.pipelines.map((commands) => commands.map(unwrapStaticCommand));
|
|
392
|
+
const unwrapped = pipelines.flat();
|
|
393
|
+
if (unwrapped.some((argv) => hasStaticEnvironmentRead(argv, allowedNames))) return true;
|
|
394
|
+
|
|
395
|
+
// A literal producer can feed executable text to a shell, xargs, or an unknown consumer.
|
|
396
|
+
return pipelines.some(
|
|
397
|
+
(pipeline) =>
|
|
398
|
+
pipeline.length > 1 &&
|
|
399
|
+
pipeline.some(
|
|
400
|
+
(argv) =>
|
|
401
|
+
isAllowedEnvironmentRead(argv, allowedNames) ||
|
|
402
|
+
(isPassiveTextCommand(argv) &&
|
|
403
|
+
(mentionsEnvironmentRead(argv.join(" ")) || hasBraceExpansion(argv.join(" ")))),
|
|
404
|
+
) &&
|
|
405
|
+
pipeline.some(
|
|
406
|
+
(argv) => !isPassiveTextCommand(argv) && !isAllowedEnvironmentRead(argv, allowedNames),
|
|
407
|
+
),
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** A single literal writer cannot execute its body or feed another command. */
|
|
412
|
+
function isLiteralTextWrite(command: string): boolean {
|
|
413
|
+
const lines = command.trimEnd().split(/\r?\n/);
|
|
414
|
+
const heredoc = /^(.*?)<<(-?)[ \t]*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\3[ \t]*$/.exec(lines[0] ?? "");
|
|
415
|
+
let header = command;
|
|
416
|
+
let writers = ["echo", "printf"];
|
|
417
|
+
if (heredoc) {
|
|
418
|
+
const [, prefix, stripTabs, quote, delimiter] = heredoc;
|
|
419
|
+
const end = lines.findIndex(
|
|
420
|
+
(line, index) => index > 0 && (stripTabs ? line.replace(/^\t+/, "") : line) === delimiter,
|
|
421
|
+
);
|
|
422
|
+
// Only the first delimiter closes the body. No following commands are exceptions.
|
|
423
|
+
if (end < 0 || end !== lines.length - 1) return false;
|
|
424
|
+
const body = lines.slice(1, end).join("\n");
|
|
425
|
+
if (!quote && /[$`\\]/.test(body)) return false;
|
|
426
|
+
header = prefix ?? "";
|
|
427
|
+
writers = ["cat", "tee"];
|
|
428
|
+
} else if (!command.includes(">")) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Output redirection does not execute text. Other unsupported syntax still fails parsing.
|
|
433
|
+
const parsed = parseStaticShellCommands(header.replace(/>/g, " "));
|
|
434
|
+
if (!parsed || parsed === "brace-expansion" || parsed.pipelines.length !== 1) return false;
|
|
435
|
+
const pipeline = parsed.pipelines[0];
|
|
436
|
+
if (pipeline?.length !== 1) return false;
|
|
437
|
+
const argv = unwrapStaticCommand(pipeline[0] ?? []);
|
|
438
|
+
return writers.includes(argv[0]?.split("/").at(-1) ?? "");
|
|
439
|
+
}
|
|
440
|
+
|
|
248
441
|
/** Extract file path from hook arguments. */
|
|
249
442
|
export function extractFilePath(args: Record<string, unknown>): string {
|
|
250
443
|
return (args.filePath as string) || (args.file_path as string) || (args.path as string) || "";
|
|
@@ -271,6 +464,19 @@ export function extractCommand(args: Record<string, unknown>): string {
|
|
|
271
464
|
* @returns Object with all guard functions bound to the merged pattern sets.
|
|
272
465
|
*/
|
|
273
466
|
export function createCredentialGuard(config?: CredentialGuardConfig): CredentialGuard {
|
|
467
|
+
const names = config?.allowedEnvironmentVariables;
|
|
468
|
+
if (
|
|
469
|
+
names !== undefined &&
|
|
470
|
+
(!Array.isArray(names) || names.some((name) => typeof name !== "string"))
|
|
471
|
+
) {
|
|
472
|
+
throw new Error("allowedEnvironmentVariables must be an array of strings");
|
|
473
|
+
}
|
|
474
|
+
const allowedEnvironmentVariables = new Set(names ?? []);
|
|
475
|
+
for (const name of allowedEnvironmentVariables) {
|
|
476
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
477
|
+
throw new Error(`Invalid allowed environment variable name: ${JSON.stringify(name)}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
274
480
|
const blockedPathPatterns = [
|
|
275
481
|
...DEFAULT_BLOCKED_PATH_PATTERNS,
|
|
276
482
|
...(config?.additionalBlockedPaths ?? []).map((p) => new RegExp(p)),
|
|
@@ -332,7 +538,10 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
332
538
|
}
|
|
333
539
|
|
|
334
540
|
function isDangerousBashCommand(command: string): boolean {
|
|
335
|
-
return
|
|
541
|
+
return (
|
|
542
|
+
dangerousBashPatterns.some((pattern) => pattern.test(command)) ||
|
|
543
|
+
hasEnvironmentRead(command, allowedEnvironmentVariables)
|
|
544
|
+
);
|
|
336
545
|
}
|
|
337
546
|
|
|
338
547
|
function isGhCommandAllowed(command: string): boolean {
|