@crewhaus/sandbox-image-rust 0.1.4 → 0.1.6
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 +29 -0
- package/{src/index.ts → dist/index.js} +15 -20
- package/package.json +11 -8
- package/src/index.test.ts +0 -157
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type SandboxImageEntry } from "@crewhaus/sandbox-image-registry";
|
|
2
|
+
/**
|
|
3
|
+
* Catalog R8 `sandbox-image-rust` — Section 36 polyglot Rust sandbox image.
|
|
4
|
+
*
|
|
5
|
+
* Registers a curated Rust stable image into `@crewhaus/sandbox-image-registry`
|
|
6
|
+
* so polyglot agents can `lookupSandboxImage("rust")` and route a snippet
|
|
7
|
+
* through `tool-code-execution`'s sandbox path.
|
|
8
|
+
*
|
|
9
|
+
* Snippet mode (default entrypoint): `rustc - -o /tmp/snippet && /tmp/snippet`
|
|
10
|
+
* is the cargo-script-style pattern. Advanced callers who mount a project
|
|
11
|
+
* exec their own compiled binary path. We keep the registered entrypoint
|
|
12
|
+
* short — `["rustc", "-"]` — and let `tool-code-execution` extend it with
|
|
13
|
+
* the snippet text + post-compile exec at call time.
|
|
14
|
+
*
|
|
15
|
+
* Cold-start budget: ≤2s for the compiled-language warm pool. Rust's
|
|
16
|
+
* `rustc --version` healthcheck is a single binary invocation and stays
|
|
17
|
+
* well under the budget. The actual compile-and-run round-trip can take
|
|
18
|
+
* longer; tool-code-execution callers may pass a higher timeoutMs.
|
|
19
|
+
*
|
|
20
|
+
* Layer R8.
|
|
21
|
+
*/
|
|
22
|
+
export declare const RUST_IMAGE_ID = "rust";
|
|
23
|
+
export declare const RUST_IMAGE_REF = "rust:1-alpine";
|
|
24
|
+
export declare const RUST_DEFAULT_ENTRYPOINT: ReadonlyArray<string>;
|
|
25
|
+
export declare const RUST_HEALTHCHECK_ARGV: ReadonlyArray<string>;
|
|
26
|
+
/** Cold-start budget for the warm pool (ms). T7 layer asserts this. */
|
|
27
|
+
export declare const RUST_COLD_START_BUDGET_MS = 2000;
|
|
28
|
+
/** Idempotent — calling twice throws via the registry's duplicate-id refusal. */
|
|
29
|
+
export declare function registerRustSandboxImage(): SandboxImageEntry;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { registerSandboxImage } from "@crewhaus/sandbox-image-registry";
|
|
3
2
|
/**
|
|
4
3
|
* Catalog R8 `sandbox-image-rust` — Section 36 polyglot Rust sandbox image.
|
|
5
4
|
*
|
|
@@ -20,27 +19,23 @@ import { type SandboxImageEntry, registerSandboxImage } from "@crewhaus/sandbox-
|
|
|
20
19
|
*
|
|
21
20
|
* Layer R8.
|
|
22
21
|
*/
|
|
23
|
-
|
|
24
22
|
export const RUST_IMAGE_ID = "rust";
|
|
25
23
|
export const RUST_IMAGE_REF = "rust:1-alpine";
|
|
26
|
-
export const RUST_DEFAULT_ENTRYPOINT
|
|
27
|
-
export const RUST_HEALTHCHECK_ARGV
|
|
28
|
-
|
|
24
|
+
export const RUST_DEFAULT_ENTRYPOINT = ["rustc", "-"];
|
|
25
|
+
export const RUST_HEALTHCHECK_ARGV = ["rustc", "--version"];
|
|
29
26
|
/** Cold-start budget for the warm pool (ms). T7 layer asserts this. */
|
|
30
27
|
export const RUST_COLD_START_BUDGET_MS = 2_000;
|
|
31
|
-
|
|
32
28
|
/** Idempotent — calling twice throws via the registry's duplicate-id refusal. */
|
|
33
|
-
export function registerRustSandboxImage()
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
});
|
|
29
|
+
export function registerRustSandboxImage() {
|
|
30
|
+
return registerSandboxImage({
|
|
31
|
+
id: RUST_IMAGE_ID,
|
|
32
|
+
image: RUST_IMAGE_REF,
|
|
33
|
+
defaultEntrypoint: RUST_DEFAULT_ENTRYPOINT,
|
|
34
|
+
healthcheck: {
|
|
35
|
+
command: RUST_HEALTHCHECK_ARGV,
|
|
36
|
+
expectedExitCode: 0,
|
|
37
|
+
timeoutMs: RUST_COLD_START_BUDGET_MS,
|
|
38
|
+
},
|
|
39
|
+
description: "Rust stable on alpine — snippet mode via `rustc -`; compiled-binary mode for mounted crates.",
|
|
40
|
+
});
|
|
46
41
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewhaus/sandbox-image-rust",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Rust stable polyglot sandbox image: registry registration + multi-stage Dockerfile + T2/T7/T8 contract tests (Section 36)",
|
|
6
|
-
"main": "
|
|
7
|
-
"types": "
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
10
13
|
},
|
|
11
14
|
"scripts": {
|
|
12
15
|
"test": "bun test src"
|
|
13
16
|
},
|
|
14
17
|
"dependencies": {
|
|
15
|
-
"@crewhaus/errors": "0.1.
|
|
16
|
-
"@crewhaus/sandbox": "0.1.
|
|
17
|
-
"@crewhaus/sandbox-image-registry": "0.1.
|
|
18
|
+
"@crewhaus/errors": "0.1.6",
|
|
19
|
+
"@crewhaus/sandbox": "0.1.6",
|
|
20
|
+
"@crewhaus/sandbox-image-registry": "0.1.6"
|
|
18
21
|
},
|
|
19
22
|
"license": "Apache-2.0",
|
|
20
23
|
"author": {
|
|
@@ -34,5 +37,5 @@
|
|
|
34
37
|
"publishConfig": {
|
|
35
38
|
"access": "public"
|
|
36
39
|
},
|
|
37
|
-
"files": ["
|
|
40
|
+
"files": ["dist", "README.md", "LICENSE", "NOTICE"]
|
|
38
41
|
}
|
package/src/index.test.ts
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { createSandbox } from "@crewhaus/sandbox";
|
|
3
|
-
import {
|
|
4
|
-
ImageRegistrationError,
|
|
5
|
-
_resetSandboxImageRegistry,
|
|
6
|
-
hasSandboxImage,
|
|
7
|
-
listAllowedImageRefs,
|
|
8
|
-
lookupSandboxImage,
|
|
9
|
-
registerSandboxImage,
|
|
10
|
-
} from "@crewhaus/sandbox-image-registry";
|
|
11
|
-
import {
|
|
12
|
-
RUST_COLD_START_BUDGET_MS,
|
|
13
|
-
RUST_DEFAULT_ENTRYPOINT,
|
|
14
|
-
RUST_HEALTHCHECK_ARGV,
|
|
15
|
-
RUST_IMAGE_ID,
|
|
16
|
-
RUST_IMAGE_REF,
|
|
17
|
-
registerRustSandboxImage,
|
|
18
|
-
} from "./index";
|
|
19
|
-
|
|
20
|
-
describe("registerRustSandboxImage (T1 + T2)", () => {
|
|
21
|
-
beforeEach(() => _resetSandboxImageRegistry());
|
|
22
|
-
afterEach(() => _resetSandboxImageRegistry());
|
|
23
|
-
|
|
24
|
-
test("constants match the kickoff prompt's spec", () => {
|
|
25
|
-
expect(RUST_IMAGE_ID).toBe("rust");
|
|
26
|
-
expect(RUST_IMAGE_REF).toBe("rust:1-alpine");
|
|
27
|
-
expect(RUST_DEFAULT_ENTRYPOINT).toEqual(["rustc", "-"]);
|
|
28
|
-
expect(RUST_HEALTHCHECK_ARGV).toEqual(["rustc", "--version"]);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("registerRustSandboxImage() registers an image with the right shape", () => {
|
|
32
|
-
const entry = registerRustSandboxImage();
|
|
33
|
-
expect(entry.id).toBe("rust");
|
|
34
|
-
expect(entry.image).toBe("rust:1-alpine");
|
|
35
|
-
expect(entry.defaultEntrypoint).toEqual(["rustc", "-"]);
|
|
36
|
-
expect(entry.healthcheck.command).toEqual(["rustc", "--version"]);
|
|
37
|
-
expect(entry.healthcheck.expectedExitCode).toBe(0);
|
|
38
|
-
expect(entry.healthcheck.timeoutMs).toBe(RUST_COLD_START_BUDGET_MS);
|
|
39
|
-
expect(entry.description).toMatch(/Rust/);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("lookupSandboxImage('rust') returns the registered entry", () => {
|
|
43
|
-
registerRustSandboxImage();
|
|
44
|
-
expect(hasSandboxImage("rust")).toBe(true);
|
|
45
|
-
expect(lookupSandboxImage("rust").image).toBe("rust:1-alpine");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("listAllowedImageRefs includes rust:1-alpine after registration", () => {
|
|
49
|
-
registerRustSandboxImage();
|
|
50
|
-
const refs = listAllowedImageRefs();
|
|
51
|
-
expect(refs).toContain("rust:1-alpine");
|
|
52
|
-
expect(refs).toContain("python:3.13-slim");
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe("Rust-shape T2 contract — round-trip via noop sandbox", () => {
|
|
57
|
-
beforeEach(() => _resetSandboxImageRegistry());
|
|
58
|
-
afterEach(() => _resetSandboxImageRegistry());
|
|
59
|
-
|
|
60
|
-
test("noop sandbox accepts rust:1-alpine when registered", async () => {
|
|
61
|
-
registerRustSandboxImage();
|
|
62
|
-
const sandbox = createSandbox({
|
|
63
|
-
backend: "noop",
|
|
64
|
-
allowedImages: listAllowedImageRefs(),
|
|
65
|
-
});
|
|
66
|
-
const result = await sandbox.exec({
|
|
67
|
-
image: RUST_IMAGE_REF,
|
|
68
|
-
argv: ["printf", "hello-from-rust"],
|
|
69
|
-
});
|
|
70
|
-
expect(result.exitCode).toBe(0);
|
|
71
|
-
expect(result.stdout).toBe("hello-from-rust");
|
|
72
|
-
await sandbox.close();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("noop sandbox refuses rust:1-alpine when NOT registered", async () => {
|
|
76
|
-
const trioRefs = ["python:3.13-slim", "node:22-alpine", "alpine:3.19"];
|
|
77
|
-
const sandbox = createSandbox({ backend: "noop", allowedImages: trioRefs });
|
|
78
|
-
await expect(sandbox.exec({ image: RUST_IMAGE_REF, argv: ["printf", "x"] })).rejects.toThrow(
|
|
79
|
-
/not on the allowlist/,
|
|
80
|
-
);
|
|
81
|
-
await sandbox.close();
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe("Rust-shape T7 — cold-start budget", () => {
|
|
86
|
-
beforeEach(() => _resetSandboxImageRegistry());
|
|
87
|
-
afterEach(() => _resetSandboxImageRegistry());
|
|
88
|
-
|
|
89
|
-
test("healthcheck timeoutMs is within the compiled-language budget (≤2s)", () => {
|
|
90
|
-
const entry = registerRustSandboxImage();
|
|
91
|
-
expect(entry.healthcheck.timeoutMs).toBeLessThanOrEqual(2_000);
|
|
92
|
-
expect(entry.healthcheck.timeoutMs).toBeGreaterThan(0);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe("Rust-shape T8 — escape-attempt suite (reuses §18 corpus shape)", () => {
|
|
97
|
-
beforeEach(() => _resetSandboxImageRegistry());
|
|
98
|
-
afterEach(() => _resetSandboxImageRegistry());
|
|
99
|
-
|
|
100
|
-
test("Rust image registration is idempotent-then-rejected", () => {
|
|
101
|
-
registerRustSandboxImage();
|
|
102
|
-
expect(() => registerRustSandboxImage()).toThrow(/already registered/);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("Rust entry's image string passes registry validation", () => {
|
|
106
|
-
expect(RUST_IMAGE_REF.startsWith("-")).toBe(false);
|
|
107
|
-
expect(/\s/.test(RUST_IMAGE_REF)).toBe(false);
|
|
108
|
-
expect(RUST_IMAGE_REF.includes("\n")).toBe(false);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("Rust defaultEntrypoint contains no shell-meta", () => {
|
|
112
|
-
for (const arg of RUST_DEFAULT_ENTRYPOINT) {
|
|
113
|
-
expect(/[;&|<>$`(){}]/.test(arg)).toBe(false);
|
|
114
|
-
expect(arg.includes("\n")).toBe(false);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("Rust healthcheck argv contains no shell-meta", () => {
|
|
119
|
-
for (const arg of RUST_HEALTHCHECK_ARGV) {
|
|
120
|
-
expect(/[;&|<>$`(){}]/.test(arg)).toBe(false);
|
|
121
|
-
expect(arg.includes("\n")).toBe(false);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("attempted CLI-flag-injection registration via Rust id is refused", () => {
|
|
126
|
-
expect(() =>
|
|
127
|
-
registerSandboxImage({
|
|
128
|
-
id: "rust",
|
|
129
|
-
image: "--privileged",
|
|
130
|
-
defaultEntrypoint: ["rustc", "-"],
|
|
131
|
-
healthcheck: { command: ["rustc", "--version"], expectedExitCode: 0 },
|
|
132
|
-
}),
|
|
133
|
-
).toThrow(ImageRegistrationError);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test("attempted whitespace-tampered Rust image is refused", () => {
|
|
137
|
-
expect(() =>
|
|
138
|
-
registerSandboxImage({
|
|
139
|
-
id: "rust",
|
|
140
|
-
image: "rust:1-alpine --privileged",
|
|
141
|
-
defaultEntrypoint: ["rustc", "-"],
|
|
142
|
-
healthcheck: { command: ["rustc", "--version"], expectedExitCode: 0 },
|
|
143
|
-
}),
|
|
144
|
-
).toThrow(/whitespace/);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("attempted shell-meta-tagged Rust image is refused", () => {
|
|
148
|
-
expect(() =>
|
|
149
|
-
registerSandboxImage({
|
|
150
|
-
id: "rust",
|
|
151
|
-
image: "rust:$(id)",
|
|
152
|
-
defaultEntrypoint: ["rustc", "-"],
|
|
153
|
-
healthcheck: { command: ["rustc", "--version"], expectedExitCode: 0 },
|
|
154
|
-
}),
|
|
155
|
-
).toThrow(/valid registry/);
|
|
156
|
-
});
|
|
157
|
-
});
|