forked from EduCraft/curriculum-project-hub
Merge pull request 'fix(hub): enable tenant Typst package resolution' (#22) from fix/tenant-typst-package-resolution into main
Reviewed-on: EduCraft/curriculum-project-hub#22
This commit is contained in:
@@ -40,6 +40,17 @@ HUB_PROJECT_WORKSPACE_ROOT="/var/lib/cph-hub/workspaces"
|
||||
# startup unless XDG_STATE_HOME is set (then defaults to $XDG_STATE_HOME/skills).
|
||||
HUB_SKILL_STORE_ROOT="/var/lib/cph-hub/state/skills"
|
||||
|
||||
# Optional tenant-local Typst package roots. When configured, the agent
|
||||
# sandbox forwards these exact paths to Typst. The preinstalled package path is
|
||||
# read-only; the cache path is the only additional write location.
|
||||
# Keep the preinstalled package path outside the release tree and provision it
|
||||
# with the namespace layout expected by Typst, for example:
|
||||
# <root>/paradigm/paradigm-templates/0.2.20/
|
||||
# Use a separate service-writable cache path when runtime dependencies may be
|
||||
# downloaded; do not make the immutable preinstalled directory the cache.
|
||||
# TYPST_PACKAGE_PATH="/srv/curriculum-project-hub/typst-packages/org-a"
|
||||
# TYPST_PACKAGE_CACHE_PATH="/var/cache/cph-hub/org-a/typst"
|
||||
|
||||
# This process is pinned to exactly one Organization. Feishu credentials are
|
||||
# resolved from that Organization's encrypted ACTIVE connection.
|
||||
HUB_SILO_ORGANIZATION_ID=""
|
||||
|
||||
@@ -85,7 +85,7 @@ REMOTE
|
||||
-e "ssh ${SSH_OPTS[*]}" \
|
||||
"$REPO_ROOT/hub/" "$DEPLOY_USER@$HOST:$HUB_DIR/"
|
||||
|
||||
echo "[fleet] npm ci + build (tsc + admin-web SPA)"
|
||||
echo "[fleet] npm ci (including build-time dev deps) + build (tsc + admin-web SPA)"
|
||||
ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" bash -s <<REMOTE
|
||||
set -euo pipefail
|
||||
flock /var/lock/cph-hub-release-publish bash -c '
|
||||
@@ -95,8 +95,10 @@ flock /var/lock/cph-hub-release-publish bash -c '
|
||||
exit 0
|
||||
fi
|
||||
cd "$HUB_DIR"
|
||||
PUPPETEER_SKIP_DOWNLOAD=1 npm ci
|
||||
npm ci --prefix admin-web
|
||||
PUPPETEER_SKIP_DOWNLOAD=1 npm ci --include=dev
|
||||
npm ci --include=dev --prefix admin-web
|
||||
test -x node_modules/.bin/tsc
|
||||
test -x admin-web/node_modules/.bin/vite
|
||||
npm run audit:production
|
||||
npm run build
|
||||
test -f admin-web/build/index.html
|
||||
|
||||
@@ -60,10 +60,12 @@ if [ "$release_ready" = false ]; then
|
||||
-e "ssh ${SSH_OPTS[*]}" \
|
||||
"$REPO_ROOT/hub/" "$DEPLOY_USER@$HOST:$HUB_DIR/"
|
||||
|
||||
# 2. Install deps (hub + admin-web), audit hub prod, build tsc + SPA, mark complete.
|
||||
# 2. Install deps (including build-time dev deps) for hub + admin-web,
|
||||
# audit hub prod, build tsc + SPA, mark complete. NODE_ENV=production may be
|
||||
# inherited by the remote shell, so --include=dev is intentional here.
|
||||
# `npm run build` → tsc then admin:build → admin-web/build for registerStaticSpa.
|
||||
ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" \
|
||||
"cd '$HUB_DIR' && PUPPETEER_SKIP_DOWNLOAD=1 npm ci && npm ci --prefix admin-web && npm run audit:production && npm run build && touch '$RELEASE_DIR/.complete'"
|
||||
"cd '$HUB_DIR' && PUPPETEER_SKIP_DOWNLOAD=1 npm ci --include=dev && npm ci --include=dev --prefix admin-web && npm run audit:production && npm run build && touch '$RELEASE_DIR/.complete'"
|
||||
fi
|
||||
|
||||
# 3. Ensure the service is installed (idempotent), then restart.
|
||||
|
||||
Generated
+50
-38
@@ -247,22 +247,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk": {
|
||||
"version": "0.3.202",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.202.tgz",
|
||||
"integrity": "sha512-LnaLxDtsZP7J6g++xRSnnpTX7CHNe4v+cvBRIlD2ar+N+xi0aqY2YDaCsxPsl+haVUB9kqlUMd0zosmwsfTGjQ==",
|
||||
"version": "0.3.217",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.3.217.tgz",
|
||||
"integrity": "sha512-juszT3itL8R6OQ6nb/8IZE34UjKps8Jf7N8vjCXLx+vbJc+k3EojZOs93tJwT5iTRvfV1a0N53zJbKn/iJpKrQ==",
|
||||
"license": "SEE LICENSE IN README.md",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.202",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.202",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.202",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.202",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.202",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.202",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.202",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.202"
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.217",
|
||||
"@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.217",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.217",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.217",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.217",
|
||||
"@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.217",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.217",
|
||||
"@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.217"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": ">=0.93.0",
|
||||
@@ -271,9 +271,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-arm64": {
|
||||
"version": "0.3.202",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.202.tgz",
|
||||
"integrity": "sha512-ujR3zDthDPkZs+AxW95iHpqLT5cuwGImsS3mVxLt1DlDij4qeTnihLX8+EpQTK+oNW9jjvFA86yKwa84fa1KYA==",
|
||||
"version": "0.3.217",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-arm64/-/claude-agent-sdk-darwin-arm64-0.3.217.tgz",
|
||||
"integrity": "sha512-dl119zmL1Ssyd8Fx0xfVMpss2scrGCZwf+rhZwl2lHa2dYuXVluLgqi4DUIWDj3rRYdrAvaMpjCAv6a5w07ddw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -284,9 +284,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64": {
|
||||
"version": "0.3.202",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.202.tgz",
|
||||
"integrity": "sha512-s/RVSGgkVmIMfyt1ndR8braLLu82bARoijmt1kk8d4IptUZ0Sc+zNUWKoFXwR9XqDBu6rBbBF9RIzD02raT57w==",
|
||||
"version": "0.3.217",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-darwin-x64/-/claude-agent-sdk-darwin-x64-0.3.217.tgz",
|
||||
"integrity": "sha512-IeKL1HN8fEcRQ4uw5d02by1ThpjhRtOgfHcCTBQ2KS4JfEIHvc1VGWt6Exb2a7VHhT8uRcfjPk9urbmYayZmaw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -297,12 +297,15 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64": {
|
||||
"version": "0.3.202",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.202.tgz",
|
||||
"integrity": "sha512-a4YtRkgGYt3ogePJDW8Ts6bNW690jb9LHyZaiWXsi+zT53xCNqJB2zKPyRc7hXWOqzIk4nCfwJpjmhLzMu3WIg==",
|
||||
"version": "0.3.217",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64/-/claude-agent-sdk-linux-arm64-0.3.217.tgz",
|
||||
"integrity": "sha512-KtrnfEwUSCdq2cc4Pgysl+U66vqw3h7u04N5/OLHmYZ4AZYy8JcqdOaSJZ27iL2bgbAxyKwu5/9YmEk9A4IswA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -310,12 +313,15 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-arm64-musl": {
|
||||
"version": "0.3.202",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.202.tgz",
|
||||
"integrity": "sha512-abSb3Gah45kUNyOeKjmQ/dd1KZ4CaQz5JAr9YQxRDXoOwx8wJVx6huBIpDxjms9wyS9X5Rqxn0Lx7zFP+wV2zQ==",
|
||||
"version": "0.3.217",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-arm64-musl/-/claude-agent-sdk-linux-arm64-musl-0.3.217.tgz",
|
||||
"integrity": "sha512-Bb4AJxqrVPouM4sYIdvX3/AO5womhe70u3Euv+6B5J2OoqcRaWarVvYevX3KRruC5TvlV2Josw14dsL5qVNL+A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -323,12 +329,15 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64": {
|
||||
"version": "0.3.202",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.202.tgz",
|
||||
"integrity": "sha512-XIvhdCWAAT4OdOA82fOJII+WH0Tf8pFckckEbJMMmOgQBKOnHT+609Pd3Ehw6zGcA9iFrhG5mY8Ncuckeo1aMw==",
|
||||
"version": "0.3.217",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64/-/claude-agent-sdk-linux-x64-0.3.217.tgz",
|
||||
"integrity": "sha512-JsAQyfl4n0PR4LX0h1SxMo0raERGb8B8dvbaoNQRRSpb9A2vvcwPEjyKu0eRKHRhTvspvuD6TfNxzxrmnouX9A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -336,12 +345,15 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl": {
|
||||
"version": "0.3.202",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.202.tgz",
|
||||
"integrity": "sha512-fze5nAQL1ErcMCQNB10ILaWdM0QbJSaTQzBz8NVAy0FGW8ZL0t4Wf/VgFkfzXbfkaxmPuM1C27Dn5HiU7UDEHQ==",
|
||||
"version": "0.3.217",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-linux-x64-musl/-/claude-agent-sdk-linux-x64-musl-0.3.217.tgz",
|
||||
"integrity": "sha512-qhugNZd77vAoPMIGM8vFHlbwTltFyI1POmfyl0ZJSpc6v7RE9+5+nqL2aGbGSDsDQkEHrJasXURxIeTMn9ut2w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -349,9 +361,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-arm64": {
|
||||
"version": "0.3.202",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.202.tgz",
|
||||
"integrity": "sha512-N1J0HRvC+8a69bqNY7+ENIYQzR0i7s+rOIGH5XtuLxvLqOnZO8LHxWEZOe8ezabGq5eZqphSCgL6vQnQQpNh+A==",
|
||||
"version": "0.3.217",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-arm64/-/claude-agent-sdk-win32-arm64-0.3.217.tgz",
|
||||
"integrity": "sha512-LuaQ+PXZvIToAR81JoiGa6Me9HDma2WH2oiYlAWh43IWaXHyOqgaI1aqSM0BjDhy2UiYWTvGzAopnqPnk+jSBw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -362,9 +374,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@anthropic-ai/claude-agent-sdk-win32-x64": {
|
||||
"version": "0.3.202",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.202.tgz",
|
||||
"integrity": "sha512-ytLGEC1fjTSiVSoXukS+j9G+06Mi20NSzxxzlG6uE75SEB0+17tHdWUaHqd8PhH/6GPzcYx81czxWQl1MVbq4Q==",
|
||||
"version": "0.3.217",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk-win32-x64/-/claude-agent-sdk-win32-x64-0.3.217.tgz",
|
||||
"integrity": "sha512-4r/T+ze/S/CLZ58tP4Mw52XPmsc/LOrCOd8jZOqM13FCPWdCMU2osWmszEIKGVMRG2cGsaLVDYcks5cWFqjCjw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2685,9 +2697,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
|
||||
"integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
|
||||
"integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
||||
@@ -33,6 +33,8 @@ const SAFE_HOST_ENV_KEYS = [
|
||||
"all_proxy",
|
||||
"no_proxy",
|
||||
"NODE_USE_ENV_PROXY",
|
||||
"TYPST_PACKAGE_PATH",
|
||||
"TYPST_PACKAGE_CACHE_PATH",
|
||||
] as const;
|
||||
|
||||
const SANDBOX_HIDDEN_ENV_KEYS = [
|
||||
@@ -134,6 +136,7 @@ export async function createAgentSecurityPolicy(input: AgentSecurityInput): Prom
|
||||
|
||||
const sensitiveReadPaths = hostSensitiveReadPaths(hostEnv);
|
||||
const runtimeReadPaths = hostRuntimeReadPaths(hostEnv);
|
||||
const typstCacheWritePaths = hostTypstCacheWritePaths(hostEnv);
|
||||
const selectedSkills = input.skills ?? [];
|
||||
const skillPlugin = selectedSkills.length === 0
|
||||
? null
|
||||
@@ -174,7 +177,7 @@ export async function createAgentSecurityPolicy(input: AgentSecurityInput): Prom
|
||||
autoAllowBashIfSandboxed: true,
|
||||
allowUnsandboxedCommands: false,
|
||||
filesystem: {
|
||||
allowWrite: [workspaceDir],
|
||||
allowWrite: [...new Set([workspaceDir, ...typstCacheWritePaths])],
|
||||
// Reject every write path by default, then re-open only the canonical
|
||||
// workspace. This prevents bubblewrap's ordinary temp exceptions from
|
||||
// turning an unauthorized path into a successful ephemeral write.
|
||||
@@ -244,9 +247,30 @@ function hostRuntimeReadPaths(env: Readonly<Record<string, string | undefined>>)
|
||||
if (!isAbsolute(cphBin)) throw new Error("CPH_BIN must be absolute for the Agent subprocess");
|
||||
platformPaths.push(resolve(cphBin));
|
||||
}
|
||||
platformPaths.push(...configuredTypstPackagePaths(env, ["TYPST_PACKAGE_PATH", "TYPST_PACKAGE_CACHE_PATH"]));
|
||||
return [...new Set(platformPaths.map((path) => resolve(path)))];
|
||||
}
|
||||
|
||||
function hostTypstCacheWritePaths(env: Readonly<Record<string, string | undefined>>): string[] {
|
||||
return configuredTypstPackagePaths(env, ["TYPST_PACKAGE_CACHE_PATH"]);
|
||||
}
|
||||
|
||||
function configuredTypstPackagePaths(
|
||||
env: Readonly<Record<string, string | undefined>>,
|
||||
names: readonly ("TYPST_PACKAGE_PATH" | "TYPST_PACKAGE_CACHE_PATH")[],
|
||||
): string[] {
|
||||
const paths: string[] = [];
|
||||
for (const name of names) {
|
||||
const packagePath = env[name]?.trim();
|
||||
if (packagePath === undefined || packagePath === "") continue;
|
||||
if (!isAbsolute(packagePath)) throw new Error(`${name} must be absolute for the Agent subprocess`);
|
||||
const canonical = resolve(packagePath);
|
||||
if (canonical === "/") throw new Error(`${name} must not be the filesystem root`);
|
||||
paths.push(canonical);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
function hostSensitiveReadPaths(env: Readonly<Record<string, string | undefined>>): string[] {
|
||||
const home = homedir();
|
||||
const paths = [
|
||||
|
||||
@@ -32,6 +32,8 @@ describe("agent subprocess security policy", () => {
|
||||
ALL_PROXY: "socks5h://127.0.0.1:7890",
|
||||
NO_PROXY: "127.0.0.1,localhost,::1",
|
||||
NODE_USE_ENV_PROXY: "1",
|
||||
TYPST_PACKAGE_PATH: "/srv/curriculum-project-hub/typst-packages/para-26071100",
|
||||
TYPST_PACKAGE_CACHE_PATH: "/srv/curriculum-project-hub/typst-packages/para-26071100",
|
||||
DATABASE_URL: "postgresql://platform-secret",
|
||||
FEISHU_APP_SECRET: "feishu-secret",
|
||||
HUB_SESSION_SECRET: "session-secret",
|
||||
@@ -44,6 +46,8 @@ describe("agent subprocess security policy", () => {
|
||||
PATH: "/usr/local/bin:/usr/bin:/bin",
|
||||
LANG: "C.UTF-8",
|
||||
CPH_BIN: "/usr/local/bin/cph",
|
||||
TYPST_PACKAGE_PATH: "/srv/curriculum-project-hub/typst-packages/para-26071100",
|
||||
TYPST_PACKAGE_CACHE_PATH: "/srv/curriculum-project-hub/typst-packages/para-26071100",
|
||||
ANTHROPIC_BASE_URL: "http://127.0.0.1:43123",
|
||||
ANTHROPIC_AUTH_TOKEN: "run-proxy-capability",
|
||||
ANTHROPIC_API_KEY: "",
|
||||
@@ -72,7 +76,10 @@ describe("agent subprocess security policy", () => {
|
||||
autoAllowBashIfSandboxed: true,
|
||||
allowUnsandboxedCommands: false,
|
||||
filesystem: {
|
||||
allowWrite: [canonicalWorkspace],
|
||||
allowWrite: expect.arrayContaining([
|
||||
canonicalWorkspace,
|
||||
"/srv/curriculum-project-hub/typst-packages/para-26071100",
|
||||
]),
|
||||
denyRead: ["/"],
|
||||
allowRead: expect.arrayContaining([canonicalWorkspace, "/usr/bin"]),
|
||||
},
|
||||
@@ -85,6 +92,59 @@ describe("agent subprocess security policy", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("passes configured Typst package roots and exposes them read-only to the sandbox", async () => {
|
||||
const { workspaceRoot, workspace } = await makeWorkspace();
|
||||
const packageRoot = "/srv/curriculum-project-hub/typst-packages/para-26071100";
|
||||
const cacheRoot = "/var/cache/cph-hub/para-26071100/typst";
|
||||
const policy = await createAgentSecurityPolicy({
|
||||
runId: "run-test",
|
||||
workspaceRoot,
|
||||
workspaceDir: workspace,
|
||||
hostEnv: {
|
||||
PATH: "/usr/bin:/bin",
|
||||
TYPST_PACKAGE_PATH: packageRoot,
|
||||
TYPST_PACKAGE_CACHE_PATH: cacheRoot,
|
||||
},
|
||||
});
|
||||
const canonicalWorkspace = await realpath(workspace);
|
||||
|
||||
expect(policy.env).toMatchObject({
|
||||
TYPST_PACKAGE_PATH: packageRoot,
|
||||
TYPST_PACKAGE_CACHE_PATH: cacheRoot,
|
||||
});
|
||||
expect(policy.sandbox.filesystem.allowRead).toEqual(expect.arrayContaining([packageRoot, cacheRoot]));
|
||||
expect(policy.sandbox.filesystem.allowWrite).toEqual(expect.arrayContaining([canonicalWorkspace, cacheRoot]));
|
||||
expect(policy.sandbox.filesystem.allowWrite).not.toContain(packageRoot);
|
||||
});
|
||||
|
||||
it("rejects a relative Typst package root instead of silently losing package access", async () => {
|
||||
const { workspaceRoot, workspace } = await makeWorkspace();
|
||||
|
||||
await expect(createAgentSecurityPolicy({
|
||||
runId: "run-test",
|
||||
workspaceRoot,
|
||||
workspaceDir: workspace,
|
||||
hostEnv: {
|
||||
PATH: "/usr/bin:/bin",
|
||||
TYPST_PACKAGE_PATH: "typst-packages",
|
||||
},
|
||||
})).rejects.toThrow("TYPST_PACKAGE_PATH must be absolute");
|
||||
});
|
||||
|
||||
it("rejects a Typst cache rooted at the filesystem root instead of widening writes", async () => {
|
||||
const { workspaceRoot, workspace } = await makeWorkspace();
|
||||
|
||||
await expect(createAgentSecurityPolicy({
|
||||
runId: "run-test",
|
||||
workspaceRoot,
|
||||
workspaceDir: workspace,
|
||||
hostEnv: {
|
||||
PATH: "/usr/bin:/bin",
|
||||
TYPST_PACKAGE_CACHE_PATH: "/",
|
||||
},
|
||||
})).rejects.toThrow("TYPST_PACKAGE_CACHE_PATH must not be the filesystem root");
|
||||
});
|
||||
|
||||
it("rejects provider environment keys outside the explicit protocol", async () => {
|
||||
const { workspaceRoot, workspace } = await makeWorkspace();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user