@themoltnet/pi-runtime 0.2.0 → 0.3.0
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/index.d.ts +32 -0
- package/dist/index.js +137 -31
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,9 @@ export declare interface AllowedToolsClient {
|
|
|
43
43
|
}) => Promise<{
|
|
44
44
|
enforcement: ToolEnforcement;
|
|
45
45
|
allowedTools: string[];
|
|
46
|
+
allowedShellCommands: Array<{
|
|
47
|
+
argvPrefix: string[];
|
|
48
|
+
}>;
|
|
46
49
|
}>;
|
|
47
50
|
};
|
|
48
51
|
}
|
|
@@ -591,12 +594,16 @@ export declare function findMainWorktree(startPath?: string): string;
|
|
|
591
594
|
*/
|
|
592
595
|
export declare type GateDecision = {
|
|
593
596
|
allow: true;
|
|
597
|
+
matchedShellCommands?: MatchedShellCommand[];
|
|
594
598
|
} | {
|
|
595
599
|
allow: false;
|
|
596
600
|
reason: string;
|
|
601
|
+
missing?: string[];
|
|
602
|
+
missingShellCommands?: MissingShellCommand[];
|
|
597
603
|
} | {
|
|
598
604
|
audit: string;
|
|
599
605
|
missing?: string[];
|
|
606
|
+
missingShellCommands?: MissingShellCommand[];
|
|
600
607
|
};
|
|
601
608
|
|
|
602
609
|
export declare interface GateInput {
|
|
@@ -607,6 +614,8 @@ export declare interface GateInput {
|
|
|
607
614
|
enforcement: ToolEnforcement;
|
|
608
615
|
/** Names the policy allows (structured tool names + shell executable names). */
|
|
609
616
|
allowedTools: ReadonlySet<string>;
|
|
617
|
+
/** Shell argv rules. Each rule matches the first N statically known tokens. */
|
|
618
|
+
allowedShellCommands: readonly ShellCommandRule[];
|
|
610
619
|
/**
|
|
611
620
|
* Synchronous shell analyzer (`ShellCommandAnalyzer.analyze`). Injected so the
|
|
612
621
|
* decision stays pure and testable; the analyzer's async WASM init happens
|
|
@@ -694,6 +703,12 @@ export declare interface ManagedVm {
|
|
|
694
703
|
agentDir: string;
|
|
695
704
|
}
|
|
696
705
|
|
|
706
|
+
declare interface MatchedShellCommand {
|
|
707
|
+
executable: string;
|
|
708
|
+
argvPrefixFingerprint: string;
|
|
709
|
+
argvPrefixLength: number;
|
|
710
|
+
}
|
|
711
|
+
|
|
697
712
|
export declare function materializePiExtensions(input: {
|
|
698
713
|
runtime: PiRuntimeDefinition;
|
|
699
714
|
context: PiToolContext;
|
|
@@ -714,6 +729,18 @@ export declare function materializePiTools(input: {
|
|
|
714
729
|
};
|
|
715
730
|
}): Promise<ToolDefinition[]>;
|
|
716
731
|
|
|
732
|
+
/**
|
|
733
|
+
* Literal-free metadata for a shell invocation that did not match policy.
|
|
734
|
+
* argv values are deliberately omitted; the fingerprint distinguishes
|
|
735
|
+
* invocations without placing literal arguments in the decision.
|
|
736
|
+
*/
|
|
737
|
+
declare interface MissingShellCommand {
|
|
738
|
+
executable: string;
|
|
739
|
+
argvFingerprint: string;
|
|
740
|
+
argvLength: number;
|
|
741
|
+
dynamicTokenCount: number;
|
|
742
|
+
}
|
|
743
|
+
|
|
717
744
|
export declare const MOLTNET_TOOL_NAMES: readonly ["moltnet_pack_get", "moltnet_pack_create", "moltnet_pack_provenance", "moltnet_pack_render", "moltnet_rendered_pack_list", "moltnet_rendered_pack_get", "moltnet_diary_tags", "moltnet_list_entries", "moltnet_get_entry", "moltnet_search_entries", "moltnet_create_entry", "moltnet_get_task", "moltnet_list_task_attempts", "moltnet_list_task_messages", "moltnet_upload_task_artifact", "moltnet_list_task_artifacts", "moltnet_download_task_artifact", "moltnet_review_session_errors", "moltnet_host_exec"];
|
|
718
745
|
|
|
719
746
|
declare type MoltNetAgent = Awaited<ReturnType<typeof connect>>;
|
|
@@ -1183,6 +1210,7 @@ export declare interface SandboxConfig {
|
|
|
1183
1210
|
export declare interface SessionToolPolicy {
|
|
1184
1211
|
enforcement: ToolEnforcement;
|
|
1185
1212
|
allowedTools: ReadonlySet<string>;
|
|
1213
|
+
allowedShellCommands: readonly ShellCommandRule[];
|
|
1186
1214
|
/**
|
|
1187
1215
|
* `true` when the allow-set is a **degraded fallback** — the allowed-tools
|
|
1188
1216
|
* fetch failed or timed out and this policy is the fail-closed/fail-open
|
|
@@ -1195,6 +1223,10 @@ export declare interface SessionToolPolicy {
|
|
|
1195
1223
|
degraded: boolean;
|
|
1196
1224
|
}
|
|
1197
1225
|
|
|
1226
|
+
declare interface ShellCommandRule {
|
|
1227
|
+
argvPrefix: readonly [string, string, ...string[]];
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1198
1230
|
/** Extract snapshot-specific config for backwards compat with ensureSnapshot. */
|
|
1199
1231
|
export declare type SnapshotConfig = NonNullable<SandboxConfig['snapshot']>;
|
|
1200
1232
|
|
package/dist/index.js
CHANGED
|
@@ -30549,6 +30549,10 @@ var FIND_EXEC_FLAGS = new Set([
|
|
|
30549
30549
|
"-okdir"
|
|
30550
30550
|
]);
|
|
30551
30551
|
var ESCAPE_FLAG_SPECS = new Map([
|
|
30552
|
+
["env", {
|
|
30553
|
+
separate: ["-S", "--split-string"],
|
|
30554
|
+
inline: [{ flag: "--split-string" }]
|
|
30555
|
+
}],
|
|
30552
30556
|
["tar", { inline: [{ flag: "--to-command" }, {
|
|
30553
30557
|
flag: "--checkpoint-action",
|
|
30554
30558
|
valuePrefix: "exec="
|
|
@@ -30611,6 +30615,7 @@ function resolveDefaultBashWasm() {
|
|
|
30611
30615
|
var DEFAULT_MAX_COMMAND_LENGTH = 1e5;
|
|
30612
30616
|
var ASSIGNMENT_RE = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
30613
30617
|
var GLOB_RE = /[*?[\]]/;
|
|
30618
|
+
var WHITESPACE_RE = /\s/u;
|
|
30614
30619
|
var SUBSTITUTION_TYPES = ["command_substitution", "process_substitution"];
|
|
30615
30620
|
/** Guards against pathological nesting of escape-flag values. */
|
|
30616
30621
|
var MAX_ESCAPE_DEPTH = 3;
|
|
@@ -30620,11 +30625,22 @@ function baseName(raw) {
|
|
|
30620
30625
|
const slash = raw.lastIndexOf("/");
|
|
30621
30626
|
return slash === -1 ? raw : raw.slice(slash + 1);
|
|
30622
30627
|
}
|
|
30628
|
+
/** Build the public argv vector for one resolved invocation. */
|
|
30629
|
+
function invocationFor(words) {
|
|
30630
|
+
const head = words[0]?.name;
|
|
30631
|
+
if (head === null || head === void 0) return null;
|
|
30632
|
+
const name = baseName(head);
|
|
30633
|
+
return {
|
|
30634
|
+
name,
|
|
30635
|
+
argv: [name, ...words.slice(1).map((word) => word.name)]
|
|
30636
|
+
};
|
|
30637
|
+
}
|
|
30623
30638
|
/** Classify one AST node into a {@link Word}. */
|
|
30624
30639
|
function classifyToken(node) {
|
|
30625
30640
|
const raw = node.text;
|
|
30626
30641
|
switch (node.type) {
|
|
30627
|
-
case "word":
|
|
30642
|
+
case "word":
|
|
30643
|
+
case "number": return {
|
|
30628
30644
|
raw,
|
|
30629
30645
|
name: raw.includes("\\") ? null : raw
|
|
30630
30646
|
};
|
|
@@ -30736,7 +30752,12 @@ function advancePastWrapper(words, start, spec) {
|
|
|
30736
30752
|
* span (up to the `;`/`+` terminator).
|
|
30737
30753
|
*/
|
|
30738
30754
|
function resolveFind(words, wrapperDepth) {
|
|
30739
|
-
const
|
|
30755
|
+
const find = invocationFor(words);
|
|
30756
|
+
if (!find) return {
|
|
30757
|
+
ok: false,
|
|
30758
|
+
reason: "non-literal find invocation"
|
|
30759
|
+
};
|
|
30760
|
+
const invocations = [find];
|
|
30740
30761
|
const escapes = [];
|
|
30741
30762
|
for (let i = 1; i < words.length; i++) {
|
|
30742
30763
|
if (!FIND_EXEC_FLAGS.has(words[i].raw)) continue;
|
|
@@ -30753,13 +30774,13 @@ function resolveFind(words, wrapperDepth) {
|
|
|
30753
30774
|
};
|
|
30754
30775
|
const inner = resolveWords(cmd, wrapperDepth);
|
|
30755
30776
|
if (!inner.ok) return inner;
|
|
30756
|
-
|
|
30777
|
+
invocations.push(...inner.invocations);
|
|
30757
30778
|
escapes.push(...inner.escapes);
|
|
30758
30779
|
i = k - 1;
|
|
30759
30780
|
}
|
|
30760
30781
|
return {
|
|
30761
30782
|
ok: true,
|
|
30762
|
-
|
|
30783
|
+
invocations,
|
|
30763
30784
|
escapes
|
|
30764
30785
|
};
|
|
30765
30786
|
}
|
|
@@ -30772,7 +30793,7 @@ function resolveWords(words, wrapperDepth = 0) {
|
|
|
30772
30793
|
const head = words[0];
|
|
30773
30794
|
if (!head) return {
|
|
30774
30795
|
ok: true,
|
|
30775
|
-
|
|
30796
|
+
invocations: [],
|
|
30776
30797
|
escapes: []
|
|
30777
30798
|
};
|
|
30778
30799
|
if (head.name === null) return {
|
|
@@ -30780,11 +30801,20 @@ function resolveWords(words, wrapperDepth = 0) {
|
|
|
30780
30801
|
reason: `non-literal command name: ${head.raw}`
|
|
30781
30802
|
};
|
|
30782
30803
|
const name = head.name;
|
|
30804
|
+
if (WHITESPACE_RE.test(name)) return {
|
|
30805
|
+
ok: false,
|
|
30806
|
+
reason: "literal command name contains whitespace"
|
|
30807
|
+
};
|
|
30783
30808
|
if (GLOB_RE.test(name)) return {
|
|
30784
30809
|
ok: false,
|
|
30785
|
-
reason:
|
|
30810
|
+
reason: "command name contains a glob"
|
|
30786
30811
|
};
|
|
30787
30812
|
const exe = baseName(name);
|
|
30813
|
+
const invocation = invocationFor(words);
|
|
30814
|
+
if (!invocation) return {
|
|
30815
|
+
ok: false,
|
|
30816
|
+
reason: `non-literal command name: ${head.raw}`
|
|
30817
|
+
};
|
|
30788
30818
|
if (exe === "eval") return {
|
|
30789
30819
|
ok: false,
|
|
30790
30820
|
reason: "eval executes a dynamically built command"
|
|
@@ -30797,26 +30827,27 @@ function resolveWords(words, wrapperDepth = 0) {
|
|
|
30797
30827
|
reason: "wrapper nesting too deep"
|
|
30798
30828
|
};
|
|
30799
30829
|
const advance = advancePastWrapper(words, 1, spec);
|
|
30830
|
+
const wrapperEscapes = escapeFlagCommands(exe, words);
|
|
30800
30831
|
if (advance.kind === "deny") return {
|
|
30801
30832
|
ok: false,
|
|
30802
30833
|
reason: advance.reason
|
|
30803
30834
|
};
|
|
30804
30835
|
if (advance.kind === "none") return {
|
|
30805
30836
|
ok: true,
|
|
30806
|
-
|
|
30807
|
-
escapes:
|
|
30837
|
+
invocations: [invocation],
|
|
30838
|
+
escapes: wrapperEscapes
|
|
30808
30839
|
};
|
|
30809
30840
|
const inner = resolveWords(words.slice(advance.index), wrapperDepth + 1);
|
|
30810
30841
|
if (!inner.ok) return inner;
|
|
30811
30842
|
return {
|
|
30812
30843
|
ok: true,
|
|
30813
|
-
|
|
30814
|
-
escapes: inner.escapes
|
|
30844
|
+
invocations: [invocation, ...inner.invocations],
|
|
30845
|
+
escapes: [...wrapperEscapes, ...inner.escapes]
|
|
30815
30846
|
};
|
|
30816
30847
|
}
|
|
30817
30848
|
return {
|
|
30818
30849
|
ok: true,
|
|
30819
|
-
|
|
30850
|
+
invocations: [invocation],
|
|
30820
30851
|
escapes: escapeFlagCommands(exe, words)
|
|
30821
30852
|
};
|
|
30822
30853
|
}
|
|
@@ -30965,6 +30996,10 @@ var ShellCommandAnalyzer = class ShellCommandAnalyzer {
|
|
|
30965
30996
|
try {
|
|
30966
30997
|
const root = tree.rootNode;
|
|
30967
30998
|
const ast = root.toString();
|
|
30999
|
+
const hasOutputRedirection = root.descendantsOfType("file_redirect").some((redirect) => {
|
|
31000
|
+
const text = redirect?.text.trimStart() ?? "";
|
|
31001
|
+
return /^(?:\d+)?(?:>|>>|>\||&>|&>>|<>)/u.test(text);
|
|
31002
|
+
});
|
|
30968
31003
|
if (root.hasError) return {
|
|
30969
31004
|
ok: false,
|
|
30970
31005
|
command,
|
|
@@ -30995,12 +31030,16 @@ var ShellCommandAnalyzer = class ShellCommandAnalyzer {
|
|
|
30995
31030
|
ast
|
|
30996
31031
|
};
|
|
30997
31032
|
const raw = node.text;
|
|
30998
|
-
for (const
|
|
30999
|
-
name
|
|
31000
|
-
|
|
31001
|
-
|
|
31002
|
-
|
|
31003
|
-
|
|
31033
|
+
for (const invocation of resolution.invocations) {
|
|
31034
|
+
const { name } = invocation;
|
|
31035
|
+
tools.push({
|
|
31036
|
+
name,
|
|
31037
|
+
argv: invocation.argv,
|
|
31038
|
+
risk: classifyRisk(name),
|
|
31039
|
+
capabilities: gtfobinsFunctions(name),
|
|
31040
|
+
raw
|
|
31041
|
+
});
|
|
31042
|
+
}
|
|
31004
31043
|
for (const sub of resolution.escapes) {
|
|
31005
31044
|
if (depth >= MAX_ESCAPE_DEPTH) return {
|
|
31006
31045
|
ok: false,
|
|
@@ -31012,7 +31051,7 @@ var ShellCommandAnalyzer = class ShellCommandAnalyzer {
|
|
|
31012
31051
|
if (!nested.ok) return {
|
|
31013
31052
|
ok: false,
|
|
31014
31053
|
command,
|
|
31015
|
-
reason: `in escape flag
|
|
31054
|
+
reason: `unresolvable command in escape flag: ${nested.reason}`,
|
|
31016
31055
|
ast
|
|
31017
31056
|
};
|
|
31018
31057
|
tools.push(...nested.tools);
|
|
@@ -31028,7 +31067,8 @@ var ShellCommandAnalyzer = class ShellCommandAnalyzer {
|
|
|
31028
31067
|
ok: true,
|
|
31029
31068
|
command,
|
|
31030
31069
|
tools,
|
|
31031
|
-
ast
|
|
31070
|
+
ast,
|
|
31071
|
+
hasOutputRedirection
|
|
31032
31072
|
};
|
|
31033
31073
|
} finally {
|
|
31034
31074
|
tree.delete();
|
|
@@ -32389,26 +32429,72 @@ function createGondolinBashOps(vm, localCwd, guestWorkspace) {
|
|
|
32389
32429
|
function decideToolCall(input) {
|
|
32390
32430
|
if (input.enforcement === "off") return { allow: true };
|
|
32391
32431
|
const resolved = resolveNames(input);
|
|
32392
|
-
if (resolved.kind === "unresolvable") return fenced(input.enforcement,
|
|
32432
|
+
if (resolved.kind === "unresolvable") return fenced(input.enforcement, "shell command could not be statically authorized", "unresolvable shell command (watch)");
|
|
32393
32433
|
const arbitraryCode = [...new Set(resolved.tools.filter((tool) => tool.risk === "arbitrary-code").map((tool) => tool.name))];
|
|
32394
32434
|
if (arbitraryCode.length > 0) return fenced(input.enforcement, `arbitrary-code interpreter not authorizable by tool policy: ${arbitraryCode.join(", ")}`, `would block — arbitrary-code interpreter (watch): ${arbitraryCode.join(", ")}`, arbitraryCode);
|
|
32395
|
-
|
|
32396
|
-
|
|
32397
|
-
|
|
32435
|
+
if (input.toolName !== "bash") {
|
|
32436
|
+
const missing = resolved.tools.map((tool) => tool.name).filter((name) => !input.allowedTools.has(name));
|
|
32437
|
+
if (missing.length === 0) return { allow: true };
|
|
32438
|
+
return fenced(input.enforcement, `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing);
|
|
32439
|
+
}
|
|
32440
|
+
const matchedShellCommands = [];
|
|
32441
|
+
const missingShellCommands = resolved.tools.filter((tool) => {
|
|
32442
|
+
if (input.allowedTools.has(tool.name)) return false;
|
|
32443
|
+
const matched = input.allowedShellCommands.find((rule) => matchesArgvPrefix(tool.argv, rule.argvPrefix));
|
|
32444
|
+
if (!matched) return true;
|
|
32445
|
+
matchedShellCommands.push(toMatchedShellCommand(tool.name, matched.argvPrefix));
|
|
32446
|
+
return false;
|
|
32447
|
+
}).map(toMissingShellCommand);
|
|
32448
|
+
if (missingShellCommands.length === 0 && resolved.hasOutputRedirection && matchedShellCommands.length > 0) return fenced(input.enforcement, "shell output redirection requires broad executable permission", "would block shell output redirection under scoped command policy", [...new Set(matchedShellCommands.map(({ executable }) => executable))], matchedShellCommands.map(({ executable, argvPrefixFingerprint, argvPrefixLength }) => ({
|
|
32449
|
+
executable,
|
|
32450
|
+
argvFingerprint: argvPrefixFingerprint,
|
|
32451
|
+
argvLength: argvPrefixLength,
|
|
32452
|
+
dynamicTokenCount: 0
|
|
32453
|
+
})));
|
|
32454
|
+
if (missingShellCommands.length === 0) return matchedShellCommands.length > 0 ? {
|
|
32455
|
+
allow: true,
|
|
32456
|
+
matchedShellCommands
|
|
32457
|
+
} : { allow: true };
|
|
32458
|
+
const missing = [...new Set(missingShellCommands.map(({ executable }) => executable))];
|
|
32459
|
+
return fenced(input.enforcement, `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing, missingShellCommands);
|
|
32460
|
+
}
|
|
32461
|
+
function fingerprintArgv(argv) {
|
|
32462
|
+
return `sha256:${createHash$1("sha256").update(JSON.stringify(argv.map((token) => token === null ? { dynamic: true } : token))).digest("hex").slice(0, 16)}`;
|
|
32463
|
+
}
|
|
32464
|
+
function toMatchedShellCommand(executable, argvPrefix) {
|
|
32465
|
+
return {
|
|
32466
|
+
executable,
|
|
32467
|
+
argvPrefixFingerprint: fingerprintArgv(argvPrefix),
|
|
32468
|
+
argvPrefixLength: argvPrefix.length
|
|
32469
|
+
};
|
|
32470
|
+
}
|
|
32471
|
+
function toMissingShellCommand(tool) {
|
|
32472
|
+
return {
|
|
32473
|
+
executable: tool.name,
|
|
32474
|
+
argvFingerprint: fingerprintArgv(tool.argv),
|
|
32475
|
+
argvLength: tool.argv.length,
|
|
32476
|
+
dynamicTokenCount: tool.argv.filter((token) => token === null).length
|
|
32477
|
+
};
|
|
32478
|
+
}
|
|
32479
|
+
function matchesArgvPrefix(argv, prefix) {
|
|
32480
|
+
return argv.length >= prefix.length && prefix.every((token, index) => argv[index] !== null && argv[index] === token);
|
|
32398
32481
|
}
|
|
32399
32482
|
/**
|
|
32400
32483
|
* Shared enforce/watch branch: block in `enforce`, audit-and-allow in `watch`.
|
|
32401
32484
|
* `enforcement` is never `off` here (short-circuited by the caller).
|
|
32402
32485
|
*/
|
|
32403
|
-
function fenced(enforcement, blockReason, auditReason, missing) {
|
|
32486
|
+
function fenced(enforcement, blockReason, auditReason, missing, missingShellCommands) {
|
|
32404
32487
|
if (enforcement === "enforce") return {
|
|
32405
32488
|
allow: false,
|
|
32406
|
-
reason: blockReason
|
|
32489
|
+
reason: blockReason,
|
|
32490
|
+
...missing ? { missing } : {},
|
|
32491
|
+
...missingShellCommands?.length ? { missingShellCommands } : {}
|
|
32407
32492
|
};
|
|
32408
|
-
return
|
|
32493
|
+
return {
|
|
32409
32494
|
audit: auditReason,
|
|
32410
|
-
missing
|
|
32411
|
-
|
|
32495
|
+
...missing ? { missing } : {},
|
|
32496
|
+
...missingShellCommands?.length ? { missingShellCommands } : {}
|
|
32497
|
+
};
|
|
32412
32498
|
}
|
|
32413
32499
|
/**
|
|
32414
32500
|
* The authorization targets for a tool call. For structured tools it is the tool
|
|
@@ -32421,6 +32507,7 @@ function resolveNames(input) {
|
|
|
32421
32507
|
kind: "names",
|
|
32422
32508
|
tools: [{
|
|
32423
32509
|
name: input.toolName,
|
|
32510
|
+
argv: [input.toolName],
|
|
32424
32511
|
risk: "unknown"
|
|
32425
32512
|
}]
|
|
32426
32513
|
};
|
|
@@ -32434,8 +32521,10 @@ function resolveNames(input) {
|
|
|
32434
32521
|
kind: "names",
|
|
32435
32522
|
tools: analysis.tools.map((tool) => ({
|
|
32436
32523
|
name: tool.name,
|
|
32524
|
+
argv: tool.argv,
|
|
32437
32525
|
risk: tool.risk
|
|
32438
|
-
}))
|
|
32526
|
+
})),
|
|
32527
|
+
hasOutputRedirection: analysis.hasOutputRedirection === true
|
|
32439
32528
|
} : {
|
|
32440
32529
|
kind: "unresolvable",
|
|
32441
32530
|
reason: analysis.reason
|
|
@@ -32463,14 +32552,20 @@ async function resolveSessionToolPolicy(input) {
|
|
|
32463
32552
|
if (input.enforcement === "off") return {
|
|
32464
32553
|
enforcement: "off",
|
|
32465
32554
|
allowedTools: /* @__PURE__ */ new Set(),
|
|
32555
|
+
allowedShellCommands: [],
|
|
32466
32556
|
degraded: false
|
|
32467
32557
|
};
|
|
32468
32558
|
const timeoutMs = input.timeoutMs ?? 5e3;
|
|
32469
32559
|
try {
|
|
32470
32560
|
const resolved = await withTimeout$1(input.agent.runtimeProfiles.allowedTools(input.profileId, { teamId: input.teamId }), timeoutMs);
|
|
32561
|
+
const shellCommands = resolved.allowedShellCommands ?? [];
|
|
32471
32562
|
return {
|
|
32472
32563
|
enforcement: resolved.enforcement,
|
|
32473
32564
|
allowedTools: new Set(resolved.allowedTools),
|
|
32565
|
+
allowedShellCommands: shellCommands.map((rule) => {
|
|
32566
|
+
if (rule.argvPrefix.length < 2 || rule.argvPrefix.length > 8 || rule.argvPrefix.some((token) => !token)) throw new Error("runtime returned an invalid shell command rule");
|
|
32567
|
+
return { argvPrefix: rule.argvPrefix };
|
|
32568
|
+
}),
|
|
32474
32569
|
degraded: false
|
|
32475
32570
|
};
|
|
32476
32571
|
} catch (err) {
|
|
@@ -32485,6 +32580,7 @@ async function resolveSessionToolPolicy(input) {
|
|
|
32485
32580
|
return {
|
|
32486
32581
|
enforcement: input.enforcement,
|
|
32487
32582
|
allowedTools: /* @__PURE__ */ new Set(),
|
|
32583
|
+
allowedShellCommands: [],
|
|
32488
32584
|
degraded: true
|
|
32489
32585
|
};
|
|
32490
32586
|
}
|
|
@@ -32526,6 +32622,7 @@ function decideForEvent(event, policy, analyze) {
|
|
|
32526
32622
|
command,
|
|
32527
32623
|
enforcement: policy.enforcement,
|
|
32528
32624
|
allowedTools: policy.allowedTools,
|
|
32625
|
+
allowedShellCommands: policy.allowedShellCommands,
|
|
32529
32626
|
analyze
|
|
32530
32627
|
});
|
|
32531
32628
|
}
|
|
@@ -32539,7 +32636,14 @@ function createToolPolicyExtension(deps) {
|
|
|
32539
32636
|
if (deps.policy.enforcement === "off") return;
|
|
32540
32637
|
pi.on("tool_call", (event) => {
|
|
32541
32638
|
const decision = decideForEvent(event, deps.policy, (command) => deps.analyzer.analyze(command));
|
|
32542
|
-
if ("allow" in decision && decision.allow)
|
|
32639
|
+
if ("allow" in decision && decision.allow) {
|
|
32640
|
+
if (decision.matchedShellCommands?.length) deps.logger.debug({
|
|
32641
|
+
toolName: event.toolName,
|
|
32642
|
+
toolCallId: event.toolCallId,
|
|
32643
|
+
matchedShellCommands: decision.matchedShellCommands
|
|
32644
|
+
}, "tool_policy.shell_command_allowed");
|
|
32645
|
+
return;
|
|
32646
|
+
}
|
|
32543
32647
|
if ("audit" in decision) {
|
|
32544
32648
|
deps.logger.info({
|
|
32545
32649
|
toolName: event.toolName,
|
|
@@ -32553,7 +32657,9 @@ function createToolPolicyExtension(deps) {
|
|
|
32553
32657
|
toolName: event.toolName,
|
|
32554
32658
|
toolCallId: event.toolCallId,
|
|
32555
32659
|
degraded: deps.policy.degraded,
|
|
32556
|
-
reason: decision.reason
|
|
32660
|
+
reason: decision.reason,
|
|
32661
|
+
missingExecutables: decision.missing,
|
|
32662
|
+
missingShellCommands: decision.missingShellCommands
|
|
32557
32663
|
}, "tool_policy.blocked");
|
|
32558
32664
|
return {
|
|
32559
32665
|
block: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/pi-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Composable MoltNet runtime kernel for Pi agents in Gondolin VMs",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"@opentelemetry/api": "^1.9.0",
|
|
28
28
|
"typebox": "^1.2.8",
|
|
29
29
|
"@themoltnet/agent-runtime": "0.38.0",
|
|
30
|
-
"@themoltnet/
|
|
31
|
-
"@themoltnet/
|
|
30
|
+
"@themoltnet/shell-command-analyzer": "0.3.0",
|
|
31
|
+
"@themoltnet/sdk": "0.128.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@earendil-works/pi-ai": "0.74.0",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"vite": "^8.0.0",
|
|
46
46
|
"vite-plugin-dts": "^4.5.4",
|
|
47
47
|
"vitest": "^3.0.0",
|
|
48
|
-
"@moltnet/crypto-service": "0.1.0",
|
|
49
48
|
"@moltnet/models": "0.1.0",
|
|
49
|
+
"@moltnet/crypto-service": "0.1.0",
|
|
50
50
|
"@moltnet/tasks": "0.1.0"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|