@absolutejs/agent-sandbox 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AbsoluteJS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # @absolutejs/agent-sandbox
2
+
3
+ Deny-by-default capability sandboxing for agent actions. Grants are scoped to
4
+ one durable run and one signed agent identity, expire, carry per-capability use
5
+ limits, and are verified through an issuer-agnostic callback.
6
+
7
+ HTTP access is restricted by origin, method, path, body/response size, and
8
+ credential alias. Filesystem access uses boundary-safe absolute roots. Process
9
+ access uses exact executable allowlists, argument prefixes, working roots,
10
+ timeouts, and output limits. Credentials are resolved inside adapters and never
11
+ returned to the agent.
12
+
13
+ The broker persists an operation before executing it. Repeating the same
14
+ `grantId + requestId` returns the recorded result instead of repeating the
15
+ external action. Use the Postgres store in production or the memory store in
16
+ tests. The built-in fetch adapter rejects redirects so an allowed origin cannot
17
+ bounce an agent to an ungranted destination.
18
+
19
+ An adapter failure is deliberately left locked for reconciliation: the remote
20
+ system may have accepted the effect before the error reached the broker, so an
21
+ automatic retry would risk duplication. Filesystem adapters must resolve
22
+ symlinks/real paths before access; process adapters must enforce the grant's
23
+ timeout and output ceilings at the operating-system boundary.
@@ -0,0 +1,6 @@
1
+ import type { AgentSandboxAdapter } from "./types";
2
+ export type AgentSandboxFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
3
+ /** Fetch adapter with manual redirects so a granted origin cannot redirect elsewhere. */
4
+ export declare const createFetchAgentSandboxAdapter: ({ fetch: fetcher, }?: {
5
+ fetch?: AgentSandboxFetch;
6
+ }) => AgentSandboxAdapter;
@@ -0,0 +1,12 @@
1
+ import type { AgentCapabilityGrant, AgentSandbox, AgentSandboxAction, AgentSandboxAdapter, AgentSandboxOperationStore } from "./types";
2
+ export declare const createAgentSandbox: ({ store, adapters, verifyGrant, resolveCredential, now, id, }: {
3
+ store: AgentSandboxOperationStore;
4
+ adapters: Partial<Record<AgentSandboxAction["kind"], AgentSandboxAdapter>>;
5
+ verifyGrant: (grant: AgentCapabilityGrant) => boolean | Promise<boolean>;
6
+ resolveCredential: (input: {
7
+ alias: string;
8
+ grant: AgentCapabilityGrant;
9
+ }) => Promise<string>;
10
+ now?: () => number;
11
+ id?: () => string;
12
+ }) => AgentSandbox;
@@ -0,0 +1,6 @@
1
+ export * from "./types";
2
+ export * from "./policy";
3
+ export * from "./memory";
4
+ export * from "./postgres";
5
+ export * from "./broker";
6
+ export * from "./adapters";
package/dist/index.js ADDED
@@ -0,0 +1,273 @@
1
+ // @bun
2
+ // src/policy.ts
3
+ import { isAbsolute, normalize, relative, resolve } from "path";
4
+ var byteLength = (value) => value === undefined ? 0 : typeof value === "string" ? new TextEncoder().encode(value).byteLength : value.byteLength;
5
+ var within = (root, requested) => {
6
+ if (!isAbsolute(root) || !isAbsolute(requested))
7
+ return false;
8
+ const remainder = relative(resolve(root), resolve(normalize(requested)));
9
+ return remainder === "" || !remainder.startsWith("..") && !isAbsolute(remainder);
10
+ };
11
+ var originMatches = (allowed, url) => {
12
+ const candidate = new URL(allowed.replace("*.", "placeholder."));
13
+ if (candidate.protocol !== url.protocol || candidate.port !== url.port)
14
+ return false;
15
+ if (!allowed.includes("*."))
16
+ return candidate.hostname === url.hostname;
17
+ const suffix = candidate.hostname.slice("placeholder".length);
18
+ return url.hostname.endsWith(suffix) && url.hostname.length > suffix.length;
19
+ };
20
+ var checkCredentials = (allowed, requested) => {
21
+ for (const alias of Object.values(requested ?? {})) {
22
+ if (!allowed?.includes(alias))
23
+ throw new Error(`Credential alias is not granted: ${alias}`);
24
+ }
25
+ };
26
+ var authorizeHttp = (capability, action) => {
27
+ const url = new URL(action.url);
28
+ if (url.username || url.password)
29
+ throw new Error("HTTP URL credentials are denied");
30
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "::1"].includes(url.hostname)))
31
+ throw new Error("HTTP capability requires HTTPS except on loopback");
32
+ if (!capability.origins.some((origin) => originMatches(origin, url)))
33
+ throw new Error("HTTP origin is not granted");
34
+ if (capability.methods && !capability.methods.map((value) => value.toUpperCase()).includes(action.method.toUpperCase()))
35
+ throw new Error("HTTP method is not granted");
36
+ if (capability.pathPrefixes && !capability.pathPrefixes.some((prefix) => decodeURIComponent(url.pathname) === prefix || decodeURIComponent(url.pathname).startsWith(prefix.endsWith("/") ? prefix : `${prefix}/`)))
37
+ throw new Error("HTTP path is not granted");
38
+ const forbiddenHeaders = new Set([
39
+ "authorization",
40
+ "proxy-authorization",
41
+ "cookie",
42
+ "host",
43
+ "connection",
44
+ "content-length",
45
+ "transfer-encoding"
46
+ ]);
47
+ for (const name of Object.keys(action.headers ?? {})) {
48
+ if (forbiddenHeaders.has(name.toLowerCase()))
49
+ throw new Error(`Sensitive HTTP header must use a credential alias: ${name}`);
50
+ }
51
+ if (byteLength(action.body) > (capability.maxRequestBytes ?? 1048576))
52
+ throw new Error("HTTP request body exceeds grant");
53
+ checkCredentials(capability.credentialAliases, action.credentials);
54
+ };
55
+ var authorizeFilesystem = (capability, action) => {
56
+ const root = capability.roots.find((entry) => within(entry.path, action.path) && (entry.access === "read-write" || entry.access === action.operation));
57
+ if (!root)
58
+ throw new Error("Filesystem path or operation is not granted");
59
+ if (action.operation === "write" && byteLength(action.data) > (capability.maxWriteBytes ?? 1048576))
60
+ throw new Error("Filesystem write exceeds grant");
61
+ };
62
+ var startsWithArgs = (args, prefix) => prefix.every((value, index) => args[index] === value);
63
+ var authorizeProcess = (capability, action) => {
64
+ if (!capability.executables.includes(action.executable))
65
+ throw new Error("Executable is not granted");
66
+ if (capability.argumentPrefixes && !capability.argumentPrefixes.some((prefix) => startsWithArgs(action.args, prefix)))
67
+ throw new Error("Process arguments are not granted");
68
+ if (action.cwd && !capability.workingDirectories?.some((root) => within(root, action.cwd)))
69
+ throw new Error("Working directory is not granted");
70
+ checkCredentials(capability.credentialAliases, action.credentials);
71
+ };
72
+ var authorizeAgentSandboxAction = (capability, action) => {
73
+ if (capability.kind !== action.kind)
74
+ throw new Error("Capability kind does not match action");
75
+ if (capability.kind === "http" && action.kind === "http")
76
+ return authorizeHttp(capability, action);
77
+ if (capability.kind === "filesystem" && action.kind === "filesystem")
78
+ return authorizeFilesystem(capability, action);
79
+ if (capability.kind === "process" && action.kind === "process")
80
+ return authorizeProcess(capability, action);
81
+ throw new Error("Unsupported sandbox action");
82
+ };
83
+ // src/memory.ts
84
+ var createMemoryAgentSandboxOperationStore = () => {
85
+ const operations = new Map;
86
+ const uses = new Map;
87
+ return {
88
+ begin: async ({ grantId, capabilityId, requestId, maxUses }) => {
89
+ const key = `${grantId}:${requestId}`;
90
+ const existing = operations.get(key);
91
+ if (existing?.state === "completed")
92
+ return {
93
+ state: "completed",
94
+ result: structuredClone(existing.result)
95
+ };
96
+ if (existing)
97
+ return { state: "in_progress" };
98
+ const usageKey = `${grantId}:${capabilityId}`;
99
+ const used = uses.get(usageKey) ?? 0;
100
+ if (maxUses !== undefined && used >= maxUses)
101
+ return { state: "limit_exceeded" };
102
+ uses.set(usageKey, used + 1);
103
+ operations.set(key, { state: "in_progress" });
104
+ return { state: "acquired" };
105
+ },
106
+ complete: async ({ grantId, requestId, result }) => {
107
+ const key = `${grantId}:${requestId}`;
108
+ if (operations.get(key)?.state !== "in_progress")
109
+ return false;
110
+ operations.set(key, {
111
+ state: "completed",
112
+ result: structuredClone(result)
113
+ });
114
+ return true;
115
+ },
116
+ fail: async () => {}
117
+ };
118
+ };
119
+ // src/postgres.ts
120
+ var safe = (value) => {
121
+ if (!/^[a-z_][a-z0-9_]*$/i.test(value))
122
+ throw new Error("Invalid SQL namespace");
123
+ return value;
124
+ };
125
+ var agentSandboxPostgresSchemaSql = (schema = "agent_sandbox") => {
126
+ const ns = safe(schema);
127
+ return `CREATE SCHEMA IF NOT EXISTS ${ns};
128
+ CREATE TABLE IF NOT EXISTS ${ns}.operations (grant_id text NOT NULL, request_id text NOT NULL, capability_id text NOT NULL, state text NOT NULL, result jsonb, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, PRIMARY KEY (grant_id, request_id));
129
+ CREATE TABLE IF NOT EXISTS ${ns}.capability_usage (grant_id text NOT NULL, capability_id text NOT NULL, uses integer NOT NULL, PRIMARY KEY (grant_id, capability_id));`;
130
+ };
131
+ var createPostgresAgentSandboxOperationStore = ({
132
+ client,
133
+ schema = "agent_sandbox"
134
+ }) => {
135
+ const ns = safe(schema);
136
+ return {
137
+ begin: (input) => client.transaction(async (tx) => {
138
+ const existing = (await tx.query(`SELECT state,result FROM ${ns}.operations WHERE grant_id=$1 AND request_id=$2 FOR UPDATE`, [input.grantId, input.requestId])).rows[0];
139
+ if (existing?.state === "completed" && existing.result)
140
+ return { state: "completed", result: existing.result };
141
+ if (existing)
142
+ return { state: "in_progress" };
143
+ await tx.query(`INSERT INTO ${ns}.capability_usage (grant_id,capability_id,uses) VALUES ($1,$2,0) ON CONFLICT DO NOTHING`, [input.grantId, input.capabilityId]);
144
+ const usage = (await tx.query(`SELECT uses FROM ${ns}.capability_usage WHERE grant_id=$1 AND capability_id=$2 FOR UPDATE`, [input.grantId, input.capabilityId])).rows[0]?.uses ?? 0;
145
+ if (input.maxUses !== undefined && usage >= input.maxUses)
146
+ return { state: "limit_exceeded" };
147
+ await tx.query(`UPDATE ${ns}.capability_usage SET uses=uses+1 WHERE grant_id=$1 AND capability_id=$2`, [input.grantId, input.capabilityId]);
148
+ await tx.query(`INSERT INTO ${ns}.operations (grant_id,request_id,capability_id,state,created_at,updated_at) VALUES ($1,$2,$3,'in_progress',$4::timestamptz,$4::timestamptz)`, [input.grantId, input.requestId, input.capabilityId, input.now]);
149
+ return { state: "acquired" };
150
+ }),
151
+ complete: async (input) => (await client.query(`UPDATE ${ns}.operations SET state='completed',result=$3::jsonb,updated_at=$4::timestamptz WHERE grant_id=$1 AND request_id=$2 AND state='in_progress' RETURNING request_id`, [
152
+ input.grantId,
153
+ input.requestId,
154
+ JSON.stringify(input.result),
155
+ input.now
156
+ ])).rows.length === 1,
157
+ fail: async (input) => {
158
+ await client.query(`UPDATE ${ns}.operations SET state='failed' WHERE grant_id=$1 AND request_id=$2 AND state='in_progress'`, [input.grantId, input.requestId]);
159
+ }
160
+ };
161
+ };
162
+ // src/broker.ts
163
+ var stable = (value) => JSON.stringify(value, (_key, item) => item && typeof item === "object" && !Array.isArray(item) ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a.localeCompare(b))) : item);
164
+ var digest = async (value) => `sha256:${Buffer.from(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(stable(value)))).toString("hex")}`;
165
+ var createAgentSandbox = ({
166
+ store,
167
+ adapters,
168
+ verifyGrant,
169
+ resolveCredential,
170
+ now = Date.now,
171
+ id = () => crypto.randomUUID()
172
+ }) => ({
173
+ execute: async ({ grant, action }) => {
174
+ if (!await verifyGrant(grant))
175
+ throw new Error("Capability grant proof is invalid");
176
+ const timestamp = now();
177
+ if (!Number.isFinite(Date.parse(grant.expiresAt)) || Date.parse(grant.expiresAt) <= timestamp)
178
+ throw new Error("Capability grant is expired");
179
+ const capability = grant.capabilities.find((value) => value.id === action.capabilityId);
180
+ if (!capability)
181
+ throw new Error("Capability is not granted");
182
+ authorizeAgentSandboxAction(capability, action);
183
+ const adapter = adapters[action.kind];
184
+ if (!adapter)
185
+ throw new Error(`No sandbox adapter for ${action.kind}`);
186
+ const requestDigest = await digest(action);
187
+ const begun = await store.begin({
188
+ grantId: grant.id,
189
+ capabilityId: capability.id,
190
+ requestId: action.requestId,
191
+ maxUses: capability.maxUses,
192
+ now: new Date(timestamp).toISOString()
193
+ });
194
+ if (begun.state === "completed")
195
+ return begun.result;
196
+ if (begun.state === "in_progress")
197
+ throw new Error("Sandbox action is already in progress");
198
+ if (begun.state === "limit_exceeded")
199
+ throw new Error("Capability use limit exceeded");
200
+ try {
201
+ const output = await adapter.execute({
202
+ grant,
203
+ capability,
204
+ action,
205
+ resolveCredential: (alias) => resolveCredential({ alias, grant })
206
+ });
207
+ const receipt = {
208
+ id: id(),
209
+ grantId: grant.id,
210
+ runId: grant.runId,
211
+ requestId: action.requestId,
212
+ capabilityId: capability.id,
213
+ kind: action.kind,
214
+ requestDigest,
215
+ outputDigest: await digest(output),
216
+ startedAt: new Date(timestamp).toISOString(),
217
+ completedAt: new Date(now()).toISOString()
218
+ };
219
+ const result = { output, receipt };
220
+ if (!await store.complete({
221
+ grantId: grant.id,
222
+ requestId: action.requestId,
223
+ result,
224
+ now: receipt.completedAt
225
+ }))
226
+ throw new Error("Sandbox operation completion was lost");
227
+ return result;
228
+ } catch (error) {
229
+ await store.fail({ grantId: grant.id, requestId: action.requestId });
230
+ throw error;
231
+ }
232
+ }
233
+ });
234
+ // src/adapters.ts
235
+ var readLimited = async (response, limit) => {
236
+ const bytes = new Uint8Array(await response.arrayBuffer());
237
+ if (bytes.byteLength > limit)
238
+ throw new Error("HTTP response exceeds grant");
239
+ return new TextDecoder().decode(bytes);
240
+ };
241
+ var createFetchAgentSandboxAdapter = ({
242
+ fetch: fetcher = fetch
243
+ } = {}) => ({
244
+ execute: async ({ capability, action, resolveCredential }) => {
245
+ if (capability.kind !== "http" || action.kind !== "http")
246
+ throw new Error("HTTP adapter received another action kind");
247
+ const headers = new Headers(action.headers);
248
+ for (const [header, alias] of Object.entries(action.credentials ?? {}))
249
+ headers.set(header, await resolveCredential(alias));
250
+ const response = await fetcher(action.url, {
251
+ method: action.method,
252
+ headers,
253
+ body: action.body,
254
+ redirect: "manual"
255
+ });
256
+ if (response.status >= 300 && response.status < 400)
257
+ throw new Error("HTTP redirects are denied by the sandbox adapter");
258
+ const body = await readLimited(response, capability.maxResponseBytes ?? 1048576);
259
+ return {
260
+ status: response.status,
261
+ headers: Object.fromEntries(response.headers),
262
+ body
263
+ };
264
+ }
265
+ });
266
+ export {
267
+ createPostgresAgentSandboxOperationStore,
268
+ createMemoryAgentSandboxOperationStore,
269
+ createFetchAgentSandboxAdapter,
270
+ createAgentSandbox,
271
+ authorizeAgentSandboxAction,
272
+ agentSandboxPostgresSchemaSql
273
+ };
@@ -0,0 +1 @@
1
+ export declare const manifest: import("@absolutejs/manifest").PackageManifest<unknown, never>;
@@ -0,0 +1,23 @@
1
+ // @bun
2
+ // src/manifest.ts
3
+ import { defineManifest } from "@absolutejs/manifest";
4
+ import { Type } from "@sinclair/typebox";
5
+ var manifest = defineManifest()({
6
+ contract: 2,
7
+ identity: {
8
+ name: "@absolutejs/agent-sandbox",
9
+ category: "security",
10
+ tagline: "Give agents narrow capabilities, never ambient authority.",
11
+ description: "Deny-by-default, expiring capability grants and idempotent provider-neutral action adapters for HTTP, filesystem, process, and credential-safe agent actions.",
12
+ docsUrl: "https://github.com/absolutejs/agent-sandbox",
13
+ accent: "#ef4444"
14
+ },
15
+ settings: Type.Object({}),
16
+ slots: {},
17
+ implements: [],
18
+ tools: {},
19
+ wiring: []
20
+ });
21
+ export {
22
+ manifest
23
+ };
@@ -0,0 +1,19 @@
1
+ {
2
+ "contract": 2,
3
+ "identity": {
4
+ "name": "@absolutejs/agent-sandbox",
5
+ "category": "security",
6
+ "tagline": "Give agents narrow capabilities, never ambient authority.",
7
+ "description": "Deny-by-default, expiring capability grants and idempotent provider-neutral action adapters for HTTP, filesystem, process, and credential-safe agent actions.",
8
+ "docsUrl": "https://github.com/absolutejs/agent-sandbox",
9
+ "accent": "#ef4444"
10
+ },
11
+ "settings": {
12
+ "type": "object",
13
+ "properties": {}
14
+ },
15
+ "slots": {},
16
+ "implements": [],
17
+ "wiring": [],
18
+ "tools": {}
19
+ }
@@ -0,0 +1,2 @@
1
+ import type { AgentSandboxOperationStore } from "./types";
2
+ export declare const createMemoryAgentSandboxOperationStore: () => AgentSandboxOperationStore;
@@ -0,0 +1,2 @@
1
+ import type { AgentCapability, AgentSandboxAction } from "./types";
2
+ export declare const authorizeAgentSandboxAction: (capability: AgentCapability, action: AgentSandboxAction) => void;
@@ -0,0 +1,15 @@
1
+ import type { AgentSandboxOperationStore } from "./types";
2
+ export type AgentSandboxSqlResult<Row> = {
3
+ rows: Row[];
4
+ };
5
+ export type AgentSandboxSqlTransaction = {
6
+ query<Row = Record<string, unknown>>(sql: string, params?: readonly unknown[]): Promise<AgentSandboxSqlResult<Row>>;
7
+ };
8
+ export type AgentSandboxSqlClient = AgentSandboxSqlTransaction & {
9
+ transaction<Value>(work: (tx: AgentSandboxSqlTransaction) => Promise<Value>): Promise<Value>;
10
+ };
11
+ export declare const agentSandboxPostgresSchemaSql: (schema?: string) => string;
12
+ export declare const createPostgresAgentSandboxOperationStore: ({ client, schema, }: {
13
+ client: AgentSandboxSqlClient;
14
+ schema?: string;
15
+ }) => AgentSandboxOperationStore;
@@ -0,0 +1,141 @@
1
+ export type AgentIdentityPin = {
2
+ descriptorId: string;
3
+ descriptorVersion: string;
4
+ descriptorDigest: string;
5
+ };
6
+ export type HttpCapability = {
7
+ id: string;
8
+ kind: "http";
9
+ origins: string[];
10
+ methods?: string[];
11
+ pathPrefixes?: string[];
12
+ credentialAliases?: string[];
13
+ maxRequestBytes?: number;
14
+ maxResponseBytes?: number;
15
+ maxUses?: number;
16
+ };
17
+ export type FilesystemCapability = {
18
+ id: string;
19
+ kind: "filesystem";
20
+ roots: Array<{
21
+ path: string;
22
+ access: "read" | "write" | "read-write";
23
+ }>;
24
+ maxWriteBytes?: number;
25
+ maxReadBytes?: number;
26
+ maxUses?: number;
27
+ };
28
+ export type ProcessCapability = {
29
+ id: string;
30
+ kind: "process";
31
+ executables: string[];
32
+ argumentPrefixes?: string[][];
33
+ workingDirectories?: string[];
34
+ credentialAliases?: string[];
35
+ timeoutMs?: number;
36
+ maxOutputBytes?: number;
37
+ maxUses?: number;
38
+ };
39
+ export type AgentCapability = HttpCapability | FilesystemCapability | ProcessCapability;
40
+ export type AgentCapabilityGrant = {
41
+ id: string;
42
+ runId: string;
43
+ actor: {
44
+ tenantId: string;
45
+ userId: string;
46
+ agentId: string;
47
+ delegationId?: string;
48
+ };
49
+ agent: AgentIdentityPin;
50
+ issuedAt: string;
51
+ expiresAt: string;
52
+ capabilities: AgentCapability[];
53
+ issuer: string;
54
+ proof?: unknown;
55
+ };
56
+ export type HttpAction = {
57
+ kind: "http";
58
+ capabilityId: string;
59
+ requestId: string;
60
+ url: string;
61
+ method: string;
62
+ headers?: Record<string, string>;
63
+ body?: string | Uint8Array;
64
+ credentials?: Record<string, string>;
65
+ };
66
+ export type FilesystemAction = {
67
+ kind: "filesystem";
68
+ capabilityId: string;
69
+ requestId: string;
70
+ operation: "read" | "write";
71
+ path: string;
72
+ data?: string | Uint8Array;
73
+ };
74
+ export type ProcessAction = {
75
+ kind: "process";
76
+ capabilityId: string;
77
+ requestId: string;
78
+ executable: string;
79
+ args: string[];
80
+ cwd?: string;
81
+ credentials?: Record<string, string>;
82
+ };
83
+ export type AgentSandboxAction = HttpAction | FilesystemAction | ProcessAction;
84
+ export type AgentSandboxReceipt = {
85
+ id: string;
86
+ grantId: string;
87
+ runId: string;
88
+ requestId: string;
89
+ capabilityId: string;
90
+ kind: AgentSandboxAction["kind"];
91
+ requestDigest: string;
92
+ outputDigest: string;
93
+ startedAt: string;
94
+ completedAt: string;
95
+ };
96
+ export type AgentSandboxResult = {
97
+ output: unknown;
98
+ receipt: AgentSandboxReceipt;
99
+ };
100
+ export type AgentSandboxOperationStore = {
101
+ begin(input: {
102
+ grantId: string;
103
+ capabilityId: string;
104
+ requestId: string;
105
+ maxUses?: number;
106
+ now: string;
107
+ }): Promise<{
108
+ state: "acquired";
109
+ } | {
110
+ state: "in_progress";
111
+ } | {
112
+ state: "limit_exceeded";
113
+ } | {
114
+ state: "completed";
115
+ result: AgentSandboxResult;
116
+ }>;
117
+ complete(input: {
118
+ grantId: string;
119
+ requestId: string;
120
+ result: AgentSandboxResult;
121
+ now: string;
122
+ }): Promise<boolean>;
123
+ fail(input: {
124
+ grantId: string;
125
+ requestId: string;
126
+ }): Promise<void>;
127
+ };
128
+ export type AgentSandboxAdapter = {
129
+ execute(input: {
130
+ grant: AgentCapabilityGrant;
131
+ capability: AgentCapability;
132
+ action: AgentSandboxAction;
133
+ resolveCredential(alias: string): Promise<string>;
134
+ }): Promise<unknown>;
135
+ };
136
+ export type AgentSandbox = {
137
+ execute(input: {
138
+ grant: AgentCapabilityGrant;
139
+ action: AgentSandboxAction;
140
+ }): Promise<AgentSandboxResult>;
141
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@absolutejs/agent-sandbox",
3
+ "version": "0.1.0",
4
+ "description": "Deny-by-default capability grants and provider-neutral HTTP, filesystem, process, and credential-safe action adapters for AI agents.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/absolutejs/agent-sandbox.git"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "absolutejs": {
17
+ "manifestContract": 2
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ },
24
+ "./manifest": {
25
+ "types": "./dist/manifest.d.ts",
26
+ "import": "./dist/manifest.js"
27
+ },
28
+ "./manifest.json": "./dist/manifest.json"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "scripts": {
36
+ "format": "prettier --write \"./**/*.{ts,json,md,yml}\"",
37
+ "typecheck": "tsc --noEmit",
38
+ "test": "bun test",
39
+ "build": "rm -rf dist && bun build src/index.ts src/manifest.ts --outdir dist --target=bun --external @absolutejs/manifest --external @sinclair/typebox && tsc -p tsconfig.build.json && absolute-manifest emit",
40
+ "check:package": "bun run typecheck && bun run test && bun run build"
41
+ },
42
+ "dependencies": {
43
+ "@absolutejs/manifest": "^0.2.0",
44
+ "@sinclair/typebox": "^0.34.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/bun": "^1.3.14",
48
+ "prettier": "3.5.3",
49
+ "typescript": "5.8.3"
50
+ }
51
+ }