Upgrade the runtime and native plugin API to OpenCode 2.0.24. Restore session-scoped learning, credential renewal, typed project queries and controlled frontend actions from the legacy Agent. Replace host Shell execution with an approved Landlock/seccomp executor and persistent per-session workspaces. Add typed Bocha search and Tianditu geocoding bridges using the owning session credential. Require a working kernel sandbox before startup and persist workspace files in deployments. Validation completed before this commit: TypeScript checks, 150 Bun tests, Docker build and UID 10001 sandbox execution, native Shell approval and session recovery. Tianditu succeeded; Bocha currently reports exhausted account quota. Unrelated investigation-report contract work is excluded.
49 lines
3.6 KiB
TypeScript
49 lines
3.6 KiB
TypeScript
import { afterEach, describe, expect, it } from "bun:test";
|
|
import express from "express";
|
|
import { type Server } from "node:http";
|
|
import { registerExternalInformationBridge } from "../../src/routes/externalInformationBridge.js";
|
|
import { ExternalInformationClient } from "../../src/product/external-information-client.js";
|
|
import { bindSessionCredential, releaseSessionCredential } from "../../src/product/credential-refresh.js";
|
|
import { removeRuntimeSessionContext, setRuntimeSessionContext } from "../../src/runtime/sessionContext.js";
|
|
|
|
let agent: Server | undefined;
|
|
let backend: Server | undefined;
|
|
const session = "external-information-bridge-test";
|
|
afterEach(async () => {
|
|
for (const server of [agent, backend]) if (server) await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
releaseSessionCredential(session); removeRuntimeSessionContext(session);
|
|
});
|
|
describe("external information bridge", () => {
|
|
it("calls search and geocode with the owning session and rejects browser, scope and URL injection", async () => {
|
|
let calls = 0;
|
|
const product = express(); product.use(express.json());
|
|
product.post(["/api/v1/web-searches", "/api/v1/geocoding-requests"], (req, res) => {
|
|
calls++;
|
|
expect(req.header("Authorization")).toBe("Bearer TEST_ONLY");
|
|
expect(req.header("X-Project-Id")).toBe("trusted-project");
|
|
res.json(req.path.endsWith("web-searches") ? { data: { results: [req.body.query] } } : { status: "0", location: { lon: "116.4", lat: "39.9" } });
|
|
});
|
|
backend = product.listen(0, "127.0.0.1");
|
|
await new Promise<void>((resolve) => backend!.once("listening", resolve));
|
|
const app = express(); app.use(express.json());
|
|
registerExternalInformationBridge(app, { internalToken: "TEST_ONLY", client: new ExternalInformationClient(`http://127.0.0.1:${(backend.address() as { port: number }).port}`) });
|
|
agent = app.listen(0, "127.0.0.1");
|
|
await new Promise<void>((resolve) => agent!.once("listening", resolve));
|
|
const base = `http://127.0.0.1:${(agent.address() as { port: number }).port}`;
|
|
const post = (name: string, body: unknown, headers: Record<string, string> = {}) => fetch(`${base}/internal/tools/${name}`, { method: "POST", headers: { "Content-Type": "application/json", "x-agent-internal-token": "TEST_ONLY", ...headers }, body: JSON.stringify(body) });
|
|
setRuntimeSessionContext({ sessionId: session, clientSessionId: session, actorKey: "owner", projectKey: "trusted-project", traceId: "test", purpose: "ordinary_chat" });
|
|
bindSessionCredential(session, { authorization: "Bearer TEST_ONLY", projectId: "trusted-project" });
|
|
expect((await post("web-search", { session_id: session, input: { query: "政策" } })).status).toBe(200);
|
|
const coordinates = await post("geocode", { session_id: session, input: { keyword: "北京" } });
|
|
expect(await coordinates.json()).toMatchObject({ center: [116.4, 39.9] });
|
|
expect((await post("web-search", { session_id: session, input: { query: "test" } }, { Origin: "http://browser" })).status).toBe(403);
|
|
expect((await post("web-search", { session_id: "missing", input: { query: "test" } })).status).toBe(403);
|
|
expect((await post("web-search", { session_id: session, input: { query: "test", url: "http://untrusted" } })).status).toBe(400);
|
|
expect((await post("geocode", { session_id: session, input: { keyword: "北京" }, project_id: "other" })).status).toBe(400);
|
|
expect(calls).toBe(2);
|
|
releaseSessionCredential(session);
|
|
expect((await post("geocode", { session_id: session, input: { keyword: "北京" } })).status).toBe(401);
|
|
expect(calls).toBe(2);
|
|
});
|
|
});
|