@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 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 { handleToolExecuteBefore } from "@blogic-cz/agent-tools/credential-guard";
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": handleToolExecuteBefore,
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;AAsOlE,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"}
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"}
@@ -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;AAuMF,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,CAqMrF;AAQD,kEAAkE;AAClE,eAAO,MAAM,uBAAuB,UAtbD,SAAS,UAAU,UAAU,KAAK,IAsbM,CAAC;AAE5E,oDAAoD;AACpD,eAAO,MAAM,aAAa,YAxbC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAwbjB,CAAC;AAExD,yEAAyE;AACzE,eAAO,MAAM,aAAa,aA1bE,MAAM,KAAK,OA0bgB,CAAC;AAExD,yDAAyD;AACzD,eAAO,MAAM,aAAa,aA5bE,MAAM,KAAK,OA4bgB,CAAC;AAExD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,YA9bC,MAAM,KAAK,OA8b0B,CAAC;AAE1E,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,YAhcC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAgcf,CAAC;AAEhE,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,YAlcC,MAAM,KAAK,OAkcsB,CAAC;AAElE,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,YApcC,MAAM,KAAK,MAAM,GAAG,IAoca,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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blogic-cz/agent-tools",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "CLI tools for AI coding agent workflows — GitHub, database, Kubernetes, Azure platform, Azure DevOps, logs, sessions, and audit",
5
5
  "keywords": [
6
6
  "agent",
@@ -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",
@@ -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)),
@@ -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 { handleToolExecuteBefore } from "./index";
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
- handleToolExecuteBefore({ tool: data.tool_name }, { args: data.tool_input ?? {} });
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 dangerousBashPatterns.some((pattern) => pattern.test(command));
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 {