@anthropic-ai/sdk 0.119.0 → 0.120.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/CHANGELOG.md +13 -0
- package/helpers/beta/environments.d.mts +5 -4
- package/helpers/beta/environments.d.mts.map +1 -1
- package/helpers/beta/environments.d.ts +5 -4
- package/helpers/beta/environments.d.ts.map +1 -1
- package/helpers/beta/environments.js +5 -4
- package/helpers/beta/environments.js.map +1 -1
- package/helpers/beta/environments.mjs +5 -4
- package/helpers/beta/environments.mjs.map +1 -1
- package/internal/file-store.d.mts +136 -0
- package/internal/file-store.d.mts.map +1 -0
- package/internal/file-store.d.ts +136 -0
- package/internal/file-store.d.ts.map +1 -0
- package/internal/file-store.js +531 -0
- package/internal/file-store.js.map +1 -0
- package/internal/file-store.mjs +523 -0
- package/internal/file-store.mjs.map +1 -0
- package/lib/environments/poller.d.mts +6 -0
- package/lib/environments/poller.d.mts.map +1 -1
- package/lib/environments/poller.d.ts +6 -0
- package/lib/environments/poller.d.ts.map +1 -1
- package/lib/environments/poller.js +46 -1
- package/lib/environments/poller.js.map +1 -1
- package/lib/environments/poller.mjs +46 -1
- package/lib/environments/poller.mjs.map +1 -1
- package/lib/environments/worker.d.mts +89 -12
- package/lib/environments/worker.d.mts.map +1 -1
- package/lib/environments/worker.d.ts +89 -12
- package/lib/environments/worker.d.ts.map +1 -1
- package/lib/environments/worker.js +336 -67
- package/lib/environments/worker.js.map +1 -1
- package/lib/environments/worker.mjs +336 -68
- package/lib/environments/worker.mjs.map +1 -1
- package/package.json +1 -1
- package/resources/beta/agents/agents.d.mts +322 -27
- package/resources/beta/agents/agents.d.mts.map +1 -1
- package/resources/beta/agents/agents.d.ts +322 -27
- package/resources/beta/agents/agents.d.ts.map +1 -1
- package/resources/beta/agents/agents.js.map +1 -1
- package/resources/beta/agents/agents.mjs.map +1 -1
- package/resources/beta/agents/index.d.mts +1 -1
- package/resources/beta/agents/index.d.mts.map +1 -1
- package/resources/beta/agents/index.d.ts +1 -1
- package/resources/beta/agents/index.d.ts.map +1 -1
- package/resources/beta/agents/index.js.map +1 -1
- package/resources/beta/agents/index.mjs.map +1 -1
- package/resources/beta/beta.d.mts +2 -2
- package/resources/beta/beta.d.mts.map +1 -1
- package/resources/beta/beta.d.ts +2 -2
- package/resources/beta/beta.d.ts.map +1 -1
- package/resources/beta/beta.js.map +1 -1
- package/resources/beta/beta.mjs.map +1 -1
- package/resources/beta/environments/work.d.mts +5 -1
- package/resources/beta/environments/work.d.mts.map +1 -1
- package/resources/beta/environments/work.d.ts +5 -1
- package/resources/beta/environments/work.d.ts.map +1 -1
- package/resources/beta/environments/work.js +0 -17
- package/resources/beta/environments/work.js.map +1 -1
- package/resources/beta/environments/work.mjs +0 -17
- package/resources/beta/environments/work.mjs.map +1 -1
- package/resources/beta/index.d.mts +1 -1
- package/resources/beta/index.d.mts.map +1 -1
- package/resources/beta/index.d.ts +1 -1
- package/resources/beta/index.d.ts.map +1 -1
- package/resources/beta/index.js.map +1 -1
- package/resources/beta/index.mjs.map +1 -1
- package/resources/beta/messages/messages.d.mts +1 -1
- package/resources/beta/messages/messages.d.ts +1 -1
- package/resources/messages/messages.d.mts +1 -1
- package/resources/messages/messages.d.ts +1 -1
- package/src/helpers/beta/environments.ts +5 -4
- package/src/internal/file-store.ts +550 -0
- package/src/lib/environments/poller.ts +47 -1
- package/src/lib/environments/worker.ts +419 -79
- package/src/resources/beta/agents/agents.ts +435 -30
- package/src/resources/beta/agents/index.ts +17 -0
- package/src/resources/beta/beta.ts +34 -0
- package/src/resources/beta/environments/work.ts +7 -0
- package/src/resources/beta/index.ts +17 -0
- package/src/resources/beta/messages/messages.ts +1 -1
- package/src/resources/messages/messages.ts +1 -1
- package/src/tools/agent-toolset/fs-util.ts +40 -24
- package/src/tools/agent-toolset/memories.ts +1017 -0
- package/src/tools/agent-toolset/node.browser.ts +68 -0
- package/src/tools/agent-toolset/node.ts +139 -53
- package/src/tools/agent-toolset/skills.ts +29 -9
- package/src/tools/agent-toolset/sync-interval.ts +30 -0
- package/src/version.ts +1 -1
- package/tools/agent-toolset/fs-util.d.mts +23 -10
- package/tools/agent-toolset/fs-util.d.mts.map +1 -1
- package/tools/agent-toolset/fs-util.d.ts +23 -10
- package/tools/agent-toolset/fs-util.d.ts.map +1 -1
- package/tools/agent-toolset/fs-util.js +40 -25
- package/tools/agent-toolset/fs-util.js.map +1 -1
- package/tools/agent-toolset/fs-util.mjs +38 -25
- package/tools/agent-toolset/fs-util.mjs.map +1 -1
- package/tools/agent-toolset/memories.d.mts +173 -0
- package/tools/agent-toolset/memories.d.mts.map +1 -0
- package/tools/agent-toolset/memories.d.ts +173 -0
- package/tools/agent-toolset/memories.d.ts.map +1 -0
- package/tools/agent-toolset/memories.js +902 -0
- package/tools/agent-toolset/memories.js.map +1 -0
- package/tools/agent-toolset/memories.mjs +895 -0
- package/tools/agent-toolset/memories.mjs.map +1 -0
- package/tools/agent-toolset/node.browser.d.mts +31 -0
- package/tools/agent-toolset/node.browser.d.mts.map +1 -1
- package/tools/agent-toolset/node.browser.d.ts +31 -0
- package/tools/agent-toolset/node.browser.d.ts.map +1 -1
- package/tools/agent-toolset/node.browser.js +59 -1
- package/tools/agent-toolset/node.browser.js.map +1 -1
- package/tools/agent-toolset/node.browser.mjs +54 -0
- package/tools/agent-toolset/node.browser.mjs.map +1 -1
- package/tools/agent-toolset/node.d.mts +54 -22
- package/tools/agent-toolset/node.d.mts.map +1 -1
- package/tools/agent-toolset/node.d.ts +54 -22
- package/tools/agent-toolset/node.d.ts.map +1 -1
- package/tools/agent-toolset/node.js +83 -44
- package/tools/agent-toolset/node.js.map +1 -1
- package/tools/agent-toolset/node.mjs +77 -44
- package/tools/agent-toolset/node.mjs.map +1 -1
- package/tools/agent-toolset/skills.d.mts +16 -7
- package/tools/agent-toolset/skills.d.mts.map +1 -1
- package/tools/agent-toolset/skills.d.ts +16 -7
- package/tools/agent-toolset/skills.d.ts.map +1 -1
- package/tools/agent-toolset/skills.js +27 -9
- package/tools/agent-toolset/skills.js.map +1 -1
- package/tools/agent-toolset/skills.mjs +27 -9
- package/tools/agent-toolset/skills.mjs.map +1 -1
- package/tools/agent-toolset/sync-interval.d.mts +19 -0
- package/tools/agent-toolset/sync-interval.d.mts.map +1 -0
- package/tools/agent-toolset/sync-interval.d.ts +19 -0
- package/tools/agent-toolset/sync-interval.d.ts.map +1 -0
- package/tools/agent-toolset/sync-interval.js +29 -0
- package/tools/agent-toolset/sync-interval.js.map +1 -0
- package/tools/agent-toolset/sync-interval.mjs +25 -0
- package/tools/agent-toolset/sync-interval.mjs.map +1 -0
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
|
@@ -16,14 +16,82 @@
|
|
|
16
16
|
import { AnthropicError } from '../../core/error';
|
|
17
17
|
import type { Anthropic } from '../../client';
|
|
18
18
|
import type { BetaRunnableTool } from '../../lib/tools/BetaRunnableTool';
|
|
19
|
+
import type { BetaManagedAgentsSession } from '../../resources/beta/sessions/sessions';
|
|
19
20
|
import type { AgentToolContext } from './node';
|
|
21
|
+
import type { SessionMemoryStoresOptions } from './memories';
|
|
20
22
|
|
|
21
23
|
export type { AgentToolContext } from './node';
|
|
24
|
+
export type { MemoryDeleteMode, SessionMemoryStoresOptions } from './memories';
|
|
22
25
|
|
|
23
26
|
function nodeOnly(name: string): never {
|
|
24
27
|
throw new AnthropicError(`${name} requires Node.js or a Node-compatible runtime`);
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
// `./sync-interval` is runtime-agnostic, so these re-export directly.
|
|
31
|
+
export { DEFAULT_MEMORY_SYNC_INTERVAL_MS, MIN_MEMORY_SYNC_INTERVAL_MS } from './sync-interval';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Duplicated literal, not a re-export: importing the value from `./memories`
|
|
35
|
+
* would pull that module's Node built-ins into browser bundles. The stub test
|
|
36
|
+
* pins it to the real module's value.
|
|
37
|
+
*/
|
|
38
|
+
export const MEMORY_FLUSH_TIMEOUT_MS = 30_000;
|
|
39
|
+
export const MARKER_PATH = '.anthropic-memory-store';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Duplicated declaration, for the same reason as the literal above. Nothing in
|
|
43
|
+
* a browser build can throw it — the store download is Node-only — so this
|
|
44
|
+
* exists to keep the stub's export surface identical to the real module's.
|
|
45
|
+
*/
|
|
46
|
+
export class SessionMemoryError extends AnthropicError {
|
|
47
|
+
constructor(message: string, cause?: unknown) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = 'SessionMemoryError';
|
|
50
|
+
// in some environments the 'cause' property is already declared
|
|
51
|
+
// @ts-ignore
|
|
52
|
+
if (cause !== undefined) this.cause = cause;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class SessionMemoryStores {
|
|
57
|
+
constructor(_client: Anthropic, _opts: SessionMemoryStoresOptions) {
|
|
58
|
+
nodeOnly('SessionMemoryStores');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get roots(): string[] {
|
|
62
|
+
return nodeOnly('SessionMemoryStores');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get readOnlyRoots(): string[] {
|
|
66
|
+
return nodeOnly('SessionMemoryStores');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
download(_session: BetaManagedAgentsSession): Promise<void> {
|
|
70
|
+
return nodeOnly('SessionMemoryStores');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
finish(): Promise<void> {
|
|
74
|
+
return nodeOnly('SessionMemoryStores');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @internal */
|
|
78
|
+
syncAll(_final: boolean): Promise<void> {
|
|
79
|
+
return nodeOnly('SessionMemoryStores');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
syncIfDue(): Promise<void> {
|
|
83
|
+
return nodeOnly('SessionMemoryStores');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
flushWrites(_signal?: AbortSignal): Promise<void> {
|
|
87
|
+
return nodeOnly('SessionMemoryStores');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
dispose(): Promise<void> {
|
|
91
|
+
return nodeOnly('SessionMemoryStores');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
27
95
|
export function setupSkills(_ctx: AgentToolContext): Promise<() => Promise<void>> {
|
|
28
96
|
return nodeOnly('setupSkills');
|
|
29
97
|
}
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
* const tools2 = betaAgentToolset20260401({ workdir: '/work' }).filter((t) => t.name !== 'bash');
|
|
25
25
|
* ```
|
|
26
26
|
*
|
|
27
|
-
* Trust model: the file tools confine to `workdir`
|
|
28
|
-
* without a sandbox; `bash` is unrestricted and
|
|
29
|
-
* {@link AgentToolContext}.
|
|
27
|
+
* Trust model: the file tools confine to `workdir` plus any `allowedRoots`
|
|
28
|
+
* (symlink-aware) and are safe without a sandbox; `bash` is unrestricted and
|
|
29
|
+
* should run inside one. See {@link AgentToolContext}.
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import * as fs from 'node:fs/promises';
|
|
@@ -36,14 +36,33 @@ import * as cp from 'node:child_process';
|
|
|
36
36
|
import * as crypto from 'node:crypto';
|
|
37
37
|
import * as readline from 'node:readline';
|
|
38
38
|
import type { Anthropic } from '../../client';
|
|
39
|
+
import type { BetaManagedAgentsSession } from '../../resources/beta/sessions/sessions';
|
|
39
40
|
import { AnthropicError } from '../../core/error';
|
|
40
41
|
import type { BetaRunnableTool } from '../../lib/tools/BetaRunnableTool';
|
|
41
42
|
import { ToolError } from '../../lib/tools/ToolError';
|
|
42
43
|
import { betaTool } from '../../helpers/beta/json-schema';
|
|
43
44
|
import { promiseWithResolvers } from '../../internal/utils/promise';
|
|
44
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
atomicWriteFile,
|
|
47
|
+
canonicalize,
|
|
48
|
+
confineToRoot,
|
|
49
|
+
containingRoot,
|
|
50
|
+
DIR_CREATE_MODE,
|
|
51
|
+
fsErrorMessage,
|
|
52
|
+
isWithin,
|
|
53
|
+
} from './fs-util';
|
|
45
54
|
|
|
46
55
|
export { setupSkills, resolveSkillVersion, extractSkillArchive } from './skills';
|
|
56
|
+
export {
|
|
57
|
+
SessionMemoryStores,
|
|
58
|
+
SessionMemoryError,
|
|
59
|
+
DEFAULT_MEMORY_SYNC_INTERVAL_MS,
|
|
60
|
+
MIN_MEMORY_SYNC_INTERVAL_MS,
|
|
61
|
+
MEMORY_FLUSH_TIMEOUT_MS,
|
|
62
|
+
MARKER_PATH,
|
|
63
|
+
type MemoryDeleteMode,
|
|
64
|
+
type SessionMemoryStoresOptions,
|
|
65
|
+
} from './memories';
|
|
47
66
|
|
|
48
67
|
const BASH_OUTPUT_LIMIT = 100 * 1024;
|
|
49
68
|
const BASH_DEFAULT_TIMEOUT_MS = 120_000;
|
|
@@ -88,6 +107,20 @@ function resolveMaxBytes(configured: number | null | undefined): number | null {
|
|
|
88
107
|
return configured === undefined ? DEFAULT_MAX_FILE_BYTES : configured;
|
|
89
108
|
}
|
|
90
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Throw when the deprecated {@link AgentToolContext.unrestrictedPaths} was
|
|
112
|
+
* passed at all. Nothing else reads that property.
|
|
113
|
+
*/
|
|
114
|
+
function rejectUnrestrictedPaths(value: boolean | undefined): void {
|
|
115
|
+
if (value === undefined) return;
|
|
116
|
+
throw new AnthropicError(
|
|
117
|
+
'The `unrestrictedPaths` option you passed to the agent toolset (AgentToolContext) is no longer ' +
|
|
118
|
+
"supported. The toolset's file tools (read, write, edit, glob, grep) are now always confined to " +
|
|
119
|
+
'the working directory plus the directories listed in `allowedRoots`. Remove `unrestrictedPaths` ' +
|
|
120
|
+
'from your context; to let the file tools reach any other directory, add it to `allowedRoots`.',
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
91
124
|
/**
|
|
92
125
|
* Workdir + path-policy for the agent toolset.
|
|
93
126
|
*
|
|
@@ -95,12 +128,12 @@ function resolveMaxBytes(configured: number | null | undefined): number | null {
|
|
|
95
128
|
*
|
|
96
129
|
* - The file tools ({@link betaReadTool}, {@link betaWriteTool},
|
|
97
130
|
* {@link betaEditTool}, {@link betaGlobTool}, {@link betaGrepTool}) confine to
|
|
98
|
-
* `workdir`
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
131
|
+
* `workdir` plus any `allowedRoots`. {@link resolvePath} canonicalizes the
|
|
132
|
+
* target (resolving every symlink, including the leaf) before the check
|
|
133
|
+
* *and* returns that canonical path for the operation, so a symlink inside
|
|
134
|
+
* the workdir that points outside it neither passes the check nor gets
|
|
135
|
+
* followed afterwards — this is a real boundary, not a lexical hint (modulo
|
|
136
|
+
* the residual TOCTOU noted on {@link resolvePath}).
|
|
104
137
|
* - {@link betaBashTool} runs an unrestricted `/bin/bash` and cannot be
|
|
105
138
|
* confined. Run it — and, for defense in depth, the whole toolset — inside a
|
|
106
139
|
* sandbox the host controls (e.g. a self-hosted environment runner).
|
|
@@ -109,19 +142,48 @@ export interface AgentToolContext {
|
|
|
109
142
|
/** Base directory for resolving relative tool paths. */
|
|
110
143
|
workdir: string;
|
|
111
144
|
/**
|
|
112
|
-
*
|
|
113
|
-
* `workdir`
|
|
145
|
+
* @deprecated No longer accepted: the file tools are always confined to
|
|
146
|
+
* `workdir` plus `allowedRoots`, which is neither behavior this flag used to
|
|
147
|
+
* select, so passing either value throws. Remove it; list extra directories
|
|
148
|
+
* in {@link AgentToolContext.allowedRoots}. The property is removed in a
|
|
149
|
+
* future release.
|
|
114
150
|
*/
|
|
115
151
|
unrestrictedPaths?: boolean;
|
|
152
|
+
/**
|
|
153
|
+
* Absolute directories outside `workdir` the file tools may also reach,
|
|
154
|
+
* resolved at check time. The environment worker sets this to the session's
|
|
155
|
+
* memory-store folders. Does **not** constrain {@link betaBashTool}.
|
|
156
|
+
*/
|
|
157
|
+
allowedRoots?: string[];
|
|
116
158
|
/**
|
|
117
159
|
* Anthropic client. Optional — the bare toolset needs no client; it is only
|
|
118
|
-
* used by `setupSkills`, which (together with {@link AgentToolContext.
|
|
119
|
-
*
|
|
120
|
-
* `{workdir}/skills/<name>/`.
|
|
160
|
+
* used by `setupSkills`, which (together with {@link AgentToolContext.session},
|
|
161
|
+
* or the deprecated {@link AgentToolContext.sessionId}) downloads each of the
|
|
162
|
+
* session agent's skills into `{workdir}/skills/<name>/`.
|
|
121
163
|
*/
|
|
122
164
|
client?: Anthropic;
|
|
123
|
-
/**
|
|
165
|
+
/**
|
|
166
|
+
* The already-fetched session whose agent's skills `setupSkills` downloads.
|
|
167
|
+
* A session's resources cannot change while it runs, so the caller fetches it
|
|
168
|
+
* once and shares that snapshot with the memory-store download; the two can
|
|
169
|
+
* then never disagree about them.
|
|
170
|
+
*/
|
|
171
|
+
session?: BetaManagedAgentsSession;
|
|
172
|
+
/**
|
|
173
|
+
* @deprecated `setupSkills` fetches the session itself when given only an id,
|
|
174
|
+
* one extra round trip per context. Prefer {@link AgentToolContext.session}.
|
|
175
|
+
* Kept for callers written before `session` existed; ignored when `session`
|
|
176
|
+
* is set.
|
|
177
|
+
*/
|
|
124
178
|
sessionId?: string;
|
|
179
|
+
/**
|
|
180
|
+
* Directories the write and edit tools refuse to modify, resolved at check
|
|
181
|
+
* time exactly like `allowedRoots`. The worker sets this to the roots of
|
|
182
|
+
* read-only memory stores so the agent sees the error at write time instead
|
|
183
|
+
* of the change silently never syncing; the mechanism is generic to any
|
|
184
|
+
* directory.
|
|
185
|
+
*/
|
|
186
|
+
readOnlyRoots?: string[];
|
|
125
187
|
/**
|
|
126
188
|
* Optional environment for the bash subprocess. When unset, the bash tool
|
|
127
189
|
* inherits the process environment with the runner's `ANTHROPIC_*`
|
|
@@ -174,13 +236,14 @@ export function betaAgentToolset20260401(ctx: AgentToolContext): BetaRunnableToo
|
|
|
174
236
|
}
|
|
175
237
|
|
|
176
238
|
/**
|
|
177
|
-
* Resolve `p` against `ctx.workdir
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
239
|
+
* Resolve `p` against `ctx.workdir`; reject results outside `ctx.workdir` and
|
|
240
|
+
* `ctx.allowedRoots`. Absolute and relative inputs go through the same
|
|
241
|
+
* canonicalise-then-contain check — an absolute path that lands inside a
|
|
242
|
+
* permitted root is accepted, only paths that resolve *outside* all of them
|
|
243
|
+
* are rejected. Every symlink in `p` (including the leaf, even a dangling one)
|
|
244
|
+
* is resolved before the check, and the resolved path is what the tool then
|
|
245
|
+
* operates on, so a symlink inside the workdir that points outside it can
|
|
246
|
+
* neither pass the check nor be followed afterwards. See the trust model on
|
|
184
247
|
* {@link AgentToolContext}.
|
|
185
248
|
*
|
|
186
249
|
* Residual TOCTOU: a component could still be swapped for a symlink between this
|
|
@@ -189,8 +252,19 @@ export function betaAgentToolset20260401(ctx: AgentToolContext): BetaRunnableToo
|
|
|
189
252
|
* residual exposure exists in `tools/memory/node` and is why a sandbox is still
|
|
190
253
|
* recommended for the toolset as a whole.
|
|
191
254
|
*/
|
|
192
|
-
export function resolvePath(ctx: AgentToolContext, p: string): Promise<string> {
|
|
193
|
-
|
|
255
|
+
export async function resolvePath(ctx: AgentToolContext, p: string): Promise<string> {
|
|
256
|
+
rejectUnrestrictedPaths(ctx.unrestrictedPaths);
|
|
257
|
+
return confineToRoot(ctx.workdir, p, { allowedRoots: ctx.allowedRoots ?? [] });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The read-only root `target` falls under, or `undefined`. `target` arrives
|
|
262
|
+
* fully canonicalized (from {@link resolvePath}), so each root is
|
|
263
|
+
* canonicalized too — a root recorded through a symlinked workdir must still
|
|
264
|
+
* match the resolved write target.
|
|
265
|
+
*/
|
|
266
|
+
function readOnlyRootFor(ctx: AgentToolContext, target: string): Promise<string | undefined> {
|
|
267
|
+
return containingRoot(ctx.readOnlyRoots ?? [], target);
|
|
194
268
|
}
|
|
195
269
|
|
|
196
270
|
// ---- bash ----------------------------------------------------------------
|
|
@@ -363,6 +437,7 @@ export class BashSession {
|
|
|
363
437
|
}
|
|
364
438
|
|
|
365
439
|
export function betaBashTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
440
|
+
rejectUnrestrictedPaths(ctx.unrestrictedPaths);
|
|
366
441
|
let session: BashSession | undefined;
|
|
367
442
|
// Concurrent run() callers chain onto this promise so writes to the shared
|
|
368
443
|
// shell's stdin can't interleave (which would corrupt the sentinel-match
|
|
@@ -431,6 +506,7 @@ export function betaBashTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
|
431
506
|
// ---- fs ------------------------------------------------------------------
|
|
432
507
|
|
|
433
508
|
export function betaReadTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
509
|
+
rejectUnrestrictedPaths(ctx.unrestrictedPaths);
|
|
434
510
|
return betaTool({
|
|
435
511
|
name: 'read',
|
|
436
512
|
description: 'Read a UTF-8 text file relative to the workdir.',
|
|
@@ -482,6 +558,7 @@ export function betaReadTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
|
482
558
|
}
|
|
483
559
|
|
|
484
560
|
export function betaWriteTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
561
|
+
rejectUnrestrictedPaths(ctx.unrestrictedPaths);
|
|
485
562
|
return betaTool({
|
|
486
563
|
name: 'write',
|
|
487
564
|
description: 'Write a UTF-8 text file relative to the workdir, creating parent directories as needed.',
|
|
@@ -493,6 +570,10 @@ export function betaWriteTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
|
493
570
|
run: async ({ file_path, content }) => {
|
|
494
571
|
if (!file_path) throw new ToolError('write: file_path is required');
|
|
495
572
|
const abs = await resolvePath(ctx, file_path);
|
|
573
|
+
const ro = await readOnlyRootFor(ctx, abs);
|
|
574
|
+
if (ro !== undefined) {
|
|
575
|
+
throw new ToolError(`write: ${file_path} is inside read-only directory ${ro}`);
|
|
576
|
+
}
|
|
496
577
|
try {
|
|
497
578
|
await fs.mkdir(path.dirname(abs), { recursive: true, mode: DIR_CREATE_MODE });
|
|
498
579
|
await atomicWriteFile(abs, content ?? '');
|
|
@@ -505,6 +586,7 @@ export function betaWriteTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
|
505
586
|
}
|
|
506
587
|
|
|
507
588
|
export function betaEditTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
589
|
+
rejectUnrestrictedPaths(ctx.unrestrictedPaths);
|
|
508
590
|
return betaTool({
|
|
509
591
|
name: 'edit',
|
|
510
592
|
description:
|
|
@@ -523,6 +605,10 @@ export function betaEditTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
|
523
605
|
if (!file_path) throw new ToolError('edit: file_path is required');
|
|
524
606
|
if (!old_string) throw new ToolError('edit: old_string is required');
|
|
525
607
|
const abs = await resolvePath(ctx, file_path);
|
|
608
|
+
const ro = await readOnlyRootFor(ctx, abs);
|
|
609
|
+
if (ro !== undefined) {
|
|
610
|
+
throw new ToolError(`edit: ${file_path} is inside read-only directory ${ro}`);
|
|
611
|
+
}
|
|
526
612
|
let data: string;
|
|
527
613
|
try {
|
|
528
614
|
// stat() before any open() — same guard as `read`: the size cap stops a
|
|
@@ -570,7 +656,17 @@ export function betaEditTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
|
570
656
|
|
|
571
657
|
// ---- search --------------------------------------------------------------
|
|
572
658
|
|
|
659
|
+
/**
|
|
660
|
+
* Best-effort: stops `fs.glob` from walking out of the root via a literal or
|
|
661
|
+
* brace-expanded `..`. The realpath post-filter in {@link betaGlobTool} is the
|
|
662
|
+
* boundary; this only avoids the walk.
|
|
663
|
+
*/
|
|
664
|
+
function patternCanAscend(pattern: string): boolean {
|
|
665
|
+
return pattern.split(/[\\/{},]/).includes('..');
|
|
666
|
+
}
|
|
667
|
+
|
|
573
668
|
export function betaGlobTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
669
|
+
rejectUnrestrictedPaths(ctx.unrestrictedPaths);
|
|
574
670
|
return betaTool({
|
|
575
671
|
name: 'glob',
|
|
576
672
|
description:
|
|
@@ -585,34 +681,31 @@ export function betaGlobTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
|
585
681
|
},
|
|
586
682
|
run: async ({ pattern, path: searchPath }) => {
|
|
587
683
|
if (!pattern) throw new ToolError('glob: pattern is required');
|
|
588
|
-
let root = path.resolve(ctx.workdir);
|
|
589
|
-
let pat = pattern;
|
|
590
684
|
if (path.isAbsolute(pattern)) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
} else if (searchPath) {
|
|
595
|
-
root = await resolvePath(ctx, searchPath);
|
|
685
|
+
throw new ToolError(
|
|
686
|
+
'glob: absolute pattern not permitted; pass a relative pattern (and optionally path)',
|
|
687
|
+
);
|
|
596
688
|
}
|
|
597
|
-
|
|
598
|
-
// the search root — this is separate from the `searchPath` confinement
|
|
599
|
-
// above, which only covers the path argument. Reject it outright when the
|
|
600
|
-
// toolset is confined.
|
|
601
|
-
if (!ctx.unrestrictedPaths && pat.split(/[\\/]/).includes('..')) {
|
|
689
|
+
if (patternCanAscend(pattern)) {
|
|
602
690
|
throw new ToolError('glob: ".." is not permitted in the pattern');
|
|
603
691
|
}
|
|
692
|
+
const root = searchPath ? await resolvePath(ctx, searchPath) : path.resolve(ctx.workdir);
|
|
604
693
|
// Compare canonical against canonical: a workdir that is itself a
|
|
605
694
|
// symlink would otherwise falsely reject every realpath'd match below.
|
|
606
|
-
const realRoot =
|
|
695
|
+
const realRoot = searchPath ? root : await canonicalize(root);
|
|
607
696
|
const matches: { path: string; mtime: number }[] = [];
|
|
697
|
+
// Bounds the walk for patterns that match, or brace-expand into,
|
|
698
|
+
// enormous trees.
|
|
699
|
+
let remaining = WALK_MAX_ENTRIES;
|
|
608
700
|
try {
|
|
609
701
|
// Native `fs.glob` (Node 22+). `exclude` prunes the noisy dirs the
|
|
610
702
|
// legacy walker skipped; only regular files are collected.
|
|
611
|
-
for await (const entry of fsGlob(
|
|
703
|
+
for await (const entry of fsGlob(pattern, {
|
|
612
704
|
cwd: root,
|
|
613
705
|
withFileTypes: true,
|
|
614
706
|
exclude: (d) => d.name === '.git' || d.name === 'node_modules',
|
|
615
707
|
})) {
|
|
708
|
+
if (remaining-- <= 0) break;
|
|
616
709
|
if (!entry.isFile()) continue;
|
|
617
710
|
const full = path.join(entry.parentPath, entry.name);
|
|
618
711
|
// Drop any match that resolves outside the search root. A pattern
|
|
@@ -622,15 +715,13 @@ export function betaGlobTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
|
622
715
|
// the raw parent path, so a lexical check on `full` alone would
|
|
623
716
|
// pass `root/link_out/secret` even though it lives outside the
|
|
624
717
|
// jail. Resolve-failure (ELOOP, EACCES, a racing unlink) is a deny.
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
continue;
|
|
631
|
-
}
|
|
632
|
-
if (!isWithin(realRoot, real)) continue;
|
|
718
|
+
let real: string;
|
|
719
|
+
try {
|
|
720
|
+
real = await fs.realpath(full);
|
|
721
|
+
} catch {
|
|
722
|
+
continue;
|
|
633
723
|
}
|
|
724
|
+
if (!isWithin(realRoot, real)) continue;
|
|
634
725
|
let mtime = 0;
|
|
635
726
|
try {
|
|
636
727
|
mtime = (await fs.stat(full)).mtimeMs;
|
|
@@ -653,6 +744,7 @@ export function betaGlobTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
|
653
744
|
}
|
|
654
745
|
|
|
655
746
|
export function betaGrepTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
747
|
+
rejectUnrestrictedPaths(ctx.unrestrictedPaths);
|
|
656
748
|
return betaTool({
|
|
657
749
|
name: 'grep',
|
|
658
750
|
description: 'Search file contents for a regex. Uses ripgrep if available, otherwise a built-in walker.',
|
|
@@ -765,12 +857,6 @@ async function grepFile(file: string, re: RegExp, push: (line: string) => boolea
|
|
|
765
857
|
|
|
766
858
|
// ---- utils ---------------------------------------------------------------
|
|
767
859
|
|
|
768
|
-
/** True when `p` is `root` itself or lexically contained within it. */
|
|
769
|
-
function isWithin(root: string, p: string): boolean {
|
|
770
|
-
const rel = path.relative(root, p);
|
|
771
|
-
return rel === '' || (!rel.startsWith('..' + path.sep) && rel !== '..' && !path.isAbsolute(rel));
|
|
772
|
-
}
|
|
773
|
-
|
|
774
860
|
const WALK_MAX_DEPTH = 40;
|
|
775
861
|
const WALK_MAX_ENTRIES = 50_000;
|
|
776
862
|
|
|
@@ -23,13 +23,22 @@ const execFileAsync = promisify(execFile);
|
|
|
23
23
|
/**
|
|
24
24
|
* Download the session agent's skills into `{ctx.workdir}/skills/<name>/`.
|
|
25
25
|
*
|
|
26
|
-
* No-op (returns a no-op cleanup) unless
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* ready).
|
|
26
|
+
* No-op (returns a no-op cleanup) unless `ctx.client` is set together with
|
|
27
|
+
* `ctx.session` (or the deprecated `ctx.sessionId`). Reads the resolved agent
|
|
28
|
+
* off the session and, for each skill, fetches its files via
|
|
29
|
+
* `client.beta.skills.versions.download` and extracts the archive (a zip or
|
|
30
|
+
* tar.* archive) into a directory named after the skill. A failure on one skill
|
|
31
|
+
* is logged and does not block the others. Call this before starting the
|
|
32
|
+
* session tool runner (e.g. right after the bash session / workdir is ready).
|
|
33
|
+
*
|
|
34
|
+
* Pass `ctx.session`. A session's resources cannot change while it runs, so the
|
|
35
|
+
* caller fetches it once and shares that snapshot with the memory-store
|
|
36
|
+
* download — the two can then never disagree about the attached resources.
|
|
37
|
+
*
|
|
38
|
+
* `ctx.sessionId` is deprecated: it costs an extra `sessions.retrieve` round
|
|
39
|
+
* trip on every call, and a caller that uses it for both this and the
|
|
40
|
+
* memory-store download fetches the session twice. It remains supported for
|
|
41
|
+
* callers written before `session` existed.
|
|
33
42
|
*
|
|
34
43
|
* Returns a cleanup function that removes the skill directories this call
|
|
35
44
|
* created — call it once the work item is done so downloaded skills do not
|
|
@@ -37,9 +46,20 @@ const execFileAsync = promisify(execFile);
|
|
|
37
46
|
*/
|
|
38
47
|
export async function setupSkills(ctx: AgentToolContext): Promise<() => Promise<void>> {
|
|
39
48
|
const { client, sessionId } = ctx;
|
|
40
|
-
if (!client
|
|
49
|
+
if (!client) return async () => {};
|
|
41
50
|
const log = loggerFor(client);
|
|
42
|
-
|
|
51
|
+
let session = ctx.session;
|
|
52
|
+
if (!session) {
|
|
53
|
+
if (sessionId === undefined) return async () => {};
|
|
54
|
+
log.warn(
|
|
55
|
+
'AgentToolContext.sessionId is deprecated and costs an extra session fetch; ' +
|
|
56
|
+
'fetch the session once and set `session` instead',
|
|
57
|
+
{ component: 'agent-tool-context' },
|
|
58
|
+
);
|
|
59
|
+
// The sessions/skills resources inject their anthropic-beta headers
|
|
60
|
+
// (managed-agents / skills) themselves — no need to pass `betas` here.
|
|
61
|
+
session = await client.beta.sessions.retrieve(sessionId);
|
|
62
|
+
}
|
|
43
63
|
const skillsRoot = path.resolve(ctx.workdir, 'skills');
|
|
44
64
|
const created: string[] = [];
|
|
45
65
|
for (const skill of session.agent.skills) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memory sync cadence, split out of `memories.ts` so runtime-agnostic
|
|
3
|
+
* callers (the environment worker) can validate an interval up front without
|
|
4
|
+
* reaching into the Node-only toolset module.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AnthropicError } from '../../core/error';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* How often (milliseconds) the worker syncs the session's memory stores back
|
|
11
|
+
* while the session runs. Checked after each dispatched tool call.
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_MEMORY_SYNC_INTERVAL_MS = 15_000;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The shortest sync interval accepted. Each sync lists every attached store,
|
|
17
|
+
* so anything tighter mostly spends requests rediscovering that nothing
|
|
18
|
+
* changed.
|
|
19
|
+
*/
|
|
20
|
+
export const MIN_MEMORY_SYNC_INTERVAL_MS = 5_000;
|
|
21
|
+
|
|
22
|
+
/** Throw unless `ms` is a usable sync interval. `option` names it in the message. */
|
|
23
|
+
export function checkMemorySyncInterval(ms: number, option: string): void {
|
|
24
|
+
if (!(ms >= MIN_MEMORY_SYNC_INTERVAL_MS)) {
|
|
25
|
+
throw new AnthropicError(
|
|
26
|
+
`${option} must be at least ${MIN_MEMORY_SYNC_INTERVAL_MS}ms (got ${ms}); ` +
|
|
27
|
+
'to run without memory sync, pass `memorySyncIntervalMs: null` to the worker instead',
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.120.0'; // x-release-please-version
|
|
@@ -8,6 +8,17 @@
|
|
|
8
8
|
export declare const DIR_CREATE_MODE = 493;
|
|
9
9
|
/** Mode for files the file tools create. */
|
|
10
10
|
export declare const FILE_CREATE_MODE = 420;
|
|
11
|
+
/** True when `p` is `root` itself or lexically contained within it. */
|
|
12
|
+
export declare function isWithin(root: string, p: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The first entry of `roots` whose canonical form contains the
|
|
15
|
+
* already-canonical `target`, returned as configured; `undefined` when none
|
|
16
|
+
* does. Each root goes through {@link canonicalize} at check time, exactly like
|
|
17
|
+
* the workdir in {@link confineToRoot}, so granting access (`allowedRoots`)
|
|
18
|
+
* and refusing writes (`readOnlyRoots`) can never resolve the same entry two
|
|
19
|
+
* different ways.
|
|
20
|
+
*/
|
|
21
|
+
export declare function containingRoot(roots: readonly string[], target: string): Promise<string | undefined>;
|
|
11
22
|
/** The `code` of a Node system error, or `undefined` for anything else. */
|
|
12
23
|
export declare function errnoCode(err: unknown): string | undefined;
|
|
13
24
|
/**
|
|
@@ -25,17 +36,19 @@ export declare function errnoCode(err: unknown): string | undefined;
|
|
|
25
36
|
*/
|
|
26
37
|
export declare function canonicalize(abs: string): Promise<string>;
|
|
27
38
|
/**
|
|
28
|
-
* Resolve `p` and confine it to `root
|
|
39
|
+
* Resolve `p` against `root` and confine it to `root` or one of `allowedRoots`
|
|
40
|
+
* (absolute paths, resolved at check time exactly like `root`).
|
|
29
41
|
*
|
|
30
42
|
* Absolute and relative inputs go through the same canonicalise-then-contain
|
|
31
|
-
* check — an absolute path that lands inside
|
|
32
|
-
* that resolve *outside* are rejected. Every symlink in
|
|
33
|
-
* leaf, even a dangling one) is resolved before the
|
|
34
|
-
* resolved path is what the caller then operates
|
|
35
|
-
* that points outside it can neither pass the
|
|
36
|
-
* `..` is collapsed lexically before any
|
|
37
|
-
* be resolved (symlink loop, unreadable
|
|
38
|
-
* `ToolError` naming `p`, never the host's
|
|
43
|
+
* check — an absolute path that lands inside a permitted root is accepted,
|
|
44
|
+
* only paths that resolve *outside* all of them are rejected. Every symlink in
|
|
45
|
+
* `p` (including the leaf, even a dangling one) is resolved before the
|
|
46
|
+
* confinement check, and the resolved path is what the caller then operates
|
|
47
|
+
* on, so a symlink inside `root` that points outside it can neither pass the
|
|
48
|
+
* check nor be followed afterwards. `..` is collapsed lexically before any
|
|
49
|
+
* symlink is followed. A path that cannot be resolved (symlink loop, unreadable
|
|
50
|
+
* component) is rejected with a `ToolError` naming `p`, never the host's
|
|
51
|
+
* absolute path.
|
|
39
52
|
*
|
|
40
53
|
* Residual TOCTOU: a component could still be swapped for a symlink between this
|
|
41
54
|
* call and the eventual `fs` operation. Closing that fully needs per-component
|
|
@@ -43,7 +56,7 @@ export declare function canonicalize(abs: string): Promise<string>;
|
|
|
43
56
|
* sandbox is still recommended for the toolset as a whole.
|
|
44
57
|
*/
|
|
45
58
|
export declare function confineToRoot(root: string, p: string, opts?: {
|
|
46
|
-
|
|
59
|
+
allowedRoots?: readonly string[];
|
|
47
60
|
}): Promise<string>;
|
|
48
61
|
/**
|
|
49
62
|
* Atomically write `content` to `targetPath`: write a sibling temp file, fsync
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs-util.d.mts","sourceRoot":"","sources":["../../src/tools/agent-toolset/fs-util.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,uFAAuF;AACvF,eAAO,MAAM,eAAe,MAAQ,CAAC;AACrC,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,MAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"fs-util.d.mts","sourceRoot":"","sources":["../../src/tools/agent-toolset/fs-util.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,uFAAuF;AACvF,eAAO,MAAM,eAAe,MAAQ,CAAC;AACrC,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,MAAQ,CAAC;AAEtC,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAK1G;AAKD,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B/D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC1C,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBxF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAwBjE"}
|
|
@@ -8,6 +8,17 @@
|
|
|
8
8
|
export declare const DIR_CREATE_MODE = 493;
|
|
9
9
|
/** Mode for files the file tools create. */
|
|
10
10
|
export declare const FILE_CREATE_MODE = 420;
|
|
11
|
+
/** True when `p` is `root` itself or lexically contained within it. */
|
|
12
|
+
export declare function isWithin(root: string, p: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The first entry of `roots` whose canonical form contains the
|
|
15
|
+
* already-canonical `target`, returned as configured; `undefined` when none
|
|
16
|
+
* does. Each root goes through {@link canonicalize} at check time, exactly like
|
|
17
|
+
* the workdir in {@link confineToRoot}, so granting access (`allowedRoots`)
|
|
18
|
+
* and refusing writes (`readOnlyRoots`) can never resolve the same entry two
|
|
19
|
+
* different ways.
|
|
20
|
+
*/
|
|
21
|
+
export declare function containingRoot(roots: readonly string[], target: string): Promise<string | undefined>;
|
|
11
22
|
/** The `code` of a Node system error, or `undefined` for anything else. */
|
|
12
23
|
export declare function errnoCode(err: unknown): string | undefined;
|
|
13
24
|
/**
|
|
@@ -25,17 +36,19 @@ export declare function errnoCode(err: unknown): string | undefined;
|
|
|
25
36
|
*/
|
|
26
37
|
export declare function canonicalize(abs: string): Promise<string>;
|
|
27
38
|
/**
|
|
28
|
-
* Resolve `p` and confine it to `root
|
|
39
|
+
* Resolve `p` against `root` and confine it to `root` or one of `allowedRoots`
|
|
40
|
+
* (absolute paths, resolved at check time exactly like `root`).
|
|
29
41
|
*
|
|
30
42
|
* Absolute and relative inputs go through the same canonicalise-then-contain
|
|
31
|
-
* check — an absolute path that lands inside
|
|
32
|
-
* that resolve *outside* are rejected. Every symlink in
|
|
33
|
-
* leaf, even a dangling one) is resolved before the
|
|
34
|
-
* resolved path is what the caller then operates
|
|
35
|
-
* that points outside it can neither pass the
|
|
36
|
-
* `..` is collapsed lexically before any
|
|
37
|
-
* be resolved (symlink loop, unreadable
|
|
38
|
-
* `ToolError` naming `p`, never the host's
|
|
43
|
+
* check — an absolute path that lands inside a permitted root is accepted,
|
|
44
|
+
* only paths that resolve *outside* all of them are rejected. Every symlink in
|
|
45
|
+
* `p` (including the leaf, even a dangling one) is resolved before the
|
|
46
|
+
* confinement check, and the resolved path is what the caller then operates
|
|
47
|
+
* on, so a symlink inside `root` that points outside it can neither pass the
|
|
48
|
+
* check nor be followed afterwards. `..` is collapsed lexically before any
|
|
49
|
+
* symlink is followed. A path that cannot be resolved (symlink loop, unreadable
|
|
50
|
+
* component) is rejected with a `ToolError` naming `p`, never the host's
|
|
51
|
+
* absolute path.
|
|
39
52
|
*
|
|
40
53
|
* Residual TOCTOU: a component could still be swapped for a symlink between this
|
|
41
54
|
* call and the eventual `fs` operation. Closing that fully needs per-component
|
|
@@ -43,7 +56,7 @@ export declare function canonicalize(abs: string): Promise<string>;
|
|
|
43
56
|
* sandbox is still recommended for the toolset as a whole.
|
|
44
57
|
*/
|
|
45
58
|
export declare function confineToRoot(root: string, p: string, opts?: {
|
|
46
|
-
|
|
59
|
+
allowedRoots?: readonly string[];
|
|
47
60
|
}): Promise<string>;
|
|
48
61
|
/**
|
|
49
62
|
* Atomically write `content` to `targetPath`: write a sibling temp file, fsync
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs-util.d.ts","sourceRoot":"","sources":["../../src/tools/agent-toolset/fs-util.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,uFAAuF;AACvF,eAAO,MAAM,eAAe,MAAQ,CAAC;AACrC,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,MAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"fs-util.d.ts","sourceRoot":"","sources":["../../src/tools/agent-toolset/fs-util.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,uFAAuF;AACvF,eAAO,MAAM,eAAe,MAAQ,CAAC;AACrC,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,MAAQ,CAAC;AAEtC,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAK1G;AAKD,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B/D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC1C,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBxF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAwBjE"}
|