@golproductions/check 3.2.11 → 3.2.12
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/package.json +1 -1
- package/src/index.js +38 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@golproductions/check",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.12",
|
|
4
4
|
"description": "Less hallucinations. A command firewall for AI agents: validates every shell command before execution, so fabricated commands are denied before they run.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"check": "src/index.js",
|
package/src/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { homedir, platform, arch } from "node:os";
|
|
|
6
6
|
import { pipeline } from "node:stream/promises";
|
|
7
7
|
import { Readable } from "node:stream";
|
|
8
8
|
|
|
9
|
-
const VERSION = "3.2.
|
|
9
|
+
const VERSION = "3.2.12";
|
|
10
10
|
const BINARY_VERSION = "3.0.0";
|
|
11
11
|
const API = "https://triage.golproductions.com/preflight";
|
|
12
12
|
const CDN = "https://pub-e55366a7f5994be9be04f0e205179f4a.r2.dev/releases";
|
|
@@ -802,19 +802,34 @@ function bashPath() {
|
|
|
802
802
|
return null;
|
|
803
803
|
}
|
|
804
804
|
|
|
805
|
+
// When the shell refuses a name, keep its own words. The reason shown on a
|
|
806
|
+
// deny must come from the interpreter that would have run the command, not
|
|
807
|
+
// from us: deterministic (same machine, same words) and never argued with.
|
|
808
|
+
// Normalized as "<name>: <the shell's phrase>", pure string ops.
|
|
809
|
+
function shellWhy(raw, name) {
|
|
810
|
+
if (!raw) return null;
|
|
811
|
+
const line = raw.split(/\r?\n/).find(l => l.includes(name));
|
|
812
|
+
if (!line) return null;
|
|
813
|
+
let tail = line.slice(line.indexOf(name) + name.length);
|
|
814
|
+
tail = tail.replace(/^['"\s:]+/, "").replace(/\s+$/, "");
|
|
815
|
+
return tail ? name + ": " + tail : null;
|
|
816
|
+
}
|
|
817
|
+
|
|
805
818
|
function shellKnows(name, cwd, shell) {
|
|
806
819
|
try {
|
|
807
820
|
if (shell === "powershell") {
|
|
808
821
|
const r = spawnSync("powershell", ["-NoProfile", "-NonInteractive", "-Command",
|
|
809
|
-
"
|
|
810
|
-
{ cwd, timeout: 4000,
|
|
811
|
-
|
|
822
|
+
"try { $null = Get-Command $env:CHECK_PROBE -ErrorAction Stop; exit 0 } catch { [Console]::Error.WriteLine($_.Exception.Message); exit 1 }"],
|
|
823
|
+
{ cwd, timeout: 4000, encoding: "utf8", env: { ...process.env, CHECK_PROBE: name } });
|
|
824
|
+
if (r.error) return null;
|
|
825
|
+
return { known: r.status === 0, why: r.status === 0 ? null : shellWhy(r.stderr, name) };
|
|
812
826
|
}
|
|
813
827
|
const bash = bashPath();
|
|
814
828
|
if (!bash) return null;
|
|
815
|
-
const r = spawnSync(bash, ["-c", 'type -- "$1"
|
|
816
|
-
{ cwd, timeout: 4000,
|
|
817
|
-
|
|
829
|
+
const r = spawnSync(bash, ["-c", 'type -- "$1"', "check", name],
|
|
830
|
+
{ cwd, timeout: 4000, encoding: "utf8" });
|
|
831
|
+
if (r.error) return null;
|
|
832
|
+
return { known: r.status === 0, why: r.status === 0 ? null : shellWhy(r.stderr, name) };
|
|
818
833
|
} catch { return null; }
|
|
819
834
|
}
|
|
820
835
|
|
|
@@ -822,7 +837,7 @@ function shellKnows(name, cwd, shell) {
|
|
|
822
837
|
// word proven resolvable, false = at least one proven unresolvable, null =
|
|
823
838
|
// cannot be determined without executing (subshells, expansions) — abstain,
|
|
824
839
|
// never guess.
|
|
825
|
-
function allBinariesExist(command, cwd, shell) {
|
|
840
|
+
function allBinariesExist(command, cwd, shell, misses) {
|
|
826
841
|
try {
|
|
827
842
|
let bases = segmentBases(command);
|
|
828
843
|
if (shell !== "powershell") bases = bases.filter(b => !SHELL_BUILTINS.has(b));
|
|
@@ -830,9 +845,12 @@ function allBinariesExist(command, cwd, shell) {
|
|
|
830
845
|
if (bases.some(b => /[(){}$`]/.test(b))) return null;
|
|
831
846
|
let verdict = true;
|
|
832
847
|
for (const b of bases) {
|
|
833
|
-
const
|
|
834
|
-
if (
|
|
835
|
-
if (known === false)
|
|
848
|
+
const probe = shellKnows(b, cwd, shell);
|
|
849
|
+
if (probe === null) return null;
|
|
850
|
+
if (probe.known === false) {
|
|
851
|
+
verdict = false;
|
|
852
|
+
if (misses) misses.push(probe.why || b + ": not found");
|
|
853
|
+
}
|
|
836
854
|
}
|
|
837
855
|
return verdict;
|
|
838
856
|
} catch { return null; }
|
|
@@ -868,7 +886,8 @@ async function main() {
|
|
|
868
886
|
const controller = new AbortController();
|
|
869
887
|
const timer = setTimeout(() => controller.abort(), 5000);
|
|
870
888
|
// Ask the same interpreter that will run the command.
|
|
871
|
-
const
|
|
889
|
+
const misses = [];
|
|
890
|
+
const exists = allBinariesExist(c, p.cwd, p.tool_name === "PowerShell" ? "powershell" : "bash", misses);
|
|
872
891
|
// Send only what the verdict needs. No transcript paths, no usernames
|
|
873
892
|
// beyond what cwd itself carries — the check is about the command.
|
|
874
893
|
const body = { command: c, cwd: p.cwd || process.cwd(), platform: f, channel: "npm", tool_name: p.tool_name, v: VERSION };
|
|
@@ -914,7 +933,13 @@ async function main() {
|
|
|
914
933
|
}
|
|
915
934
|
|
|
916
935
|
if (d.verdict === "runnable") { out(f, true); }
|
|
917
|
-
else {
|
|
936
|
+
else {
|
|
937
|
+
// Prefer the shell's own words: when the interpreter that would run
|
|
938
|
+
// this command already said why it can't, that sentence is the reason.
|
|
939
|
+
// Server reason covers everything the shell can't see (URLs, syntax).
|
|
940
|
+
const local = misses.length ? "denied by the shell that would run it. " + misses.join("; ") : null;
|
|
941
|
+
out(f, false, local || d.reason || "denied. Address the issue before continuing.");
|
|
942
|
+
}
|
|
918
943
|
} catch {
|
|
919
944
|
// Network failure or timeout: fail OPEN. An unreachable validation
|
|
920
945
|
// service must never block the user's work (Terms, section 5).
|