@themoltnet/pi-runtime 0.2.0 → 0.4.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 +35 -0
- package/dist/index.js +157 -32
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,10 @@ export declare interface AllowedToolsClient {
|
|
|
43
43
|
}) => Promise<{
|
|
44
44
|
enforcement: ToolEnforcement;
|
|
45
45
|
allowedTools: string[];
|
|
46
|
+
allowedShellCommands: Array<{
|
|
47
|
+
argvPrefix: string[];
|
|
48
|
+
}>;
|
|
49
|
+
runtimeKind: string;
|
|
46
50
|
}>;
|
|
47
51
|
};
|
|
48
52
|
}
|
|
@@ -591,12 +595,16 @@ export declare function findMainWorktree(startPath?: string): string;
|
|
|
591
595
|
*/
|
|
592
596
|
export declare type GateDecision = {
|
|
593
597
|
allow: true;
|
|
598
|
+
matchedShellCommands?: MatchedShellCommand[];
|
|
594
599
|
} | {
|
|
595
600
|
allow: false;
|
|
596
601
|
reason: string;
|
|
602
|
+
missing?: string[];
|
|
603
|
+
missingShellCommands?: MissingShellCommand[];
|
|
597
604
|
} | {
|
|
598
605
|
audit: string;
|
|
599
606
|
missing?: string[];
|
|
607
|
+
missingShellCommands?: MissingShellCommand[];
|
|
600
608
|
};
|
|
601
609
|
|
|
602
610
|
export declare interface GateInput {
|
|
@@ -607,6 +615,8 @@ export declare interface GateInput {
|
|
|
607
615
|
enforcement: ToolEnforcement;
|
|
608
616
|
/** Names the policy allows (structured tool names + shell executable names). */
|
|
609
617
|
allowedTools: ReadonlySet<string>;
|
|
618
|
+
/** Shell argv rules. Each rule matches the first N statically known tokens. */
|
|
619
|
+
allowedShellCommands: readonly ShellCommandRule[];
|
|
610
620
|
/**
|
|
611
621
|
* Synchronous shell analyzer (`ShellCommandAnalyzer.analyze`). Injected so the
|
|
612
622
|
* decision stays pure and testable; the analyzer's async WASM init happens
|
|
@@ -694,6 +704,12 @@ export declare interface ManagedVm {
|
|
|
694
704
|
agentDir: string;
|
|
695
705
|
}
|
|
696
706
|
|
|
707
|
+
declare interface MatchedShellCommand {
|
|
708
|
+
executable: string;
|
|
709
|
+
argvPrefixFingerprint: string;
|
|
710
|
+
argvPrefixLength: number;
|
|
711
|
+
}
|
|
712
|
+
|
|
697
713
|
export declare function materializePiExtensions(input: {
|
|
698
714
|
runtime: PiRuntimeDefinition;
|
|
699
715
|
context: PiToolContext;
|
|
@@ -714,6 +730,18 @@ export declare function materializePiTools(input: {
|
|
|
714
730
|
};
|
|
715
731
|
}): Promise<ToolDefinition[]>;
|
|
716
732
|
|
|
733
|
+
/**
|
|
734
|
+
* Literal-free metadata for a shell invocation that did not match policy.
|
|
735
|
+
* argv values are deliberately omitted; the fingerprint distinguishes
|
|
736
|
+
* invocations without placing literal arguments in the decision.
|
|
737
|
+
*/
|
|
738
|
+
declare interface MissingShellCommand {
|
|
739
|
+
executable: string;
|
|
740
|
+
argvFingerprint: string;
|
|
741
|
+
argvLength: number;
|
|
742
|
+
dynamicTokenCount: number;
|
|
743
|
+
}
|
|
744
|
+
|
|
717
745
|
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
746
|
|
|
719
747
|
declare type MoltNetAgent = Awaited<ReturnType<typeof connect>>;
|
|
@@ -1045,6 +1073,8 @@ declare interface ResolveSessionToolPolicyInput {
|
|
|
1045
1073
|
agent: AllowedToolsClient;
|
|
1046
1074
|
profileId: string;
|
|
1047
1075
|
teamId: string;
|
|
1076
|
+
/** Runtime kind supplied by the trusted daemon adapter for this session. */
|
|
1077
|
+
runtimeKind: string;
|
|
1048
1078
|
/**
|
|
1049
1079
|
* The profile's enforcement mode, already known to the daemon from the
|
|
1050
1080
|
* resolved runtime profile. Used to decide fail-open vs fail-closed when the
|
|
@@ -1183,6 +1213,7 @@ export declare interface SandboxConfig {
|
|
|
1183
1213
|
export declare interface SessionToolPolicy {
|
|
1184
1214
|
enforcement: ToolEnforcement;
|
|
1185
1215
|
allowedTools: ReadonlySet<string>;
|
|
1216
|
+
allowedShellCommands: readonly ShellCommandRule[];
|
|
1186
1217
|
/**
|
|
1187
1218
|
* `true` when the allow-set is a **degraded fallback** — the allowed-tools
|
|
1188
1219
|
* fetch failed or timed out and this policy is the fail-closed/fail-open
|
|
@@ -1195,6 +1226,10 @@ export declare interface SessionToolPolicy {
|
|
|
1195
1226
|
degraded: boolean;
|
|
1196
1227
|
}
|
|
1197
1228
|
|
|
1229
|
+
declare interface ShellCommandRule {
|
|
1230
|
+
argvPrefix: readonly [string, string, ...string[]];
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1198
1233
|
/** Extract snapshot-specific config for backwards compat with ensureSnapshot. */
|
|
1199
1234
|
export declare type SnapshotConfig = NonNullable<SandboxConfig['snapshot']>;
|
|
1200
1235
|
|
package/dist/index.js
CHANGED
|
@@ -10046,7 +10046,11 @@ var ProblemDetailsSchema = _Object_({
|
|
|
10046
10046
|
}),
|
|
10047
10047
|
code: ProblemCodeSchema,
|
|
10048
10048
|
detail: Optional(String$1()),
|
|
10049
|
-
instance: Optional(String$1())
|
|
10049
|
+
instance: Optional(String$1()),
|
|
10050
|
+
retryAfter: Optional(Integer({
|
|
10051
|
+
minimum: 0,
|
|
10052
|
+
description: "Non-negative delay in seconds before retrying, matching the Retry-After response header when present."
|
|
10053
|
+
}))
|
|
10050
10054
|
}, {
|
|
10051
10055
|
$id: "ProblemDetails",
|
|
10052
10056
|
additionalProperties: true
|
|
@@ -14991,6 +14995,10 @@ var TaskAttempt = _Object_({
|
|
|
14991
14995
|
taskId: Uuid,
|
|
14992
14996
|
attemptN: Number$1({ minimum: 1 }),
|
|
14993
14997
|
claimedByAgentId: Uuid,
|
|
14998
|
+
leaseId: Union([Uuid, Null()]),
|
|
14999
|
+
runtimeProfileId: Union([Uuid, Null()]),
|
|
15000
|
+
runtimeProfileRevision: Union([Integer({ minimum: 1 }), Null()]),
|
|
15001
|
+
policySnapshotHash: Union([String$1({ pattern: "^sha256:[0-9a-f]{64}$" }), Null()]),
|
|
14994
15002
|
runtimeId: Union([Uuid, Null()]),
|
|
14995
15003
|
claimedAt: IsoTimestamp,
|
|
14996
15004
|
startedAt: Union([IsoTimestamp, Null()]),
|
|
@@ -30549,6 +30557,10 @@ var FIND_EXEC_FLAGS = new Set([
|
|
|
30549
30557
|
"-okdir"
|
|
30550
30558
|
]);
|
|
30551
30559
|
var ESCAPE_FLAG_SPECS = new Map([
|
|
30560
|
+
["env", {
|
|
30561
|
+
separate: ["-S", "--split-string"],
|
|
30562
|
+
inline: [{ flag: "--split-string" }]
|
|
30563
|
+
}],
|
|
30552
30564
|
["tar", { inline: [{ flag: "--to-command" }, {
|
|
30553
30565
|
flag: "--checkpoint-action",
|
|
30554
30566
|
valuePrefix: "exec="
|
|
@@ -30611,6 +30623,7 @@ function resolveDefaultBashWasm() {
|
|
|
30611
30623
|
var DEFAULT_MAX_COMMAND_LENGTH = 1e5;
|
|
30612
30624
|
var ASSIGNMENT_RE = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
30613
30625
|
var GLOB_RE = /[*?[\]]/;
|
|
30626
|
+
var WHITESPACE_RE = /\s/u;
|
|
30614
30627
|
var SUBSTITUTION_TYPES = ["command_substitution", "process_substitution"];
|
|
30615
30628
|
/** Guards against pathological nesting of escape-flag values. */
|
|
30616
30629
|
var MAX_ESCAPE_DEPTH = 3;
|
|
@@ -30620,11 +30633,22 @@ function baseName(raw) {
|
|
|
30620
30633
|
const slash = raw.lastIndexOf("/");
|
|
30621
30634
|
return slash === -1 ? raw : raw.slice(slash + 1);
|
|
30622
30635
|
}
|
|
30636
|
+
/** Build the public argv vector for one resolved invocation. */
|
|
30637
|
+
function invocationFor(words) {
|
|
30638
|
+
const head = words[0]?.name;
|
|
30639
|
+
if (head === null || head === void 0) return null;
|
|
30640
|
+
const name = baseName(head);
|
|
30641
|
+
return {
|
|
30642
|
+
name,
|
|
30643
|
+
argv: [name, ...words.slice(1).map((word) => word.name)]
|
|
30644
|
+
};
|
|
30645
|
+
}
|
|
30623
30646
|
/** Classify one AST node into a {@link Word}. */
|
|
30624
30647
|
function classifyToken(node) {
|
|
30625
30648
|
const raw = node.text;
|
|
30626
30649
|
switch (node.type) {
|
|
30627
|
-
case "word":
|
|
30650
|
+
case "word":
|
|
30651
|
+
case "number": return {
|
|
30628
30652
|
raw,
|
|
30629
30653
|
name: raw.includes("\\") ? null : raw
|
|
30630
30654
|
};
|
|
@@ -30736,7 +30760,12 @@ function advancePastWrapper(words, start, spec) {
|
|
|
30736
30760
|
* span (up to the `;`/`+` terminator).
|
|
30737
30761
|
*/
|
|
30738
30762
|
function resolveFind(words, wrapperDepth) {
|
|
30739
|
-
const
|
|
30763
|
+
const find = invocationFor(words);
|
|
30764
|
+
if (!find) return {
|
|
30765
|
+
ok: false,
|
|
30766
|
+
reason: "non-literal find invocation"
|
|
30767
|
+
};
|
|
30768
|
+
const invocations = [find];
|
|
30740
30769
|
const escapes = [];
|
|
30741
30770
|
for (let i = 1; i < words.length; i++) {
|
|
30742
30771
|
if (!FIND_EXEC_FLAGS.has(words[i].raw)) continue;
|
|
@@ -30753,13 +30782,13 @@ function resolveFind(words, wrapperDepth) {
|
|
|
30753
30782
|
};
|
|
30754
30783
|
const inner = resolveWords(cmd, wrapperDepth);
|
|
30755
30784
|
if (!inner.ok) return inner;
|
|
30756
|
-
|
|
30785
|
+
invocations.push(...inner.invocations);
|
|
30757
30786
|
escapes.push(...inner.escapes);
|
|
30758
30787
|
i = k - 1;
|
|
30759
30788
|
}
|
|
30760
30789
|
return {
|
|
30761
30790
|
ok: true,
|
|
30762
|
-
|
|
30791
|
+
invocations,
|
|
30763
30792
|
escapes
|
|
30764
30793
|
};
|
|
30765
30794
|
}
|
|
@@ -30772,7 +30801,7 @@ function resolveWords(words, wrapperDepth = 0) {
|
|
|
30772
30801
|
const head = words[0];
|
|
30773
30802
|
if (!head) return {
|
|
30774
30803
|
ok: true,
|
|
30775
|
-
|
|
30804
|
+
invocations: [],
|
|
30776
30805
|
escapes: []
|
|
30777
30806
|
};
|
|
30778
30807
|
if (head.name === null) return {
|
|
@@ -30780,11 +30809,20 @@ function resolveWords(words, wrapperDepth = 0) {
|
|
|
30780
30809
|
reason: `non-literal command name: ${head.raw}`
|
|
30781
30810
|
};
|
|
30782
30811
|
const name = head.name;
|
|
30812
|
+
if (WHITESPACE_RE.test(name)) return {
|
|
30813
|
+
ok: false,
|
|
30814
|
+
reason: "literal command name contains whitespace"
|
|
30815
|
+
};
|
|
30783
30816
|
if (GLOB_RE.test(name)) return {
|
|
30784
30817
|
ok: false,
|
|
30785
|
-
reason:
|
|
30818
|
+
reason: "command name contains a glob"
|
|
30786
30819
|
};
|
|
30787
30820
|
const exe = baseName(name);
|
|
30821
|
+
const invocation = invocationFor(words);
|
|
30822
|
+
if (!invocation) return {
|
|
30823
|
+
ok: false,
|
|
30824
|
+
reason: `non-literal command name: ${head.raw}`
|
|
30825
|
+
};
|
|
30788
30826
|
if (exe === "eval") return {
|
|
30789
30827
|
ok: false,
|
|
30790
30828
|
reason: "eval executes a dynamically built command"
|
|
@@ -30797,26 +30835,27 @@ function resolveWords(words, wrapperDepth = 0) {
|
|
|
30797
30835
|
reason: "wrapper nesting too deep"
|
|
30798
30836
|
};
|
|
30799
30837
|
const advance = advancePastWrapper(words, 1, spec);
|
|
30838
|
+
const wrapperEscapes = escapeFlagCommands(exe, words);
|
|
30800
30839
|
if (advance.kind === "deny") return {
|
|
30801
30840
|
ok: false,
|
|
30802
30841
|
reason: advance.reason
|
|
30803
30842
|
};
|
|
30804
30843
|
if (advance.kind === "none") return {
|
|
30805
30844
|
ok: true,
|
|
30806
|
-
|
|
30807
|
-
escapes:
|
|
30845
|
+
invocations: [invocation],
|
|
30846
|
+
escapes: wrapperEscapes
|
|
30808
30847
|
};
|
|
30809
30848
|
const inner = resolveWords(words.slice(advance.index), wrapperDepth + 1);
|
|
30810
30849
|
if (!inner.ok) return inner;
|
|
30811
30850
|
return {
|
|
30812
30851
|
ok: true,
|
|
30813
|
-
|
|
30814
|
-
escapes: inner.escapes
|
|
30852
|
+
invocations: [invocation, ...inner.invocations],
|
|
30853
|
+
escapes: [...wrapperEscapes, ...inner.escapes]
|
|
30815
30854
|
};
|
|
30816
30855
|
}
|
|
30817
30856
|
return {
|
|
30818
30857
|
ok: true,
|
|
30819
|
-
|
|
30858
|
+
invocations: [invocation],
|
|
30820
30859
|
escapes: escapeFlagCommands(exe, words)
|
|
30821
30860
|
};
|
|
30822
30861
|
}
|
|
@@ -30965,6 +31004,10 @@ var ShellCommandAnalyzer = class ShellCommandAnalyzer {
|
|
|
30965
31004
|
try {
|
|
30966
31005
|
const root = tree.rootNode;
|
|
30967
31006
|
const ast = root.toString();
|
|
31007
|
+
const hasOutputRedirection = root.descendantsOfType("file_redirect").some((redirect) => {
|
|
31008
|
+
const text = redirect?.text.trimStart() ?? "";
|
|
31009
|
+
return /^(?:\d+)?(?:>|>>|>\||&>|&>>|<>)/u.test(text);
|
|
31010
|
+
});
|
|
30968
31011
|
if (root.hasError) return {
|
|
30969
31012
|
ok: false,
|
|
30970
31013
|
command,
|
|
@@ -30995,12 +31038,16 @@ var ShellCommandAnalyzer = class ShellCommandAnalyzer {
|
|
|
30995
31038
|
ast
|
|
30996
31039
|
};
|
|
30997
31040
|
const raw = node.text;
|
|
30998
|
-
for (const
|
|
30999
|
-
name
|
|
31000
|
-
|
|
31001
|
-
|
|
31002
|
-
|
|
31003
|
-
|
|
31041
|
+
for (const invocation of resolution.invocations) {
|
|
31042
|
+
const { name } = invocation;
|
|
31043
|
+
tools.push({
|
|
31044
|
+
name,
|
|
31045
|
+
argv: invocation.argv,
|
|
31046
|
+
risk: classifyRisk(name),
|
|
31047
|
+
capabilities: gtfobinsFunctions(name),
|
|
31048
|
+
raw
|
|
31049
|
+
});
|
|
31050
|
+
}
|
|
31004
31051
|
for (const sub of resolution.escapes) {
|
|
31005
31052
|
if (depth >= MAX_ESCAPE_DEPTH) return {
|
|
31006
31053
|
ok: false,
|
|
@@ -31012,7 +31059,7 @@ var ShellCommandAnalyzer = class ShellCommandAnalyzer {
|
|
|
31012
31059
|
if (!nested.ok) return {
|
|
31013
31060
|
ok: false,
|
|
31014
31061
|
command,
|
|
31015
|
-
reason: `in escape flag
|
|
31062
|
+
reason: `unresolvable command in escape flag: ${nested.reason}`,
|
|
31016
31063
|
ast
|
|
31017
31064
|
};
|
|
31018
31065
|
tools.push(...nested.tools);
|
|
@@ -31028,7 +31075,8 @@ var ShellCommandAnalyzer = class ShellCommandAnalyzer {
|
|
|
31028
31075
|
ok: true,
|
|
31029
31076
|
command,
|
|
31030
31077
|
tools,
|
|
31031
|
-
ast
|
|
31078
|
+
ast,
|
|
31079
|
+
hasOutputRedirection
|
|
31032
31080
|
};
|
|
31033
31081
|
} finally {
|
|
31034
31082
|
tree.delete();
|
|
@@ -32389,26 +32437,72 @@ function createGondolinBashOps(vm, localCwd, guestWorkspace) {
|
|
|
32389
32437
|
function decideToolCall(input) {
|
|
32390
32438
|
if (input.enforcement === "off") return { allow: true };
|
|
32391
32439
|
const resolved = resolveNames(input);
|
|
32392
|
-
if (resolved.kind === "unresolvable") return fenced(input.enforcement,
|
|
32440
|
+
if (resolved.kind === "unresolvable") return fenced(input.enforcement, "shell command could not be statically authorized", "unresolvable shell command (watch)");
|
|
32393
32441
|
const arbitraryCode = [...new Set(resolved.tools.filter((tool) => tool.risk === "arbitrary-code").map((tool) => tool.name))];
|
|
32394
32442
|
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
|
-
|
|
32443
|
+
if (input.toolName !== "bash") {
|
|
32444
|
+
const missing = resolved.tools.map((tool) => tool.name).filter((name) => !input.allowedTools.has(name));
|
|
32445
|
+
if (missing.length === 0) return { allow: true };
|
|
32446
|
+
return fenced(input.enforcement, `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing);
|
|
32447
|
+
}
|
|
32448
|
+
const matchedShellCommands = [];
|
|
32449
|
+
const missingShellCommands = resolved.tools.filter((tool) => {
|
|
32450
|
+
if (input.allowedTools.has(tool.name)) return false;
|
|
32451
|
+
const matched = input.allowedShellCommands.find((rule) => matchesArgvPrefix(tool.argv, rule.argvPrefix));
|
|
32452
|
+
if (!matched) return true;
|
|
32453
|
+
matchedShellCommands.push(toMatchedShellCommand(tool.name, matched.argvPrefix));
|
|
32454
|
+
return false;
|
|
32455
|
+
}).map(toMissingShellCommand);
|
|
32456
|
+
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 }) => ({
|
|
32457
|
+
executable,
|
|
32458
|
+
argvFingerprint: argvPrefixFingerprint,
|
|
32459
|
+
argvLength: argvPrefixLength,
|
|
32460
|
+
dynamicTokenCount: 0
|
|
32461
|
+
})));
|
|
32462
|
+
if (missingShellCommands.length === 0) return matchedShellCommands.length > 0 ? {
|
|
32463
|
+
allow: true,
|
|
32464
|
+
matchedShellCommands
|
|
32465
|
+
} : { allow: true };
|
|
32466
|
+
const missing = [...new Set(missingShellCommands.map(({ executable }) => executable))];
|
|
32467
|
+
return fenced(input.enforcement, `not permitted by tool policy: ${missing.join(", ")}`, `would block (watch): ${missing.join(", ")}`, missing, missingShellCommands);
|
|
32468
|
+
}
|
|
32469
|
+
function fingerprintArgv(argv) {
|
|
32470
|
+
return `sha256:${createHash$1("sha256").update(JSON.stringify(argv.map((token) => token === null ? { dynamic: true } : token))).digest("hex").slice(0, 16)}`;
|
|
32471
|
+
}
|
|
32472
|
+
function toMatchedShellCommand(executable, argvPrefix) {
|
|
32473
|
+
return {
|
|
32474
|
+
executable,
|
|
32475
|
+
argvPrefixFingerprint: fingerprintArgv(argvPrefix),
|
|
32476
|
+
argvPrefixLength: argvPrefix.length
|
|
32477
|
+
};
|
|
32478
|
+
}
|
|
32479
|
+
function toMissingShellCommand(tool) {
|
|
32480
|
+
return {
|
|
32481
|
+
executable: tool.name,
|
|
32482
|
+
argvFingerprint: fingerprintArgv(tool.argv),
|
|
32483
|
+
argvLength: tool.argv.length,
|
|
32484
|
+
dynamicTokenCount: tool.argv.filter((token) => token === null).length
|
|
32485
|
+
};
|
|
32486
|
+
}
|
|
32487
|
+
function matchesArgvPrefix(argv, prefix) {
|
|
32488
|
+
return argv.length >= prefix.length && prefix.every((token, index) => argv[index] !== null && argv[index] === token);
|
|
32398
32489
|
}
|
|
32399
32490
|
/**
|
|
32400
32491
|
* Shared enforce/watch branch: block in `enforce`, audit-and-allow in `watch`.
|
|
32401
32492
|
* `enforcement` is never `off` here (short-circuited by the caller).
|
|
32402
32493
|
*/
|
|
32403
|
-
function fenced(enforcement, blockReason, auditReason, missing) {
|
|
32494
|
+
function fenced(enforcement, blockReason, auditReason, missing, missingShellCommands) {
|
|
32404
32495
|
if (enforcement === "enforce") return {
|
|
32405
32496
|
allow: false,
|
|
32406
|
-
reason: blockReason
|
|
32497
|
+
reason: blockReason,
|
|
32498
|
+
...missing ? { missing } : {},
|
|
32499
|
+
...missingShellCommands?.length ? { missingShellCommands } : {}
|
|
32407
32500
|
};
|
|
32408
|
-
return
|
|
32501
|
+
return {
|
|
32409
32502
|
audit: auditReason,
|
|
32410
|
-
missing
|
|
32411
|
-
|
|
32503
|
+
...missing ? { missing } : {},
|
|
32504
|
+
...missingShellCommands?.length ? { missingShellCommands } : {}
|
|
32505
|
+
};
|
|
32412
32506
|
}
|
|
32413
32507
|
/**
|
|
32414
32508
|
* The authorization targets for a tool call. For structured tools it is the tool
|
|
@@ -32421,6 +32515,7 @@ function resolveNames(input) {
|
|
|
32421
32515
|
kind: "names",
|
|
32422
32516
|
tools: [{
|
|
32423
32517
|
name: input.toolName,
|
|
32518
|
+
argv: [input.toolName],
|
|
32424
32519
|
risk: "unknown"
|
|
32425
32520
|
}]
|
|
32426
32521
|
};
|
|
@@ -32434,8 +32529,10 @@ function resolveNames(input) {
|
|
|
32434
32529
|
kind: "names",
|
|
32435
32530
|
tools: analysis.tools.map((tool) => ({
|
|
32436
32531
|
name: tool.name,
|
|
32532
|
+
argv: tool.argv,
|
|
32437
32533
|
risk: tool.risk
|
|
32438
|
-
}))
|
|
32534
|
+
})),
|
|
32535
|
+
hasOutputRedirection: analysis.hasOutputRedirection === true
|
|
32439
32536
|
} : {
|
|
32440
32537
|
kind: "unresolvable",
|
|
32441
32538
|
reason: analysis.reason
|
|
@@ -32463,14 +32560,24 @@ async function resolveSessionToolPolicy(input) {
|
|
|
32463
32560
|
if (input.enforcement === "off") return {
|
|
32464
32561
|
enforcement: "off",
|
|
32465
32562
|
allowedTools: /* @__PURE__ */ new Set(),
|
|
32563
|
+
allowedShellCommands: [],
|
|
32466
32564
|
degraded: false
|
|
32467
32565
|
};
|
|
32468
32566
|
const timeoutMs = input.timeoutMs ?? 5e3;
|
|
32469
32567
|
try {
|
|
32470
32568
|
const resolved = await withTimeout$1(input.agent.runtimeProfiles.allowedTools(input.profileId, { teamId: input.teamId }), timeoutMs);
|
|
32569
|
+
const shellCommands = resolved.allowedShellCommands ?? [];
|
|
32570
|
+
if (resolved.runtimeKind !== input.runtimeKind) throw new RuntimeKindMismatchError({
|
|
32571
|
+
expectedRuntimeKind: input.runtimeKind,
|
|
32572
|
+
receivedRuntimeKind: resolved.runtimeKind
|
|
32573
|
+
});
|
|
32471
32574
|
return {
|
|
32472
32575
|
enforcement: resolved.enforcement,
|
|
32473
32576
|
allowedTools: new Set(resolved.allowedTools),
|
|
32577
|
+
allowedShellCommands: shellCommands.map((rule) => {
|
|
32578
|
+
if (rule.argvPrefix.length < 2 || rule.argvPrefix.length > 8 || rule.argvPrefix.some((token) => !token)) throw new Error("runtime returned an invalid shell command rule");
|
|
32579
|
+
return { argvPrefix: rule.argvPrefix };
|
|
32580
|
+
}),
|
|
32474
32581
|
degraded: false
|
|
32475
32582
|
};
|
|
32476
32583
|
} catch (err) {
|
|
@@ -32485,6 +32592,7 @@ async function resolveSessionToolPolicy(input) {
|
|
|
32485
32592
|
return {
|
|
32486
32593
|
enforcement: input.enforcement,
|
|
32487
32594
|
allowedTools: /* @__PURE__ */ new Set(),
|
|
32595
|
+
allowedShellCommands: [],
|
|
32488
32596
|
degraded: true
|
|
32489
32597
|
};
|
|
32490
32598
|
}
|
|
@@ -32496,6 +32604,12 @@ var ToolPolicyResolveTimeoutError = class extends Error {
|
|
|
32496
32604
|
this.name = "ToolPolicyResolveTimeoutError";
|
|
32497
32605
|
}
|
|
32498
32606
|
};
|
|
32607
|
+
var RuntimeKindMismatchError = class extends Error {
|
|
32608
|
+
constructor(input) {
|
|
32609
|
+
super(`runtime kind mismatch: expected ${input.expectedRuntimeKind}, received ${input.receivedRuntimeKind}`);
|
|
32610
|
+
this.name = "RuntimeKindMismatchError";
|
|
32611
|
+
}
|
|
32612
|
+
};
|
|
32499
32613
|
/**
|
|
32500
32614
|
* Reject with {@link ToolPolicyResolveTimeoutError} if `promise` does not settle
|
|
32501
32615
|
* within `timeoutMs`. A non-positive `timeoutMs` disables the deadline. The
|
|
@@ -32526,6 +32640,7 @@ function decideForEvent(event, policy, analyze) {
|
|
|
32526
32640
|
command,
|
|
32527
32641
|
enforcement: policy.enforcement,
|
|
32528
32642
|
allowedTools: policy.allowedTools,
|
|
32643
|
+
allowedShellCommands: policy.allowedShellCommands,
|
|
32529
32644
|
analyze
|
|
32530
32645
|
});
|
|
32531
32646
|
}
|
|
@@ -32539,7 +32654,14 @@ function createToolPolicyExtension(deps) {
|
|
|
32539
32654
|
if (deps.policy.enforcement === "off") return;
|
|
32540
32655
|
pi.on("tool_call", (event) => {
|
|
32541
32656
|
const decision = decideForEvent(event, deps.policy, (command) => deps.analyzer.analyze(command));
|
|
32542
|
-
if ("allow" in decision && decision.allow)
|
|
32657
|
+
if ("allow" in decision && decision.allow) {
|
|
32658
|
+
if (decision.matchedShellCommands?.length) deps.logger.debug({
|
|
32659
|
+
toolName: event.toolName,
|
|
32660
|
+
toolCallId: event.toolCallId,
|
|
32661
|
+
matchedShellCommands: decision.matchedShellCommands
|
|
32662
|
+
}, "tool_policy.shell_command_allowed");
|
|
32663
|
+
return;
|
|
32664
|
+
}
|
|
32543
32665
|
if ("audit" in decision) {
|
|
32544
32666
|
deps.logger.info({
|
|
32545
32667
|
toolName: event.toolName,
|
|
@@ -32553,7 +32675,9 @@ function createToolPolicyExtension(deps) {
|
|
|
32553
32675
|
toolName: event.toolName,
|
|
32554
32676
|
toolCallId: event.toolCallId,
|
|
32555
32677
|
degraded: deps.policy.degraded,
|
|
32556
|
-
reason: decision.reason
|
|
32678
|
+
reason: decision.reason,
|
|
32679
|
+
missingExecutables: decision.missing,
|
|
32680
|
+
missingShellCommands: decision.missingShellCommands
|
|
32557
32681
|
}, "tool_policy.blocked");
|
|
32558
32682
|
return {
|
|
32559
32683
|
block: true,
|
|
@@ -34196,6 +34320,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
34196
34320
|
agent: moltnetAgent,
|
|
34197
34321
|
profileId: opts.runtimeProfileId,
|
|
34198
34322
|
teamId: taskTeamId,
|
|
34323
|
+
runtimeKind: opts.runtimeDefinition?.runtimeKind ?? "gondolin_pi",
|
|
34199
34324
|
enforcement: opts.toolEnforcement,
|
|
34200
34325
|
logger: toolPolicyLogger
|
|
34201
34326
|
})]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/pi-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Composable MoltNet runtime kernel for Pi agents in Gondolin VMs",
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"@earendil-works/gondolin": "^0.9.1",
|
|
27
27
|
"@opentelemetry/api": "^1.9.0",
|
|
28
28
|
"typebox": "^1.2.8",
|
|
29
|
-
"@themoltnet/agent-runtime": "0.
|
|
30
|
-
"@themoltnet/
|
|
31
|
-
"@themoltnet/
|
|
29
|
+
"@themoltnet/agent-runtime": "0.39.0",
|
|
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,9 +45,9 @@
|
|
|
45
45
|
"vite": "^8.0.0",
|
|
46
46
|
"vite-plugin-dts": "^4.5.4",
|
|
47
47
|
"vitest": "^3.0.0",
|
|
48
|
+
"@moltnet/tasks": "0.1.0",
|
|
48
49
|
"@moltnet/crypto-service": "0.1.0",
|
|
49
|
-
"@moltnet/models": "0.1.0"
|
|
50
|
-
"@moltnet/tasks": "0.1.0"
|
|
50
|
+
"@moltnet/models": "0.1.0"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": ">=22"
|