@blogic-cz/agent-tools 1.5.9 → 1.5.10
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 +6 -0
- package/dist/credential-guard/index.d.ts.map +1 -1
- package/dist/gh-tool/api.d.ts.map +1 -1
- package/dist/gh-tool/environment.d.ts +2 -0
- package/dist/gh-tool/environment.d.ts.map +1 -0
- package/dist/gh-tool/service.d.ts.map +1 -1
- package/dist/gh-tool/text-input.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/credential-guard/index.ts +9 -22
- package/src/gh-tool/api.ts +50 -24
- package/src/gh-tool/environment.ts +50 -0
- package/src/gh-tool/service.ts +3 -1
- package/src/gh-tool/text-input.ts +7 -1
package/README.md
CHANGED
|
@@ -612,6 +612,10 @@ The guard blocks agents from accessing sensitive files, leaking secrets, and run
|
|
|
612
612
|
|
|
613
613
|
GitHub text and file publication, database queries, Azure and Kubernetes command arguments, and observability requests are checked before execution. GitHub file uploads use snapshots of the inspected bytes. Generated release notes and existing pending reviews or draft releases are inspected before publication. Authentication headers and connection credentials remain inside the tools.
|
|
614
614
|
|
|
615
|
+
GitHub API requests validate the serialized body that will be sent, including JSON keys and decoded string values. Serialization happens once, before authentication lookup or network access. File publication checks both the supplied path and its resolved target against known credential locations before reading content. This includes SSH, AWS, Kubernetes, Azure, and common CLI credential stores. An unknown filename can still contain credentials, so content checks also apply.
|
|
616
|
+
|
|
617
|
+
The two direct GitHub CLI launch paths receive an explicit environment containing supported GitHub authentication, host/configuration, proxy/CA, and platform variables. Unrelated parent variables and runtime loader settings are not inherited. Other tools retain their own environment contracts; this does not isolate the parent agent or prevent GitHub CLI access to its configured credential files.
|
|
618
|
+
|
|
615
619
|
Results retain their JSON or TOON structure, with detected credentials replaced by `[REDACTED]`. Audit records are sanitized before storage; reading older records also sanitizes the returned content but does not rewrite the database.
|
|
616
620
|
|
|
617
621
|
These checks apply to these wrappers. They do not intercept arbitrary shell programs or other agent tools. GitHub does not offer an atomic content-version condition for submitting pending reviews or publishing existing drafts, so a concurrent remote edit between inspection and publication remains possible.
|
|
@@ -654,6 +658,8 @@ Paths are checked against sensitive filename patterns and configured path rules.
|
|
|
654
658
|
|
|
655
659
|
Single literal `echo`/`printf` writes with output redirection and single `cat`/`tee` heredoc writes are allowed. Heredoc braces are data. Use a quoted delimiter for literal code; unquoted heredocs containing `$`, backticks, or backslashes do not receive the literal-write exception. The exception permits only supported combinations with navigation or selected inspections; unsupported following commands remain outside it. Supported complete Python stdin heredocs are checked as inline programs, with the normal runtime-argument, path, execution, and environment checks.
|
|
656
660
|
|
|
661
|
+
Python JSON parsing, dictionary iteration, and printing are not themselves evidence of credential access. They follow the same access and execution rules as other supported inline programs. This intentionally permits ordinary JSON output that older versions refused. Unknown secrets in ordinary files can therefore reach arbitrary shell output; wrapper content checks do not protect that output. A refusal caused by unsupported syntax means the guard could not establish its policy conditions, not that it observed a secret being read.
|
|
662
|
+
|
|
657
663
|
A separate bounded Node proof supports literal `package.json` inventory loops that print `bin` metadata or use the supported scripts filter. The complete invocation must preserve loader identity through known passive commands, closed literal writers, or proved inventories. Startup/environment mutations, unknown commands, loader changes, and other dynamic loading remain unproved and are refused.
|
|
658
664
|
|
|
659
665
|
### Setup for OpenCode
|
|
@@ -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;AAI5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAM/D,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;AAI5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAM/D,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;AA66DF,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,CAiTrF;AAQD,kEAAkE;AAClE,eAAO,MAAM,uBAAuB,UAxwED,SAAS,UAAU,UAAU,KAAK,IAwwEM,CAAC;AAE5E,oDAAoD;AACpD,eAAO,MAAM,aAAa,YA1wEC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IA0wEjB,CAAC;AAExD,yEAAyE;AACzE,eAAO,MAAM,aAAa,aA5wEE,MAAM,KAAK,OA4wEgB,CAAC;AAExD,yDAAyD;AACzD,eAAO,MAAM,aAAa,aA9wEE,MAAM,KAAK,OA8wEgB,CAAC;AAExD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,YAhxEC,MAAM,KAAK,OAgxE0B,CAAC;AAE1E,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,YAlxEC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAkxEf,CAAC;AAEhE,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,YApxEC,MAAM,KAAK,OAoxEsB,CAAC;AAElE,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,YAtxEC,MAAM,KAAK,MAAM,GAAG,IAsxEa,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;AAW/C,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAYF,eAAO,MAAM,kBAAkB,qDA6B7B,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;AAuFF,eAAO,MAAM,SAAS,GAAI,CAAC,QACnB,gBAAgB,KACrB,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,cAAc,CAqCjD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/gh-tool/environment.ts"],"names":[],"mappings":"AAyCA,eAAO,MAAM,aAAa,iBACX,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,KAC9C,MAAM,CAAC,MAAM,EAAE,MAAM,CAMrB,CAAC"}
|
|
@@ -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;
|
|
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;AAIxC,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,6FA0RnB;CACH"}
|
|
@@ -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;AAGxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGhD,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,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;AAGxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGhD,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAepE,QAAA,MAAM,WAAW,wDAAgD,CAAC;AAIlE,eAAO,MAAM,eAAe,aAAc,MAAM,YACyC,CAAC;AAE1F,eAAO,MAAM,oBAAoB,SAAU,MAAM,WAAW,MAAM,0FAYjE,CAAC;AAEF,eAAO,MAAM,yBAAyB,aAAc,MAAM,WAAW,MAAM;;;6BAUH,CAAC;AAEzE,eAAO,MAAM,oBAAoB,aAAc,MAAM,WAAW,MAAM,mDACJ,CAAC;AAuBnE,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
|
@@ -997,18 +997,6 @@ function inlinePackageCommands(command: string, allowInventory: boolean): string
|
|
|
997
997
|
});
|
|
998
998
|
}
|
|
999
999
|
|
|
1000
|
-
/** JSON values selected by arbitrary keys or printed wholesale have no metadata proof. */
|
|
1001
|
-
function hasUnverifiedJsonValueOutput(code: string): boolean {
|
|
1002
|
-
if (!/\bjson\.loads?\s*\(/.test(code) || !/\bprint\s*\(/.test(code)) return false;
|
|
1003
|
-
if (/\.items\s*\(/.test(code) || /\bprint\s*\(\s*json\.loads?\s*\(/.test(code)) return true;
|
|
1004
|
-
const names = [...code.matchAll(/\b([A-Za-z_]\w*)\s*=\s*json\.loads?\s*\(/g)].map(
|
|
1005
|
-
(match) => match[1] ?? "",
|
|
1006
|
-
);
|
|
1007
|
-
return names.some((name) =>
|
|
1008
|
-
new RegExp(`\\bprint\\s*\\(\\s*(?:json\\.dumps\\s*\\(\\s*)?${name}\\s*[,)]`).test(code),
|
|
1009
|
-
);
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
1000
|
function hasInlineProgramExecution(
|
|
1013
1001
|
program: string,
|
|
1014
1002
|
python: boolean,
|
|
@@ -1016,7 +1004,6 @@ function hasInlineProgramExecution(
|
|
|
1016
1004
|
): boolean {
|
|
1017
1005
|
const parts = programParts(program, python);
|
|
1018
1006
|
if (!parts) return true;
|
|
1019
|
-
if (python && hasUnverifiedJsonValueOutput(parts.code)) return true;
|
|
1020
1007
|
if (parts.loadedModules.some((name) => /^(?:node:)?vm$/.test(name))) return true;
|
|
1021
1008
|
// Dynamic loading and reflective access cannot establish a non-executing code operand.
|
|
1022
1009
|
if (
|
|
@@ -2137,7 +2124,7 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
2137
2124
|
const rawEnvironmentSource =
|
|
2138
2125
|
mentionsEnvironmentSource(command) && mentionsInlineRuntime(command);
|
|
2139
2126
|
if (rawEnvironmentSource && hasEnvironmentMutationPrefix(originalCommand))
|
|
2140
|
-
return "
|
|
2127
|
+
return "blocked by the environment/execution policy after an unproved environment mutation";
|
|
2141
2128
|
command = normalizeProgramHeredocs(command);
|
|
2142
2129
|
const proof = materializeLiteralShell(command);
|
|
2143
2130
|
if (!proof && /^\s*for\b/.test(command)) return "cannot prove a bounded literal shell loop";
|
|
@@ -2158,19 +2145,19 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
2158
2145
|
if (
|
|
2159
2146
|
hasSensitiveFileRead(command, isPathBlocked) ||
|
|
2160
2147
|
hasSensitivePathRedirect(command, isPathBlocked)
|
|
2161
|
-
)
|
|
2162
|
-
return "
|
|
2163
|
-
}
|
|
2148
|
+
)
|
|
2149
|
+
return "blocked by the file-access policy; a sensitive path matched or this command form could not be verified";
|
|
2164
2150
|
if (
|
|
2165
2151
|
!isLiteralTextWrite(command) &&
|
|
2166
2152
|
hasEnvironmentVariableExpansionRead(command, allowedEnvironmentVariables, isPathBlocked)
|
|
2167
2153
|
) {
|
|
2168
2154
|
return "expands an unapproved or executable environment variable value";
|
|
2169
2155
|
}
|
|
2170
|
-
if (
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2156
|
+
if (
|
|
2157
|
+
hasEnvironmentSourceAccess(command, rawEnvironmentSource) ||
|
|
2158
|
+
hasEnvironmentRead(command, allowedEnvironmentVariables, allowPackageInventory)
|
|
2159
|
+
) {
|
|
2160
|
+
return "blocked by the environment/execution policy; permitted access or non-execution could not be established for this command form";
|
|
2174
2161
|
}
|
|
2175
2162
|
if (
|
|
2176
2163
|
dangerousBashPatterns.some(
|
|
@@ -2319,7 +2306,7 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
2319
2306
|
throw new Error(
|
|
2320
2307
|
`\u{1F6AB} Command blocked: ${dangerousReason}; this command might expose secrets.\n\n` +
|
|
2321
2308
|
`Command: ${command}\n\n` +
|
|
2322
|
-
`
|
|
2309
|
+
`Use a supported command form or the appropriate wrapper tool.\n\n` +
|
|
2323
2310
|
`Think this is wrong? Review the project repository, adjust the patterns, and submit a PR.`,
|
|
2324
2311
|
);
|
|
2325
2312
|
}
|
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 { ghEnvironment } from "./environment";
|
|
7
8
|
import { validateOutboundText } from "./text-input";
|
|
8
9
|
|
|
9
10
|
// Direct HTTP, not `gh api`: the CLI collapses every failure into a non-zero exit and
|
|
@@ -44,6 +45,7 @@ export const resolveGitHubToken = Effect.fn("gh.resolveGitHubToken")(function* (
|
|
|
44
45
|
const proc = Bun.spawn(["gh", "auth", "token", "--hostname", "github.com"], {
|
|
45
46
|
stdout: "pipe",
|
|
46
47
|
stderr: "ignore",
|
|
48
|
+
env: ghEnvironment(),
|
|
47
49
|
});
|
|
48
50
|
const stdout = await new Response(proc.stdout).text();
|
|
49
51
|
const exitCode = await proc.exited;
|
|
@@ -67,27 +69,22 @@ export type GitHubApiRequest = {
|
|
|
67
69
|
|
|
68
70
|
const MAX_API_RETRIES = 2;
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
// Traverse only parsed JSON, never the caller's getters or toJSON hooks.
|
|
73
|
+
const decodedBodyText = (value: unknown): string => {
|
|
71
74
|
if (typeof value === "string") return value;
|
|
72
|
-
if (Array.isArray(value)) return value.map(
|
|
75
|
+
if (Array.isArray(value)) return value.map(decodedBodyText).join("\n");
|
|
73
76
|
if (value !== null && typeof value === "object")
|
|
74
|
-
return Object.
|
|
77
|
+
return Object.entries(value)
|
|
78
|
+
.map(([key, item]) => `${key}\n${decodedBodyText(item)}`)
|
|
79
|
+
.join("\n");
|
|
75
80
|
return "";
|
|
76
81
|
};
|
|
77
82
|
|
|
78
|
-
const githubApiAttempt = Effect.fn("gh.githubApiAttempt")(function* <T>(
|
|
83
|
+
const githubApiAttempt = Effect.fn("gh.githubApiAttempt")(function* <T>(
|
|
84
|
+
opts: GitHubApiRequest,
|
|
85
|
+
serializedBody: string | undefined,
|
|
86
|
+
) {
|
|
79
87
|
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
88
|
const token = yield* resolveGitHubToken();
|
|
92
89
|
const url = `${GITHUB_API_ROOT}/${opts.path.replace(/^\//, "")}`;
|
|
93
90
|
|
|
@@ -99,9 +96,9 @@ const githubApiAttempt = Effect.fn("gh.githubApiAttempt")(function* <T>(opts: Gi
|
|
|
99
96
|
Accept: GITHUB_ACCEPT,
|
|
100
97
|
Authorization: `Bearer ${token}`,
|
|
101
98
|
"X-GitHub-Api-Version": GITHUB_API_VERSION,
|
|
102
|
-
...(
|
|
99
|
+
...(serializedBody === undefined ? {} : { "Content-Type": "application/json" }),
|
|
103
100
|
},
|
|
104
|
-
...(
|
|
101
|
+
...(serializedBody === undefined ? {} : { body: serializedBody }),
|
|
105
102
|
}),
|
|
106
103
|
catch: (cause) =>
|
|
107
104
|
new GitHubCommandError({
|
|
@@ -158,13 +155,42 @@ const githubApiAttempt = Effect.fn("gh.githubApiAttempt")(function* <T>(opts: Gi
|
|
|
158
155
|
export const githubApi = <T>(
|
|
159
156
|
opts: GitHubApiRequest,
|
|
160
157
|
): Effect.Effect<GitHubApiResponse<T>, GitHubApiError> =>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
158
|
+
Effect.gen(function* () {
|
|
159
|
+
const snapshot = yield* Effect.try({
|
|
160
|
+
try: () => {
|
|
161
|
+
const path = opts.path;
|
|
162
|
+
const method = opts.method ?? "GET";
|
|
163
|
+
const alsoAcceptStatus = opts.alsoAcceptStatus;
|
|
164
|
+
const request = {
|
|
165
|
+
path,
|
|
166
|
+
method,
|
|
167
|
+
...(alsoAcceptStatus === undefined ? {} : { alsoAcceptStatus: [...alsoAcceptStatus] }),
|
|
168
|
+
};
|
|
169
|
+
const body = opts.body;
|
|
170
|
+
if (body === undefined) return { request, serialized: undefined, decoded: "" };
|
|
171
|
+
const serialized = JSON.stringify(body);
|
|
172
|
+
if (serialized === undefined) throw new Error("Body is not JSON serializable");
|
|
173
|
+
return { request, serialized, decoded: decodedBodyText(JSON.parse(serialized)) };
|
|
174
|
+
},
|
|
175
|
+
catch: () =>
|
|
176
|
+
new GitHubCommandError({
|
|
177
|
+
command: "gh-tool API",
|
|
178
|
+
exitCode: 1,
|
|
179
|
+
stderr: "Refusing to publish an invalid request body",
|
|
180
|
+
message: "Refusing to publish an invalid request body",
|
|
181
|
+
}),
|
|
182
|
+
});
|
|
183
|
+
const { request } = snapshot;
|
|
184
|
+
yield* validateOutboundText(
|
|
185
|
+
`${request.path}\n${snapshot.serialized ?? ""}\n${snapshot.decoded}`,
|
|
186
|
+
`gh-tool ${request.method} ${request.path}`,
|
|
187
|
+
);
|
|
188
|
+
return yield* retryTransient({
|
|
189
|
+
attempt: () => githubApiAttempt<T>(request, snapshot.serialized),
|
|
190
|
+
isTransient: (error) =>
|
|
191
|
+
error._tag === "GitHubCommandError" && error.retryable === true && request.method === "GET",
|
|
192
|
+
maxRetries: MAX_API_RETRIES,
|
|
193
|
+
});
|
|
168
194
|
});
|
|
169
195
|
|
|
170
196
|
const safeJsonParse = (text: string): unknown => {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Exact names only: GH children need their own auth/config, not unrelated credentials
|
|
2
|
+
// or executable-loader, editor, and pager settings from the parent process.
|
|
3
|
+
const GH_ENVIRONMENT_NAMES = [
|
|
4
|
+
"PATH",
|
|
5
|
+
"HOME",
|
|
6
|
+
"USERPROFILE",
|
|
7
|
+
"APPDATA",
|
|
8
|
+
"AppData",
|
|
9
|
+
"HOMEDRIVE",
|
|
10
|
+
"HOMEPATH",
|
|
11
|
+
"SYSTEMROOT",
|
|
12
|
+
"SystemRoot",
|
|
13
|
+
"WINDIR",
|
|
14
|
+
"TMPDIR",
|
|
15
|
+
"TMP",
|
|
16
|
+
"TEMP",
|
|
17
|
+
"LANG",
|
|
18
|
+
"LC_ALL",
|
|
19
|
+
"LC_CTYPE",
|
|
20
|
+
"TERM",
|
|
21
|
+
"XDG_CONFIG_HOME",
|
|
22
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
23
|
+
"GH_CONFIG_DIR",
|
|
24
|
+
"GH_HOST",
|
|
25
|
+
"GH_REPO",
|
|
26
|
+
"GH_TOKEN",
|
|
27
|
+
"GITHUB_TOKEN",
|
|
28
|
+
"GH_ENTERPRISE_TOKEN",
|
|
29
|
+
"GITHUB_ENTERPRISE_TOKEN",
|
|
30
|
+
"HTTP_PROXY",
|
|
31
|
+
"http_proxy",
|
|
32
|
+
"HTTPS_PROXY",
|
|
33
|
+
"https_proxy",
|
|
34
|
+
"ALL_PROXY",
|
|
35
|
+
"all_proxy",
|
|
36
|
+
"NO_PROXY",
|
|
37
|
+
"no_proxy",
|
|
38
|
+
"SSL_CERT_FILE",
|
|
39
|
+
"SSL_CERT_DIR",
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
export const ghEnvironment = (
|
|
43
|
+
environment: Record<string, string | undefined> = process.env,
|
|
44
|
+
): Record<string, string> =>
|
|
45
|
+
Object.fromEntries(
|
|
46
|
+
GH_ENVIRONMENT_NAMES.flatMap((name) => {
|
|
47
|
+
const value = environment[name];
|
|
48
|
+
return value === undefined ? [] : [[name, value]];
|
|
49
|
+
}),
|
|
50
|
+
);
|
package/src/gh-tool/service.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { GitHubRepoConfig } from "#config";
|
|
|
5
5
|
import type { RepoInfo } from "./types";
|
|
6
6
|
|
|
7
7
|
import { GH_BINARY } from "./config";
|
|
8
|
+
import { ghEnvironment } from "./environment";
|
|
8
9
|
import { GitHubAuthError, GitHubCommandError, GitHubNotFoundError } from "./errors";
|
|
9
10
|
import { retryTransient } from "#shared/retry-transient";
|
|
10
11
|
import { githubApi } from "./api";
|
|
@@ -165,7 +166,8 @@ export class GitHubService extends Context.Service<
|
|
|
165
166
|
const command = ChildProcess.make(GH_BINARY, args, {
|
|
166
167
|
stdout: "pipe",
|
|
167
168
|
stderr: "pipe",
|
|
168
|
-
...(ghRepo ? {
|
|
169
|
+
env: { ...ghEnvironment(), ...(ghRepo ? { GH_REPO: ghRepo } : {}) },
|
|
170
|
+
extendEnv: false,
|
|
169
171
|
});
|
|
170
172
|
|
|
171
173
|
const proc = yield* executor.spawn(command);
|
|
@@ -12,13 +12,19 @@ const SENSITIVE_PATH_PATTERNS = [
|
|
|
12
12
|
/\.envrc$/,
|
|
13
13
|
/\.(pem|key|p12|pfx|cer|crt)$/i,
|
|
14
14
|
/(?:^|[\\/])(credentials?|passwd|shadow)$/i,
|
|
15
|
+
/(?:^|\/)(?:\.ssh|\.aws|\.kube|\.azure|secrets?|credentials?)(?:\/|$)/i,
|
|
16
|
+
/(?:^|\/)(?:\.npmrc|\.netrc|\.git-credentials|\.sentryclirc|\.pypirc|\.pgpass)$/i,
|
|
17
|
+
/(?:^|\/)\.docker\/config\.json$/i,
|
|
18
|
+
/(?:^|\/)\.config\/gh\/hosts\.yml$/i,
|
|
19
|
+
/(?:^|\/)GitHub CLI\/hosts\.yml$/i,
|
|
20
|
+
/(?:^|\/)(?:kubeconfig(?:[.-].+)?|[^/]+\.kubeconfig)$/i,
|
|
15
21
|
];
|
|
16
22
|
const MissingMode = Schema.Literals(["error", "null", "default"]);
|
|
17
23
|
|
|
18
24
|
const readTextFromStdin = () => Bun.stdin.text();
|
|
19
25
|
|
|
20
26
|
export const isSensitivePath = (filePath: string) =>
|
|
21
|
-
SENSITIVE_PATH_PATTERNS.some((pattern) => pattern.test(filePath));
|
|
27
|
+
SENSITIVE_PATH_PATTERNS.some((pattern) => pattern.test(filePath.replaceAll("\\", "/")));
|
|
22
28
|
|
|
23
29
|
export const validateOutboundText = (text: string, command: string) => {
|
|
24
30
|
const reason = unsafeOutboundTextReason(text);
|