@blogic-cz/agent-tools 1.5.1 → 1.5.3
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/dist/credential-guard/index.d.ts.map +1 -1
- package/dist/gh-tool/api.d.ts.map +1 -1
- package/dist/gh-tool/gist.d.ts.map +1 -1
- package/dist/gh-tool/service.d.ts.map +1 -1
- package/dist/gh-tool/text-input.d.ts +4 -0
- package/dist/gh-tool/text-input.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/credential-guard/index.ts +311 -36
- package/src/gh-tool/api.ts +21 -1
- package/src/gh-tool/gist.ts +7 -1
- package/src/gh-tool/service.ts +9 -5
- package/src/gh-tool/text-input.ts +63 -4
|
@@ -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;AAgsBF,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,CAuNrF;AAQD,kEAAkE;AAClE,eAAO,MAAM,uBAAuB,UAj8BD,SAAS,UAAU,UAAU,KAAK,IAi8BM,CAAC;AAE5E,oDAAoD;AACpD,eAAO,MAAM,aAAa,YAn8BC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAm8BjB,CAAC;AAExD,yEAAyE;AACzE,eAAO,MAAM,aAAa,aAr8BE,MAAM,KAAK,OAq8BgB,CAAC;AAExD,yDAAyD;AACzD,eAAO,MAAM,aAAa,aAv8BE,MAAM,KAAK,OAu8BgB,CAAC;AAExD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,YAz8BC,MAAM,KAAK,OAy8B0B,CAAC;AAE1E,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,YA38BC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IA28Bf,CAAC;AAEhE,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,YA78BC,MAAM,KAAK,OA68BsB,CAAC;AAElE,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,YA/8BC,MAAM,KAAK,MAAM,GAAG,IA+8Ba,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/gh-tool/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,OAAO,EAAE,eAAe,EAA2C,MAAM,UAAU,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/gh-tool/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,OAAO,EAAE,eAAe,EAA2C,MAAM,UAAU,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAU/C,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAYF,eAAO,MAAM,kBAAkB,qDA4B7B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B,CAAC;AA4FF,eAAO,MAAM,SAAS,GAAI,CAAC,QACnB,gBAAgB,KACrB,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,cAAc,CAQjD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gist.d.ts","sourceRoot":"","sources":["../../src/gh-tool/gist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAQ,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAKxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAO1C,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,KAAK,OAAO,GAAG;IACb,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACpC,CAAC;AAYF,KAAK,QAAQ,GAAG;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,OAAO,EAAE,IAAI,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAoHF,eAAO,MAAM,YAAY,SACjB,OAAO,QACP;IAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,KACtD,UA4BF,CAAC;AAEF,eAAO,MAAM,oBAAoB,SACzB,MAAM,GAAG,IAAI,YACT,MAAM,GAAG,IAAI,WACd,MAAM,KACd,kBAAkB,GAAG,IASvB,CAAC;AAEF,eAAO,MAAM,iBAAiB,SAAU;IACtC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,KAAG,kBAAkB,GAAG,IAgBxB,CAAC;AAkCF,eAAO,MAAM,UAAU;WACd,MAAM,EAAE;iBACF,MAAM,GAAG,IAAI;YAClB,OAAO;
|
|
1
|
+
{"version":3,"file":"gist.d.ts","sourceRoot":"","sources":["../../src/gh-tool/gist.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/gh-tool/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGxC,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAGpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,EAAE,aAAa,EAA4C,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/gh-tool/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGxC,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAGpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,EAAE,aAAa,EAA4C,MAAM,SAAS,CAAC;AA4DlF,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,kBAAkB,GAAG,eAAe,GAAG,mBAAmB,CAAC;;oBAK7D,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC;wBAChD,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC;yBAC/C,CACnB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,KAC9C,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC;yBACf,CAAC,CAAC,EACrB,IAAI,EAAE,gBAAgB,KACnB,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;4BACzB,MAAM,MAAM,CAAC,MAAM,CAAC,gBAAgB,GAAG,SAAS,EAAE,KAAK,CAAC;0BAC1D,MAAM,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC;6BACnC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAC/B,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,kBAAkB,EAAE,CAAC,CAAC;;AAjBpD,qBAAa,aAAc,SAAQ,kBAmBF;IAC/B,MAAM,CAAC,QAAQ,CAAC,KAAK,6FAyRnB;CACH"}
|
|
@@ -2,6 +2,10 @@ import { Effect, Schema } from "effect";
|
|
|
2
2
|
import { GitHubCommandError } from "#gh/errors";
|
|
3
3
|
declare const MissingMode: Schema.Literals<readonly ["error", "null", "default"]>;
|
|
4
4
|
export declare const isSensitivePath: (filePath: string) => boolean;
|
|
5
|
+
/** Inspect the final text, after shell expansion and after reading files or stdin. */
|
|
6
|
+
export declare function unsafeOutboundTextReason(text: string, environment?: Record<string, string | undefined>): string | null;
|
|
7
|
+
export declare const validateOutboundText: (text: string, command: string) => Effect.Effect<string, never, never> | Effect.Effect<never, GitHubCommandError, never>;
|
|
8
|
+
export declare const validateOutboundFile: (filePath: string, command: string) => Effect.Effect<void, GitHubCommandError, never>;
|
|
5
9
|
type ResolveTextInputOptions = {
|
|
6
10
|
command: string;
|
|
7
11
|
value: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-input.d.ts","sourceRoot":"","sources":["../../src/gh-tool/text-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"text-input.d.ts","sourceRoot":"","sources":["../../src/gh-tool/text-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAUhD,QAAA,MAAM,WAAW,wDAAgD,CAAC;AAIlE,eAAO,MAAM,eAAe,aAAc,MAAM,YACmB,CAAC;AAEpE,sFAAsF;AACtF,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAC5D,MAAM,GAAG,IAAI,CAqBf;AAED,eAAO,MAAM,oBAAoB,SAAU,MAAM,WAAW,MAAM,0FAYjE,CAAC;AAEF,eAAO,MAAM,oBAAoB,aAAc,MAAM,WAAW,MAAM,mDAanE,CAAC;AAkBJ,KAAK,uBAAuB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,WAAW,CAAC,CAAC;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAwFF,eAAO,MAAM,wBAAwB,YAC1B,IAAI,CAAC,uBAAuB,EAAE,aAAa,GAAG,cAAc,CAAC,KACrE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAIuD,CAAC;AAEnG,eAAO,MAAM,wBAAwB,YAC1B,IAAI,CAAC,uBAAuB,EAAE,aAAa,GAAG,cAAc,CAAC,KACrE,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,EAAE,kBAAkB,CAI9C,CAAC;AAEL,eAAO,MAAM,uBAAuB,YACzB,IAAI,CAAC,uBAAuB,EAAE,aAAa,GAAG,cAAc,CAAC,GAAG;IACvE,YAAY,EAAE,MAAM,CAAC;CACtB,KACA,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAKsD,CAAC"}
|
package/package.json
CHANGED
|
@@ -139,19 +139,6 @@ const SECRET_PATTERNS = [
|
|
|
139
139
|
},
|
|
140
140
|
];
|
|
141
141
|
|
|
142
|
-
/**
|
|
143
|
-
* Dangerous bash patterns that might expose secrets.
|
|
144
|
-
*/
|
|
145
|
-
const DEFAULT_DANGEROUS_BASH_PATTERNS: RegExp[] = [
|
|
146
|
-
/\bcat\s+\S*\.env/i,
|
|
147
|
-
/\bcat\s+\S*\.pem/i,
|
|
148
|
-
/\bcat\s+\S*\.key/i,
|
|
149
|
-
/\bcat\s+\S*secret/i,
|
|
150
|
-
/\bcat\s+\S*credential/i,
|
|
151
|
-
/\bcat\s+\S*\/\.ssh\//i,
|
|
152
|
-
/\bcat\s+\S*\/\.aws\//i,
|
|
153
|
-
];
|
|
154
|
-
|
|
155
142
|
/**
|
|
156
143
|
* CLI tools that must use wrapper tools for security and audit.
|
|
157
144
|
*/
|
|
@@ -313,13 +300,28 @@ function parseStaticShellCommands(
|
|
|
313
300
|
|
|
314
301
|
function mentionsEnvironmentRead(text: string): boolean {
|
|
315
302
|
// `--env dev` is a flag of another command, not the env command.
|
|
316
|
-
return /printenv|(
|
|
303
|
+
return /(?<![\w.-])printenv\b(?!-)|(?<![\w.-])-\w*Oprintenv\b|(?<![\w.-])\benv\b(?!-)/i.test(
|
|
304
|
+
text.replace(/['"\\]/g, ""),
|
|
305
|
+
);
|
|
317
306
|
}
|
|
318
307
|
|
|
319
308
|
function hasBraceExpansion(text: string): boolean {
|
|
320
309
|
return /\{[^{}]*(?:,|\.\.)[^{}]*\}/.test(text);
|
|
321
310
|
}
|
|
322
311
|
|
|
312
|
+
function hasNonExecutingAwkProgram(text: string): boolean {
|
|
313
|
+
const match = /\bawk\s+(['"])([\s\S]*?)\1/.exec(text);
|
|
314
|
+
if (!match) return false;
|
|
315
|
+
const program = match[2] ?? "";
|
|
316
|
+
return !/\b(?:system|getline)\s*\(|\||\b(?:print|printf)\b[^\n]*>/.test(program);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function hasShellBraceExpansion(text: string): boolean {
|
|
320
|
+
if (!hasBraceExpansion(text)) return false;
|
|
321
|
+
if (!hasNonExecutingAwkProgram(text)) return true;
|
|
322
|
+
return hasBraceExpansion(text.replace(/(\bawk\s+(['"]))[\s\S]*?\2/, "awk "));
|
|
323
|
+
}
|
|
324
|
+
|
|
323
325
|
/**
|
|
324
326
|
* The command without quoted regex quantifiers such as `{0,80}`. Many commands re-parse quoted
|
|
325
327
|
* text as shell code, so every other quoted brace stays. When both sides are digits, the expansion
|
|
@@ -355,7 +357,23 @@ function withoutQuotedQuantifiers(command: string): string {
|
|
|
355
357
|
|
|
356
358
|
/** Braces in static words, except `{m,n}` with digits on both sides (see above). */
|
|
357
359
|
function hasArgumentBraceExpansion(text: string): boolean {
|
|
358
|
-
return
|
|
360
|
+
return hasShellBraceExpansion(text.replace(/\{\d+,\d+\}/g, ""));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function hasCommandArgumentBraceExpansion(argv: string[]): boolean {
|
|
364
|
+
// LogQL is data for this wrapper; its quoted label selectors are never shell-evaluated.
|
|
365
|
+
if (
|
|
366
|
+
argv[0]?.split("/").at(-1) === "bun" &&
|
|
367
|
+
argv[1] === "run" &&
|
|
368
|
+
argv[2] === "observability-tool" &&
|
|
369
|
+
argv[3] === "logs" &&
|
|
370
|
+
argv[4] === "query"
|
|
371
|
+
)
|
|
372
|
+
return false;
|
|
373
|
+
return (argv[0]?.split("/").at(-1) === "awk" && !isAwkExecution(argv)) ||
|
|
374
|
+
isJqObjectConstruction(argv)
|
|
375
|
+
? false
|
|
376
|
+
: hasArgumentBraceExpansion(argv.join(" "));
|
|
359
377
|
}
|
|
360
378
|
|
|
361
379
|
/** echo and printf print their arguments; grep -o, rg and git grep print matched pattern text. */
|
|
@@ -363,6 +381,68 @@ function isLiteralTextProducer(argv: string[]): boolean {
|
|
|
363
381
|
return isPassiveTextCommand(argv) && argv[0]?.split("/").at(-1) !== "head";
|
|
364
382
|
}
|
|
365
383
|
|
|
384
|
+
function isAwkExecution(argv: string[]): boolean {
|
|
385
|
+
if (argv[0]?.split("/").at(-1) !== "awk") return false;
|
|
386
|
+
const program = argv.slice(1).join(" ");
|
|
387
|
+
return /\b(?:system|getline)\s*\(|\||\b(?:print|printf)\b[^\n]*>/.test(program);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function isJqObjectConstruction(argv: string[]): boolean {
|
|
391
|
+
return (
|
|
392
|
+
argv[0]?.split("/").at(-1) === "jq" &&
|
|
393
|
+
argv
|
|
394
|
+
.slice(1)
|
|
395
|
+
.some((arg) => /^\{[A-Za-z_][A-Za-z0-9_-]*(?:,\s*[A-Za-z_][A-Za-z0-9_-]*)*\}$/.test(arg)) &&
|
|
396
|
+
!argv.slice(1).some((arg) => /\benv\b|\binput(?:s)?\b/.test(arg))
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** Index in `args` where the command wrapped by `env` starts, or null when `env` only lists. */
|
|
401
|
+
function environmentCommandStart(args: string[]): number | null {
|
|
402
|
+
for (let i = 0; i < args.length; i++) {
|
|
403
|
+
const arg = args[i] ?? "";
|
|
404
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(arg)) continue;
|
|
405
|
+
if (arg === "--") return i + 1 === args.length ? null : i + 1;
|
|
406
|
+
if (arg === "-u" || arg === "--unset" || arg === "-C" || arg === "--chdir") {
|
|
407
|
+
i++;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (arg === "-i" || arg === "--ignore-environment" || arg.startsWith("--chdir=")) continue;
|
|
411
|
+
if (arg.startsWith("-")) continue;
|
|
412
|
+
return i;
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function isEnvironmentListing(argv: string[]): boolean {
|
|
418
|
+
return environmentCommandStart(argv.slice(1)) === null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function unwrapEnvironmentCommand(argv: string[]): string[] | null {
|
|
422
|
+
if (argv[0]?.split("/").at(-1) !== "env") return null;
|
|
423
|
+
const args = argv.slice(1);
|
|
424
|
+
const start = environmentCommandStart(args);
|
|
425
|
+
return start === null ? null : args.slice(start);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function isSafeLiteralInspection(argv: string[]): boolean {
|
|
429
|
+
const name = argv[0]?.split("/").at(-1) ?? "";
|
|
430
|
+
return (
|
|
431
|
+
["head", "tail"].includes(name) &&
|
|
432
|
+
!argv.slice(1).some((arg) => /\.env(?:\.|$)|\.(?:pem|key)\b|secret|credential/i.test(arg))
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function isSafeLiteralConsumer(argv: string[]): boolean {
|
|
437
|
+
return (
|
|
438
|
+
isSafeLiteralInspection(argv) ||
|
|
439
|
+
(argv[0]?.split("/").at(-1) === "bun" &&
|
|
440
|
+
argv[1] === "run" &&
|
|
441
|
+
argv[2] === "gh-tool" &&
|
|
442
|
+
argv.includes("--body-file"))
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
366
446
|
function unwrapStaticCommand(words: string[]): string[] {
|
|
367
447
|
const argv = [...words];
|
|
368
448
|
const executable = () => argv[0]?.split("/").at(-1);
|
|
@@ -401,6 +481,8 @@ function isPassiveTextCommand(argv: string[]): boolean {
|
|
|
401
481
|
return true;
|
|
402
482
|
}
|
|
403
483
|
if (name === "rg") return !hasExecutionOption(args, ["pre", "hostname-bin"]);
|
|
484
|
+
if (name === "awk") return !isAwkExecution(argv);
|
|
485
|
+
if (name === "jq") return isJqObjectConstruction(argv);
|
|
404
486
|
return (
|
|
405
487
|
name === "git" &&
|
|
406
488
|
args[0] === "grep" &&
|
|
@@ -411,40 +493,79 @@ function isPassiveTextCommand(argv: string[]): boolean {
|
|
|
411
493
|
function hasStaticEnvironmentRead(argv: string[], allowedNames: Set<string>): boolean {
|
|
412
494
|
const name = argv[0]?.split("/").at(-1);
|
|
413
495
|
if (name === "printenv") return !isAllowedEnvironmentRead(argv, allowedNames);
|
|
414
|
-
if (name === "env")
|
|
496
|
+
if (name === "env") {
|
|
497
|
+
const wrapped = unwrapEnvironmentCommand(argv);
|
|
498
|
+
return wrapped === null
|
|
499
|
+
? isEnvironmentListing(argv)
|
|
500
|
+
: hasStaticEnvironmentRead(unwrapStaticCommand(wrapped), allowedNames);
|
|
501
|
+
}
|
|
415
502
|
if (isPassiveTextCommand(argv)) return false;
|
|
416
503
|
// Any command may re-parse an argument as shell code (trap, find -exec, awk system()).
|
|
417
|
-
return (
|
|
418
|
-
|
|
419
|
-
|
|
504
|
+
return hasCommandArgumentBraceExpansion(argv) || mentionsEnvironmentRead(argv.join(" "));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function isStaticHerdrPrompt(command: string): boolean {
|
|
508
|
+
if (/^herdr\s+agent\s+prompt\s+\S+\s+(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')$/.test(command.trim())) {
|
|
509
|
+
if (!/[$`]/.test(command)) return true;
|
|
510
|
+
return !hasSensitiveFileRead(command) && !mentionsEnvironmentRead(command);
|
|
511
|
+
}
|
|
512
|
+
const parsed = parseStaticShellCommands(command);
|
|
513
|
+
if (
|
|
514
|
+
!parsed ||
|
|
515
|
+
typeof parsed === "string" ||
|
|
516
|
+
parsed.pipelines.length !== 1 ||
|
|
517
|
+
parsed.pipelines[0]?.length !== 1
|
|
518
|
+
) {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
const argv = unwrapStaticCommand(parsed.pipelines[0]?.[0] ?? []);
|
|
522
|
+
return argv[0] === "herdr" && argv[1] === "agent" && argv[2] === "prompt";
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function isHerdrPrompt(command: string): boolean {
|
|
526
|
+
return /^herdr\s+agent\s+prompt\b/.test(command.trim());
|
|
420
527
|
}
|
|
421
528
|
|
|
422
529
|
function hasEnvironmentRead(command: string, allowedNames: Set<string>): boolean {
|
|
530
|
+
if (isStaticHerdrPrompt(command)) return false;
|
|
531
|
+
if (isHerdrPrompt(command)) return true;
|
|
423
532
|
if (isLiteralTextWrite(command)) return false;
|
|
424
533
|
// ponytail: complex shell syntax stays conservative; use a shell AST if more exceptions are needed.
|
|
425
534
|
const parsed = parseStaticShellCommands(command);
|
|
426
535
|
if (parsed === "brace-expansion") return true;
|
|
427
536
|
if (!parsed) {
|
|
428
|
-
|
|
537
|
+
const unquoted = command.replace(/'(?:\\.|[^'])*'/g, " ").replace(/"(?:\\.|[^"$`])*"/g, " ");
|
|
538
|
+
const hasExecutor =
|
|
539
|
+
/\|\s*(?:sh|bash|zsh|xargs)|\b(?:sh|bash|zsh)\b|\$\(|`|\b(?:eval|source)\b|\bfind\b.*\b-exec\b/i.test(
|
|
540
|
+
command,
|
|
541
|
+
);
|
|
542
|
+
return (
|
|
543
|
+
mentionsEnvironmentRead(unquoted) ||
|
|
544
|
+
(mentionsEnvironmentRead(command) && hasExecutor) ||
|
|
545
|
+
hasShellBraceExpansion(withoutQuotedQuantifiers(command))
|
|
546
|
+
);
|
|
429
547
|
}
|
|
430
548
|
const pipelines = parsed.pipelines.map((commands) => commands.map(unwrapStaticCommand));
|
|
431
549
|
const unwrapped = pipelines.flat();
|
|
432
|
-
if (unwrapped.some((argv) => hasStaticEnvironmentRead(argv, allowedNames)))
|
|
550
|
+
if (unwrapped.some((argv) => hasStaticEnvironmentRead(argv, allowedNames))) {
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
433
553
|
|
|
434
554
|
// A literal producer can feed executable text to a shell, xargs, or an unknown consumer.
|
|
435
|
-
|
|
555
|
+
const result = pipelines.some(
|
|
436
556
|
(pipeline) =>
|
|
437
557
|
pipeline.length > 1 &&
|
|
438
558
|
pipeline.some(
|
|
439
559
|
(argv) =>
|
|
440
560
|
isAllowedEnvironmentRead(argv, allowedNames) ||
|
|
441
561
|
(isLiteralTextProducer(argv) &&
|
|
442
|
-
(mentionsEnvironmentRead(argv.join(" ")) ||
|
|
562
|
+
(mentionsEnvironmentRead(argv.join(" ")) || hasCommandArgumentBraceExpansion(argv))),
|
|
443
563
|
) &&
|
|
444
564
|
pipeline.some(
|
|
445
565
|
(argv) => !isPassiveTextCommand(argv) && !isAllowedEnvironmentRead(argv, allowedNames),
|
|
446
566
|
),
|
|
447
567
|
);
|
|
568
|
+
return result;
|
|
448
569
|
}
|
|
449
570
|
|
|
450
571
|
/** A single literal writer cannot execute its body or feed another command. */
|
|
@@ -469,12 +590,164 @@ function isLiteralTextWrite(command: string): boolean {
|
|
|
469
590
|
}
|
|
470
591
|
|
|
471
592
|
// Output redirection does not execute text. Other unsupported syntax still fails parsing.
|
|
472
|
-
const parsed = parseStaticShellCommands(header.replace(
|
|
473
|
-
if (!parsed || parsed === "brace-expansion"
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
593
|
+
const parsed = parseStaticShellCommands(header.replace(/2>&1/g, " ").replace(/>>?/g, " "));
|
|
594
|
+
if (!parsed || parsed === "brace-expansion") {
|
|
595
|
+
const balancedQuotes =
|
|
596
|
+
(command.match(/'/g)?.length ?? 0) % 2 === 0 && (command.match(/"/g)?.length ?? 0) % 2 === 0;
|
|
597
|
+
return (
|
|
598
|
+
balancedQuotes &&
|
|
599
|
+
/^(?:cd\s+\S+\s+&&\s+)?(?:echo|printf)\b[\s\S]*>>?\s+\S+\s*(?:&&\s+tail\b.*)?$/i.test(command)
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
const commands = parsed.pipelines.flat().map(unwrapStaticCommand);
|
|
603
|
+
const writerIndexes = commands
|
|
604
|
+
.map((argv, index) => (writers.includes(argv[0]?.split("/").at(-1) ?? "") ? index : -1))
|
|
605
|
+
.filter((index) => index >= 0);
|
|
606
|
+
return (
|
|
607
|
+
writerIndexes.length === 1 &&
|
|
608
|
+
commands.every((argv, index) => {
|
|
609
|
+
if (index === writerIndexes[0]) return true;
|
|
610
|
+
const name = argv[0]?.split("/").at(-1) ?? "";
|
|
611
|
+
return name === "cd" || isSafeLiteralConsumer(argv);
|
|
612
|
+
})
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function hasSensitiveFileRead(command: string): boolean {
|
|
617
|
+
const sensitivePath =
|
|
618
|
+
/(?:^|[/\s'"])(?:[.]env(?\b)|\S*[.](?:pem|key)\b|\S*[.](?:ssh|aws)[/]|(?:\S*secret(?:-|[/\s.]|$))|(?:(?:secrets?|credentials?)(?:[/\s.]|$)|[A-Za-z0-9_.-]*(?:secrets?|credentials?)(?:[/\s.]|$)|[A-Za-z0-9_.-]*-(?:secrets?|credentials?)(?:[/\s.-]|$)))/i;
|
|
619
|
+
const sensitiveCamelCase =
|
|
620
|
+
/(?:^|[/\s'"])[A-Za-z0-9_.-]*(?:(?:secret|credential)[A-Z]|(?:Secret|Credential)[A-Z])[^/\s'"]*/;
|
|
621
|
+
const sensitiveName = /secret|credential/i;
|
|
622
|
+
const isSensitivePath = (text: string) =>
|
|
623
|
+
sensitivePath.test(text) || sensitiveCamelCase.test(text) || sensitiveName.test(text);
|
|
624
|
+
if (/(?:^|[\s;&|])--env-file(?:=|\s)[^;&|]*\.env\b/i.test(command)) return true;
|
|
625
|
+
if (/\bcat\b[^;&|]*<<-?\s*\S+[\s\S]*\b(?:secret|credential)\b/i.test(command)) return true;
|
|
626
|
+
const parsed = parseStaticShellCommands(command);
|
|
627
|
+
const readers = /\b(?:cat|cp|grep|rg|sed|awk|head|tail|less|more|source|ls)\b/i;
|
|
628
|
+
if (parsed && parsed !== "brace-expansion") {
|
|
629
|
+
return parsed.pipelines.flat().some((words) => {
|
|
630
|
+
const argv = unwrapStaticCommand(words);
|
|
631
|
+
const inspectedArgv =
|
|
632
|
+
argv[0]?.split("/").at(-1) === "env" ? unwrapEnvironmentCommand(argv) : argv;
|
|
633
|
+
if (inspectedArgv === null) return false;
|
|
634
|
+
const inspected = unwrapStaticCommand(inspectedArgv);
|
|
635
|
+
const name = inspected[0]?.split("/").at(-1) ?? "";
|
|
636
|
+
if (!readers.test(name)) return false;
|
|
637
|
+
if (name === "rg" || name === "grep") {
|
|
638
|
+
const args = inspected.slice(1);
|
|
639
|
+
const paths: string[] = [];
|
|
640
|
+
let hasPattern = false;
|
|
641
|
+
let options = true;
|
|
642
|
+
let filesOnly = false;
|
|
643
|
+
for (let i = 0; i < args.length; i++) {
|
|
644
|
+
const arg = args[i] ?? "";
|
|
645
|
+
if (options && arg === "--") {
|
|
646
|
+
options = false;
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (
|
|
650
|
+
options &&
|
|
651
|
+
(arg === "--pre" ||
|
|
652
|
+
arg.startsWith("--pre=") ||
|
|
653
|
+
arg === "--hostname-bin" ||
|
|
654
|
+
arg.startsWith("--hostname-bin="))
|
|
655
|
+
)
|
|
656
|
+
return true;
|
|
657
|
+
if (options && (arg.startsWith("--file=") || /^-f.+/.test(arg))) {
|
|
658
|
+
paths.push(arg.startsWith("--file=") ? arg.slice(7) : arg.slice(2));
|
|
659
|
+
hasPattern = true;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (options && (arg.startsWith("--regexp=") || /^-e.+/.test(arg))) {
|
|
663
|
+
hasPattern = true;
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (
|
|
667
|
+
options &&
|
|
668
|
+
(arg.startsWith("--glob=") ||
|
|
669
|
+
arg.startsWith("--iglob=") ||
|
|
670
|
+
arg.startsWith("--include=") ||
|
|
671
|
+
/^-g.+/.test(arg))
|
|
672
|
+
) {
|
|
673
|
+
const glob = arg.startsWith("--iglob=")
|
|
674
|
+
? arg.slice(8)
|
|
675
|
+
: arg.startsWith("--include=")
|
|
676
|
+
? arg.slice(10)
|
|
677
|
+
: arg.startsWith("--glob=")
|
|
678
|
+
? arg.slice(7)
|
|
679
|
+
: arg.slice(2);
|
|
680
|
+
if (isSensitivePath(glob)) paths.push(glob);
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (
|
|
684
|
+
options &&
|
|
685
|
+
["-e", "--regexp", "-f", "--file", "-g", "--glob", "--iglob", "--include"].includes(arg)
|
|
686
|
+
) {
|
|
687
|
+
const value = args[++i];
|
|
688
|
+
if (arg === "-f" || arg === "--file") paths.push(value ?? "");
|
|
689
|
+
if (
|
|
690
|
+
(arg === "-g" || arg === "--glob" || arg === "--iglob" || arg === "--include") &&
|
|
691
|
+
value &&
|
|
692
|
+
isSensitivePath(value)
|
|
693
|
+
)
|
|
694
|
+
paths.push(value);
|
|
695
|
+
if (arg === "-e" || arg === "--regexp" || arg === "-f" || arg === "--file")
|
|
696
|
+
hasPattern = true;
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
if (options && (arg === "--files" || (arg === "-l" && name === "grep"))) {
|
|
700
|
+
filesOnly = arg === "--files";
|
|
701
|
+
if (filesOnly) continue;
|
|
702
|
+
}
|
|
703
|
+
if (options && arg.startsWith("-")) {
|
|
704
|
+
if (/^-[A-Za-z]+[fg]$/.test(arg)) {
|
|
705
|
+
const value = args[++i];
|
|
706
|
+
if (value && isSensitivePath(value)) paths.push(value);
|
|
707
|
+
if (arg.endsWith("f")) hasPattern = true;
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
const combinedPatternFile = arg.match(/^-[A-Za-z]*f(.+)$/)?.[1];
|
|
711
|
+
if (combinedPatternFile) {
|
|
712
|
+
if (isSensitivePath(combinedPatternFile)) paths.push(combinedPatternFile);
|
|
713
|
+
hasPattern = true;
|
|
714
|
+
}
|
|
715
|
+
const optionValue = arg.includes("=") ? arg.slice(arg.indexOf("=") + 1) : null;
|
|
716
|
+
if (optionValue && isSensitivePath(optionValue)) paths.push(optionValue);
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
if (!hasPattern && !filesOnly) {
|
|
720
|
+
hasPattern = true;
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
paths.push(arg);
|
|
724
|
+
}
|
|
725
|
+
return paths.some(isSensitivePath);
|
|
726
|
+
}
|
|
727
|
+
return inspected.slice(1).some(isSensitivePath);
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
const unquoted = command.replace(/'(?:\\.|[^'])*'/g, " ").replace(/"(?:\\.|[^"$`])*"/g, " ");
|
|
731
|
+
if (/\b(?:bun|node|python3?|ruby)\b/i.test(command) && isSensitivePath(command)) {
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
return (
|
|
735
|
+
new RegExp(readers.source + "[^;&|]*" + sensitivePath.source, "i").test(unquoted) ||
|
|
736
|
+
new RegExp(readers.source + "[^;&|]*" + sensitiveCamelCase.source).test(unquoted) ||
|
|
737
|
+
new RegExp(readers.source + "[^;&|]*" + sensitiveName.source, "i").test(unquoted)
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function hasEnvironmentVariableExpansionRead(command: string): boolean {
|
|
742
|
+
if (!/\$\{?[A-Za-z_][A-Za-z0-9_]*\}?/.test(command)) return false;
|
|
743
|
+
return /\b(?:echo|printf)\b[^;&|]*\$|\b(?:sh|bash|zsh)\b[^;&|]*\s-c\b/i.test(command);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function hasEnvironmentSourceAccess(command: string): boolean {
|
|
747
|
+
return (
|
|
748
|
+
/\b(?:os\.environ|process\.env|Environment\.GetEnvironmentVariable)\b/.test(command) &&
|
|
749
|
+
/\b(?:python3?|node|bun)\b/.test(command)
|
|
750
|
+
);
|
|
478
751
|
}
|
|
479
752
|
|
|
480
753
|
/** Extract file path from hook arguments. */
|
|
@@ -527,8 +800,7 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
527
800
|
];
|
|
528
801
|
|
|
529
802
|
const dangerousBashPatterns = [
|
|
530
|
-
...
|
|
531
|
-
...(config?.additionalDangerousBashPatterns ?? []).map((p) => new RegExp(p)),
|
|
803
|
+
...(config?.additionalDangerousBashPatterns?.map((p) => new RegExp(p)) ?? []),
|
|
532
804
|
];
|
|
533
805
|
|
|
534
806
|
const blockedCliTools: BlockedCliTool[] = [
|
|
@@ -579,6 +851,9 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
579
851
|
function isDangerousBashCommand(command: string): boolean {
|
|
580
852
|
return (
|
|
581
853
|
dangerousBashPatterns.some((pattern) => pattern.test(command)) ||
|
|
854
|
+
hasSensitiveFileRead(command) ||
|
|
855
|
+
hasEnvironmentVariableExpansionRead(command) ||
|
|
856
|
+
hasEnvironmentSourceAccess(command) ||
|
|
582
857
|
hasEnvironmentRead(command, allowedEnvironmentVariables)
|
|
583
858
|
);
|
|
584
859
|
}
|
|
@@ -632,7 +907,7 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
632
907
|
|
|
633
908
|
function handleToolExecuteBefore(input: HookInput, output: HookOutput): void {
|
|
634
909
|
// Normalize tool name across platforms:
|
|
635
|
-
// -
|
|
910
|
+
// - AI coding agents pass capitalized: "Bash", "Read", "Write", "Edit"
|
|
636
911
|
// - OpenCode MCP tools pass prefixed: "mcp_bash", "mcp_read", "mcp_write", "mcp_edit"
|
|
637
912
|
const tool = input.tool.toLowerCase().replace(/^mcp_/, "");
|
|
638
913
|
const args = output.args;
|
|
@@ -645,7 +920,7 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
645
920
|
`\u{1F6AB} Access blocked: "${filePath}" is a sensitive file.\n\n` +
|
|
646
921
|
`This file may contain credentials or secrets.\n` +
|
|
647
922
|
`If you need this file's content, ask the user to provide relevant parts.\n\n` +
|
|
648
|
-
`Think this should be allowed?
|
|
923
|
+
`Think this should be allowed? Review the project repository, extend the guard, and submit a PR.`,
|
|
649
924
|
);
|
|
650
925
|
}
|
|
651
926
|
}
|
|
@@ -661,7 +936,7 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
661
936
|
`\u{1F6AB} Secret detected: Potential ${detected.name} found in content.\n\n` +
|
|
662
937
|
`Matched: ${detected.match}\n\n` +
|
|
663
938
|
`Never commit secrets to code. Use environment variables or secret managers.\n\n` +
|
|
664
|
-
`Think this is a false positive?
|
|
939
|
+
`Think this is a false positive? Review the project repository, fix the pattern, and submit a PR.`,
|
|
665
940
|
);
|
|
666
941
|
}
|
|
667
942
|
}
|
|
@@ -676,7 +951,7 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
676
951
|
`\u{1F6AB} Command blocked: This command might expose secrets.\n\n` +
|
|
677
952
|
`Command: ${command}\n\n` +
|
|
678
953
|
`If you need environment info, ask the user directly.\n\n` +
|
|
679
|
-
`Think this is wrong?
|
|
954
|
+
`Think this is wrong? Review the project repository, adjust the patterns, and submit a PR.`,
|
|
680
955
|
);
|
|
681
956
|
}
|
|
682
957
|
|
|
@@ -698,7 +973,7 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
698
973
|
`AI agents must use wrapper tools for security and audit.\n\n` +
|
|
699
974
|
`Use instead: bun ${skillName}\n\n` +
|
|
700
975
|
`Example: bun ${skillName} --help\n\n` +
|
|
701
|
-
`Think this tool should be allowed?
|
|
976
|
+
`Think this tool should be allowed? Review the project repository, extend the whitelist, and submit a PR.\n` +
|
|
702
977
|
`→ Skill "${skillName}"`,
|
|
703
978
|
);
|
|
704
979
|
}
|
package/src/gh-tool/api.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { retryTransient } from "#shared/retry-transient";
|
|
|
4
4
|
|
|
5
5
|
import { GitHubAuthError, GitHubCommandError, GitHubNotFoundError } from "./errors";
|
|
6
6
|
import type { GitHubApiError } from "./errors";
|
|
7
|
+
import { validateOutboundText } from "./text-input";
|
|
7
8
|
|
|
8
9
|
// Direct HTTP, not `gh api`: the CLI collapses every failure into a non-zero exit and
|
|
9
10
|
// loses the status code, but merge-async answers 202 (accepted), 200 (already merged or
|
|
@@ -66,9 +67,28 @@ export type GitHubApiRequest = {
|
|
|
66
67
|
|
|
67
68
|
const MAX_API_RETRIES = 2;
|
|
68
69
|
|
|
70
|
+
const outboundBodyText = (value: unknown): string => {
|
|
71
|
+
if (typeof value === "string") return value;
|
|
72
|
+
if (Array.isArray(value)) return value.map(outboundBodyText).join("\n");
|
|
73
|
+
if (value !== null && typeof value === "object")
|
|
74
|
+
return Object.values(value).map(outboundBodyText).join("\n");
|
|
75
|
+
return "";
|
|
76
|
+
};
|
|
77
|
+
|
|
69
78
|
const githubApiAttempt = Effect.fn("gh.githubApiAttempt")(function* <T>(opts: GitHubApiRequest) {
|
|
70
|
-
const token = yield* resolveGitHubToken();
|
|
71
79
|
const method = opts.method ?? "GET";
|
|
80
|
+
const bodyText = yield* Effect.try({
|
|
81
|
+
try: () => outboundBodyText(opts.body),
|
|
82
|
+
catch: () =>
|
|
83
|
+
new GitHubCommandError({
|
|
84
|
+
command: `gh-tool ${method} ${opts.path}`,
|
|
85
|
+
exitCode: 1,
|
|
86
|
+
stderr: "Refusing to publish an invalid request body",
|
|
87
|
+
message: "Refusing to publish an invalid request body",
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
yield* validateOutboundText(`${opts.path}\n${bodyText}`, `gh-tool ${method} ${opts.path}`);
|
|
91
|
+
const token = yield* resolveGitHubToken();
|
|
72
92
|
const url = `${GITHUB_API_ROOT}/${opts.path.replace(/^\//, "")}`;
|
|
73
93
|
|
|
74
94
|
const response = yield* Effect.tryPromise({
|
package/src/gh-tool/gist.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Effect, Option } from "effect";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
5
5
|
import { formatOption, logFormatted } from "#shared";
|
|
6
|
-
import { isSensitivePath, resolveOptionalTextInput } from "#gh/text-input";
|
|
6
|
+
import { isSensitivePath, resolveOptionalTextInput, validateOutboundFile } from "#gh/text-input";
|
|
7
7
|
import { GitHubCommandError } from "./errors";
|
|
8
8
|
import { GitHubService } from "./service";
|
|
9
9
|
|
|
@@ -294,6 +294,8 @@ export const createGist = Effect.fn("gist.createGist")(function* (opts: {
|
|
|
294
294
|
}) {
|
|
295
295
|
const gh = yield* GitHubService;
|
|
296
296
|
|
|
297
|
+
for (const path of opts.paths) yield* validateOutboundFile(path, "gh-tool gist create");
|
|
298
|
+
|
|
297
299
|
const args = ["gist", "create", ...opts.paths];
|
|
298
300
|
|
|
299
301
|
if (opts.description !== null) {
|
|
@@ -339,6 +341,10 @@ const editGist = Effect.fn("gist.editGist")(function* (opts: {
|
|
|
339
341
|
}) {
|
|
340
342
|
const gh = yield* GitHubService;
|
|
341
343
|
|
|
344
|
+
for (const path of [opts.sourcePath, opts.add]) {
|
|
345
|
+
if (path !== null) yield* validateOutboundFile(path, "gh-tool gist edit");
|
|
346
|
+
}
|
|
347
|
+
|
|
342
348
|
const args = ["gist", "edit", opts.id];
|
|
343
349
|
const changes: string[] = [];
|
|
344
350
|
|
package/src/gh-tool/service.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { retryTransient } from "#shared/retry-transient";
|
|
|
10
10
|
import { githubApi } from "./api";
|
|
11
11
|
import type { GitHubApiRequest, GitHubApiResponse } from "./api";
|
|
12
12
|
import { ConfigService, getGitHubConfig, resolveGitHubRepoTarget } from "#config";
|
|
13
|
+
import { validateOutboundText } from "#gh/text-input";
|
|
13
14
|
|
|
14
15
|
// Transient GitHub-side failures worth a silent retry (vs. a hard error the agent must act on).
|
|
15
16
|
const NETWORK_ERROR_RE =
|
|
@@ -267,11 +268,14 @@ export class GitHubService extends Context.Service<
|
|
|
267
268
|
// Auto-retry transient failures, but only for idempotent reads (never replay a mutation).
|
|
268
269
|
const runGh = (args: string[]): Effect.Effect<GhResult, GhError> => {
|
|
269
270
|
const canRetry = isSafeRetryRead(args);
|
|
270
|
-
return
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
271
|
+
return Effect.gen(function* () {
|
|
272
|
+
yield* validateOutboundText(args.join("\n"), "gh-tool");
|
|
273
|
+
return yield* retryTransient({
|
|
274
|
+
attempt: () => runGhAttempt(args),
|
|
275
|
+
isTransient: (err) =>
|
|
276
|
+
err instanceof GitHubCommandError && err.retryable === true && canRetry,
|
|
277
|
+
maxRetries: MAX_GH_RETRIES,
|
|
278
|
+
});
|
|
275
279
|
});
|
|
276
280
|
};
|
|
277
281
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
|
|
3
3
|
import { GitHubCommandError } from "#gh/errors";
|
|
4
|
+
import { detectSecrets } from "#guard";
|
|
4
5
|
|
|
5
6
|
const STDIN_SENTINEL = "-";
|
|
6
7
|
const SENSITIVE_PATH_PATTERNS = [
|
|
@@ -16,6 +17,62 @@ const readTextFromStdin = () => Bun.stdin.text();
|
|
|
16
17
|
export const isSensitivePath = (filePath: string) =>
|
|
17
18
|
SENSITIVE_PATH_PATTERNS.some((pattern) => pattern.test(filePath));
|
|
18
19
|
|
|
20
|
+
/** Inspect the final text, after shell expansion and after reading files or stdin. */
|
|
21
|
+
export function unsafeOutboundTextReason(
|
|
22
|
+
text: string,
|
|
23
|
+
environment: Record<string, string | undefined> = process.env,
|
|
24
|
+
): string | null {
|
|
25
|
+
if (detectSecrets(text)) return "a credential pattern";
|
|
26
|
+
|
|
27
|
+
const sensitiveName =
|
|
28
|
+
/(?:KEY|TOKEN|SECRET|PASS(?:WORD)?|PWD|CREDENTIAL|AUTH|COOKIE|SESSION|PSK)/i;
|
|
29
|
+
const isSensitiveName = (name: string) =>
|
|
30
|
+
name !== "PWD" && name !== "OLDPWD" && sensitiveName.test(name);
|
|
31
|
+
for (const [name, value] of Object.entries(environment)) {
|
|
32
|
+
if (isSensitiveName(name) && value && value.length >= 8 && text.includes(value)) {
|
|
33
|
+
return "a credential from the process environment";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const assignments = text.match(/^[A-Za-z_][A-Za-z0-9_]*=.*$/gm) ?? [];
|
|
38
|
+
if (
|
|
39
|
+
assignments.length >= 5 &&
|
|
40
|
+
assignments.some((line) => isSensitiveName(line.split("=", 1)[0] ?? ""))
|
|
41
|
+
) {
|
|
42
|
+
return "an environment dump";
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const validateOutboundText = (text: string, command: string) => {
|
|
48
|
+
const reason = unsafeOutboundTextReason(text);
|
|
49
|
+
return reason === null
|
|
50
|
+
? Effect.succeed(text)
|
|
51
|
+
: Effect.fail(
|
|
52
|
+
new GitHubCommandError({
|
|
53
|
+
command,
|
|
54
|
+
exitCode: 1,
|
|
55
|
+
stderr: `Refusing to publish text containing ${reason}`,
|
|
56
|
+
message: `Refusing to publish text containing ${reason}`,
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const validateOutboundFile = (filePath: string, command: string) =>
|
|
62
|
+
Effect.tryPromise({
|
|
63
|
+
try: () => readTextFile(filePath),
|
|
64
|
+
catch: () =>
|
|
65
|
+
new GitHubCommandError({
|
|
66
|
+
command,
|
|
67
|
+
exitCode: 1,
|
|
68
|
+
stderr: `Refusing to publish unreadable or sensitive file: ${filePath}`,
|
|
69
|
+
message: `Refusing to publish unreadable or sensitive file: ${filePath}`,
|
|
70
|
+
}),
|
|
71
|
+
}).pipe(
|
|
72
|
+
Effect.flatMap((text) => validateOutboundText(text, command)),
|
|
73
|
+
Effect.asVoid,
|
|
74
|
+
);
|
|
75
|
+
|
|
19
76
|
const readTextFile = (filePath: string) => {
|
|
20
77
|
if (isSensitivePath(filePath)) {
|
|
21
78
|
return Promise.reject(new Error(`Refusing to read sensitive file: ${filePath}`));
|
|
@@ -80,13 +137,13 @@ const resolveTextInputInternal = Effect.fn("gh.resolveTextInputInternal")(functi
|
|
|
80
137
|
}
|
|
81
138
|
|
|
82
139
|
if (value !== null) {
|
|
83
|
-
return value;
|
|
140
|
+
return yield* validateOutboundText(value, command);
|
|
84
141
|
}
|
|
85
142
|
|
|
86
143
|
if (fileValue !== null) {
|
|
87
144
|
const source = fileValue === STDIN_SENTINEL ? "stdin" : fileValue;
|
|
88
145
|
|
|
89
|
-
|
|
146
|
+
const text = yield* Effect.tryPromise({
|
|
90
147
|
try: () => (fileValue === STDIN_SENTINEL ? readTextFromStdin() : readTextFile(fileValue)),
|
|
91
148
|
catch: (error) =>
|
|
92
149
|
new GitHubCommandError({
|
|
@@ -96,10 +153,11 @@ const resolveTextInputInternal = Effect.fn("gh.resolveTextInputInternal")(functi
|
|
|
96
153
|
message: `Failed to read ${label} from ${source}: ${error instanceof Error ? error.message : String(error)}`,
|
|
97
154
|
}),
|
|
98
155
|
});
|
|
156
|
+
return yield* validateOutboundText(text, command);
|
|
99
157
|
}
|
|
100
158
|
|
|
101
159
|
if (stdin) {
|
|
102
|
-
|
|
160
|
+
const text = yield* Effect.tryPromise({
|
|
103
161
|
try: () => readTextFromStdin(),
|
|
104
162
|
catch: (error) =>
|
|
105
163
|
new GitHubCommandError({
|
|
@@ -109,6 +167,7 @@ const resolveTextInputInternal = Effect.fn("gh.resolveTextInputInternal")(functi
|
|
|
109
167
|
message: `Failed to read ${label} from stdin: ${error instanceof Error ? error.message : String(error)}`,
|
|
110
168
|
}),
|
|
111
169
|
});
|
|
170
|
+
return yield* validateOutboundText(text, command);
|
|
112
171
|
}
|
|
113
172
|
|
|
114
173
|
if (missingMode === "null") {
|
|
@@ -116,7 +175,7 @@ const resolveTextInputInternal = Effect.fn("gh.resolveTextInputInternal")(functi
|
|
|
116
175
|
}
|
|
117
176
|
|
|
118
177
|
if (missingMode === "default") {
|
|
119
|
-
return missingValue ?? "";
|
|
178
|
+
return yield* validateOutboundText(missingValue ?? "", command);
|
|
120
179
|
}
|
|
121
180
|
|
|
122
181
|
return yield* Effect.fail(
|