fix: clear dependency security advisories

This commit is contained in:
2026-07-10 18:59:03 +08:00
parent d730e51c3d
commit e049cb6880
11 changed files with 242 additions and 41 deletions
+6
View File
@@ -37,6 +37,12 @@ jobs:
restore-keys: | restore-keys: |
cargo-${{ runner.os }}- cargo-${{ runner.os }}-
- name: Install cargo-audit 0.22.2
run: cargo install cargo-audit --version 0.22.2 --locked
- name: Audit Rust vulnerabilities and unsoundness
run: cargo audit --deny unsound
# Fonts for typst compilation (WU-4 onward): the engineering files are # Fonts for typst compilation (WU-4 onward): the engineering files are
# CJK-heavy, so the compile-check / build tests need a CJK font present. # CJK-heavy, so the compile-check / build tests need a CJK font present.
# Harmless before those crates exist. # Harmless before those crates exist.
+3
View File
@@ -43,6 +43,9 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Audit production Node dependencies
run: npm run audit:production
- name: Install Linux sandbox dependency - name: Install Linux sandbox dependency
run: sudo apt-get update && sudo apt-get install --yes bubblewrap socat run: sudo apt-get update && sudo apt-get install --yes bubblewrap socat
@@ -1,7 +1,7 @@
# Clear dependency advisories and gate releases # Clear dependency advisories and gate releases
Type: task Type: task
Status: open Status: resolved
## Question ## Question
@@ -9,3 +9,36 @@ Remove every current `npm audit --omit=dev` high finding and every `cargo audit`
vulnerability/unsound finding through tested upgrades, compatible overrides or vulnerability/unsound finding through tested upgrades, compatible overrides or
upstream fixes—not ignores—and add reproducible Node and Rust security-audit upstream fixes—not ignores—and add reproducible Node and Rust security-audit
gates to CI and the production release evidence. gates to CI and the production release evidence.
## Answer
All current production-high Node advisories and all Rust vulnerability/unsound
advisories are removed without advisory ignores:
- `@larksuiteoapi/node-sdk` 1.70.0 declares Axios `~1.13.3`, which prevented npm
from selecting a patched release. The Hub now has a targeted npm override to
Axios 1.18.1. The direct SDK stays current; the override is locked and the
full Hub type/build/test gate validates the resolved tree. A deterministic
local-HTTP smoke also drives the real SDK/Axios stack through tenant-token,
authenticated success and HTTP-error paths without external credentials.
- `memmap2` is upgraded to 0.9.11 and `crossbeam-epoch` to 0.9.20.
- `plist` is upgraded to 1.10.0, which moves its XML parser to `quick-xml`
0.41.0.
- crates.io `citationberg` 0.7.0 still pins vulnerable `quick-xml` 0.38.4.
Cargo now patches it to the exact upstream citationberg commit that adopts
`quick-xml` 0.41.0; the git revision is immutable in both manifest and lock.
`hub-check` runs the checked-in `npm run audit:production` command immediately
after `npm ci`. `checker-check` installs the pinned cargo-audit 0.22.2 release
with `--locked`, then runs `cargo audit --deny unsound`; vulnerabilities fail by
default and unsound warnings are promoted to failures.
Current evidence:
- `npm audit --omit=dev --audit-level=high`: 0 vulnerabilities.
- `cargo audit --deny unsound`: 0 vulnerabilities and 0 unsound warnings.
- Four Rust `unmaintained` informational warnings remain visible and are not
ignored; they are outside this issue's vulnerability/unsound acceptance
criterion and will continue to appear in CI output.
- Hub: type-check, build and 280 tests pass (10 environment-specific skips).
- Rust: fmt, clippy with warnings denied, and all 53 workspace tests pass.
@@ -48,6 +48,7 @@ rollback and recovery, and no known critical security or data-integrity gaps.
- [Confine the agent runtime and protect service credentials](issues/16-confine-agent-and-protect-credentials.md) — replace the Agent environment, deny host reads/writes from root, keep every writable SDK path inside the canonical project, require bubblewrap plus socat, emit bounded correlated diagnostics, and prove the boundary with the real Linux SDK under a capability-free non-root identity. - [Confine the agent runtime and protect service credentials](issues/16-confine-agent-and-protect-credentials.md) — replace the Agent environment, deny host reads/writes from root, keep every writable SDK path inside the canonical project, require bubblewrap plus socat, emit bounded correlated diagnostics, and prove the boundary with the real Linux SDK under a capability-free non-root identity.
- [Close MCP context and file-delivery escape paths](issues/17-close-mcp-data-egress-escapes.md) — use no-follow project-rooted file operations and delivery snapshots, remove Git-root delivery, and bind every Feishu MCP read to the run's project and chat. - [Close MCP context and file-delivery escape paths](issues/17-close-mcp-data-egress-escapes.md) — use no-follow project-rooted file operations and delivery snapshots, remove Git-root delivery, and bind every Feishu MCP read to the run's project and chat.
- [Enforce Organization status at the shared authorization seam](issues/22-enforce-organization-status.md) — deny non-ACTIVE tenants before principal resolution, serialize every customer mutation and run admission against Organization lifecycle updates, reauthorize queued work, and stage/publish attachments with crash recovery and explicit filesystem compensation. - [Enforce Organization status at the shared authorization seam](issues/22-enforce-organization-status.md) — deny non-ACTIVE tenants before principal resolution, serialize every customer mutation and run admission against Organization lifecycle updates, reauthorize queued work, and stage/publish attachments with crash recovery and explicit filesystem compensation.
- [Clear dependency advisories and gate releases](issues/21-clear-dependency-security-advisories.md) — resolve Node production-high and Rust vulnerability/unsound advisories with locked compatible upgrades plus an exact upstream citationberg fix, and make both audits mandatory CI gates without ignores.
## Fog ## Fog
Generated
+11 -21
View File
@@ -264,10 +264,9 @@ dependencies = [
[[package]] [[package]]
name = "citationberg" name = "citationberg"
version = "0.7.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/typst/citationberg?rev=06a591e2f237d25e1dfdedac3f3d1494c496c52d#06a591e2f237d25e1dfdedac3f3d1494c496c52d"
checksum = "756ff1e3d43a9ecc8183932fb4d9fd3971236f3ce4acb62fe51d1cd43297547d"
dependencies = [ dependencies = [
"quick-xml 0.38.4", "quick-xml",
"serde", "serde",
"serde_path_to_error", "serde_path_to_error",
] ]
@@ -481,9 +480,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-epoch" name = "crossbeam-epoch"
version = "0.9.18" version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
@@ -1403,9 +1402,9 @@ checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]] [[package]]
name = "memmap2" name = "memmap2"
version = "0.9.10" version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -1642,13 +1641,13 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]] [[package]]
name = "plist" name = "plist"
version = "1.9.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85"
dependencies = [ dependencies = [
"base64", "base64",
"indexmap", "indexmap",
"quick-xml 0.39.4", "quick-xml",
"serde", "serde",
"time", "time",
] ]
@@ -1756,23 +1755,14 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.38.4" version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
dependencies = [ dependencies = [
"memchr", "memchr",
"serde", "serde",
] ]
[[package]]
name = "quick-xml"
version = "0.39.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.45" version = "1.0.45"
+5
View File
@@ -21,3 +21,8 @@ serde = { version = "1", features = ["derive"] }
toml = "0.8" toml = "0.8"
cph-diag = { path = "crates/cph-diag" } cph-diag = { path = "crates/cph-diag" }
cph-model = { path = "crates/cph-model" } cph-model = { path = "crates/cph-model" }
# citationberg 0.7.0 on crates.io pins vulnerable quick-xml 0.38. Upstream has
# adopted quick-xml 0.41 but has not published a replacement release yet.
[patch.crates-io]
citationberg = { git = "https://github.com/typst/citationberg", rev = "06a591e2f237d25e1dfdedac3f3d1494c496c52d" }
+5
View File
@@ -21,6 +21,11 @@ run the same command again. No systemd unit is installed until configuration,
paths, Node, bubblewrap, `socat`, `cph --version`, PostgreSQL connectivity and directory paths, Node, bubblewrap, `socat`, `cph --version`, PostgreSQL connectivity and directory
ownership all pass preflight. ownership all pass preflight.
The deployment path also runs `npm run audit:production` from the locked clean
install before building or restarting the service. A current high or critical
production advisory therefore blocks deployment instead of becoming a warning
buried in CI output.
An existing service account is accepted only when its primary group, home and An existing service account is accepted only when its primary group, home and
non-login shell match the requested configuration and it has no supplementary non-login shell match the requested configuration and it has no supplementary
groups. After provisioning, the installer executes path-access, built Hub, groups. After provisioning, the installer executes path-access, built Hub,
+2 -2
View File
@@ -36,8 +36,8 @@ rsync -az --delete \
-e "ssh ${SSH_OPTS[*]}" \ -e "ssh ${SSH_OPTS[*]}" \
"$REPO_ROOT/hub/" "$DEPLOY_USER@$HOST:$HUB_DIR/" "$REPO_ROOT/hub/" "$DEPLOY_USER@$HOST:$HUB_DIR/"
# 2. Install deps + build on the host. # 2. Install deps, prove the production dependency tree, then build on the host.
ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" "cd '$HUB_DIR' && npm ci && npm run build" ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" "cd '$HUB_DIR' && npm ci && npm run audit:production && npm run build"
# 3. Ensure the service is installed (idempotent), then restart. # 3. Ensure the service is installed (idempotent), then restart.
ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" " ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$HOST" "
+43 -16
View File
@@ -10,7 +10,7 @@
"dependencies": { "dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.3.202", "@anthropic-ai/claude-agent-sdk": "^0.3.202",
"@fastify/cookie": "^11.0.2", "@fastify/cookie": "^11.0.2",
"@larksuiteoapi/node-sdk": "^1.66.1", "@larksuiteoapi/node-sdk": "^1.70.0",
"@prisma/client": "^6.19.3", "@prisma/client": "^6.19.3",
"ai": "^7.0.16", "ai": "^7.0.16",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
@@ -874,9 +874,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@larksuiteoapi/node-sdk": { "node_modules/@larksuiteoapi/node-sdk": {
"version": "1.68.0", "version": "1.70.0",
"resolved": "https://registry.npmjs.org/@larksuiteoapi/node-sdk/-/node-sdk-1.68.0.tgz", "resolved": "https://registry.npmjs.org/@larksuiteoapi/node-sdk/-/node-sdk-1.70.0.tgz",
"integrity": "sha512-ip14+pWAv2La3bss4oDDtee2P5xBLDXuvmzhTvkxQMcPA5jCffFsler1TvNt4MyW6pexscj72AEWektyZwU9Yw==", "integrity": "sha512-CzVfMbh2MO+cVWQgQGeYN9KkFdX6mofYYTsIjImU0hQqlxaXKzHcUEwG6mrpsmU2LWqYYtIGwCSIZjYQRjD6kw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "~1.13.3", "axios": "~1.13.3",
@@ -1621,6 +1621,18 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/ai": { "node_modules/ai": {
"version": "7.0.16", "version": "7.0.16",
"resolved": "https://registry.npmjs.org/ai/-/ai-7.0.16.tgz", "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.16.tgz",
@@ -1717,14 +1729,15 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.13.6", "version": "1.18.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz",
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.11", "follow-redirects": "^1.16.0",
"form-data": "^4.0.5", "form-data": "^4.0.5",
"proxy-from-env": "^1.1.0" "https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
} }
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
@@ -2004,7 +2017,6 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
}, },
@@ -2823,6 +2835,19 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.7.3", "version": "0.7.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
@@ -3353,8 +3378,7 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.15", "version": "3.3.15",
@@ -3735,10 +3759,13 @@
} }
}, },
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "1.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
"license": "MIT" "license": "MIT",
"engines": {
"node": ">=10"
}
}, },
"node_modules/pure-rand": { "node_modules/pure-rand": {
"version": "6.1.0", "version": "6.1.0",
+7 -1
View File
@@ -9,7 +9,7 @@
"dependencies": { "dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.3.202", "@anthropic-ai/claude-agent-sdk": "^0.3.202",
"@fastify/cookie": "^11.0.2", "@fastify/cookie": "^11.0.2",
"@larksuiteoapi/node-sdk": "^1.66.1", "@larksuiteoapi/node-sdk": "^1.70.0",
"@prisma/client": "^6.19.3", "@prisma/client": "^6.19.3",
"ai": "^7.0.16", "ai": "^7.0.16",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
@@ -22,12 +22,18 @@
"typescript": "^5.7.0", "typescript": "^5.7.0",
"vitest": "^4.1.10" "vitest": "^4.1.10"
}, },
"overrides": {
"@larksuiteoapi/node-sdk": {
"axios": "1.18.1"
}
},
"description": "Curriculum Project Hub — Feishu-group collaboration + Claude Code SDK agent runtime. Aligns to spec/System (ADR-0001..0004, 0017).", "description": "Curriculum Project Hub — Feishu-group collaboration + Claude Code SDK agent runtime. Aligns to spec/System (ADR-0001..0004, 0017).",
"scripts": { "scripts": {
"dev": "npm run prisma:migrate && tsx watch src/server.ts", "dev": "npm run prisma:migrate && tsx watch src/server.ts",
"build": "tsc -p tsconfig.json", "build": "tsc -p tsconfig.json",
"start": "npm run prisma:migrate && node dist/server.js", "start": "npm run prisma:migrate && node dist/server.js",
"check": "tsc -p tsconfig.json --noEmit", "check": "tsc -p tsconfig.json --noEmit",
"audit:production": "npm audit --omit=dev --audit-level=high",
"prisma:generate": "prisma generate --schema prisma/schema.prisma", "prisma:generate": "prisma generate --schema prisma/schema.prisma",
"prisma:validate": "DATABASE_URL=${DATABASE_URL:-postgresql://stub:stub@127.0.0.1:5432/stub} prisma validate --schema prisma/schema.prisma", "prisma:validate": "DATABASE_URL=${DATABASE_URL:-postgresql://stub:stub@127.0.0.1:5432/stub} prisma validate --schema prisma/schema.prisma",
"prisma:migrate": "DATABASE_URL=${DATABASE_URL:-postgresql://paradigm:paradigm@127.0.0.1:5432/paradigm} prisma migrate deploy --schema prisma/schema.prisma", "prisma:migrate": "DATABASE_URL=${DATABASE_URL:-postgresql://paradigm:paradigm@127.0.0.1:5432/paradigm} prisma migrate deploy --schema prisma/schema.prisma",
+125
View File
@@ -0,0 +1,125 @@
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
import * as lark from "@larksuiteoapi/node-sdk";
import { afterEach, describe, expect, it } from "vitest";
const openServers: ReturnType<typeof createServer>[] = [];
describe("real Lark SDK HTTP transport", () => {
afterEach(async () => {
await Promise.all(openServers.splice(0).map(async (server) => {
server.closeAllConnections();
await new Promise<void>((resolve, reject) => {
server.close((error) => error === undefined ? resolve() : reject(error));
});
}));
});
it("uses overridden Axios for token, authenticated success, and HTTP error paths", async () => {
const requests: Array<{
readonly method: string | undefined;
readonly url: string | undefined;
readonly authorization: string | undefined;
readonly body: string;
}> = [];
const server = createServer(async (request, response) => {
const body = await readBody(request);
requests.push({
method: request.method,
url: request.url,
authorization: request.headers.authorization,
body,
});
routeSmokeRequest(request, response, body);
});
openServers.push(server);
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
const address = server.address();
if (address === null || typeof address === "string") throw new Error("local HTTP server has no TCP address");
const domain = `http://127.0.0.1:${address.port}`;
const client = new lark.Client({
appId: "cli_transport_smoke",
appSecret: "transport-smoke-secret",
domain,
loggerLevel: lark.LoggerLevel.error,
});
await expect(client.request<{ readonly code: number; readonly data: { readonly ok: boolean } }>({
method: "GET",
url: "/open-apis/smoke/success",
params: { probe: "axios-override" },
})).resolves.toEqual({ code: 0, data: { ok: true } });
await expect(client.request({
method: "GET",
url: "/open-apis/smoke/failure",
})).rejects.toMatchObject({
response: {
status: 429,
data: { code: 999_429, msg: "forced smoke failure" },
},
});
expect(requests).toHaveLength(3);
expect(requests[0]).toMatchObject({
method: "POST",
url: "/open-apis/auth/v3/tenant_access_token/internal",
authorization: undefined,
});
expect(JSON.parse(requests[0]!.body)).toEqual({
app_id: "cli_transport_smoke",
app_secret: "transport-smoke-secret",
});
expect(requests[1]).toMatchObject({
method: "GET",
url: "/open-apis/smoke/success?probe=axios-override",
authorization: "Bearer tenant-transport-smoke",
});
expect(requests[2]).toMatchObject({
method: "GET",
url: "/open-apis/smoke/failure",
authorization: "Bearer tenant-transport-smoke",
});
});
});
async function readBody(request: IncomingMessage): Promise<string> {
const chunks: Buffer[] = [];
for await (const chunk of request) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
return Buffer.concat(chunks).toString("utf8");
}
function routeSmokeRequest(request: IncomingMessage, response: ServerResponse, body: string): void {
if (request.method === "POST" && request.url === "/open-apis/auth/v3/tenant_access_token/internal") {
const credential = JSON.parse(body) as { app_id?: unknown; app_secret?: unknown };
if (credential.app_id !== "cli_transport_smoke" || credential.app_secret !== "transport-smoke-secret") {
sendJson(response, 401, { code: 401, msg: "bad smoke credential" });
return;
}
sendJson(response, 200, {
code: 0,
msg: "ok",
tenant_access_token: "tenant-transport-smoke",
expire: 3_600,
});
return;
}
if (request.method === "GET" && request.url === "/open-apis/smoke/success?probe=axios-override") {
sendJson(response, 200, { code: 0, data: { ok: true } });
return;
}
if (request.method === "GET" && request.url === "/open-apis/smoke/failure") {
sendJson(response, 429, { code: 999_429, msg: "forced smoke failure" });
return;
}
sendJson(response, 404, { code: 404, msg: "unknown smoke route" });
}
function sendJson(response: ServerResponse, statusCode: number, body: unknown): void {
response.writeHead(statusCode, { "content-type": "application/json" });
response.end(JSON.stringify(body));
}