Tighten opaque plugin content boundaries

This commit is contained in:
npc0-hue
2026-09-03 18:24:39 +08:00
parent 80cddbf19d
commit 14cbc63e61
31 changed files with 452 additions and 558 deletions
+21 -12
View File
@@ -638,18 +638,18 @@ describe("plugin manifest validation", () => {
expect(actionErrors.some((error) => error.includes("page must declare remote.access.request"))).toBe(true);
});
it.each(["sqlText", "dsn", "hostPath", "shellCommand", "socketAddress", "accessToken", "credential"])("rejects unsafe query parameter schema field %s", (fieldName) => {
it.each(["sqlText", "dsn", "hostPath", "shellCommand", "socketAddress", "accessToken", "credential"])("keeps opaque query parameter schema field %s", (fieldName) => {
const errors = validateTemporaryBridgeManifest((_manifest, fixtureDir) => {
writeFixtureJSON(fixtureDir, "schemas/bridge/player-by-id.parameters.schema.json", bridgeObjectSchema({ [fieldName]: { type: "string", minLength: 1, maxLength: 120 } }, [fieldName]));
});
expect(errors.some((error) => error.includes("queryTemplates[0].parameterSchemaRef") && error.includes("not allowed"))).toBe(true);
expect(errors.some((error) => error.includes("queryTemplates[0].parameterSchemaRef"))).toBe(false);
});
it("rejects SQL text embedded in a query result schema", () => {
it("keeps opaque SQL-looking text embedded in a query result schema", () => {
const errors = validateTemporaryBridgeManifest((_manifest, fixtureDir) => {
writeFixtureJSON(fixtureDir, "schemas/bridge/player-by-id.result.schema.json", bridgeObjectSchema({ summary: { type: "string", minLength: 1, maxLength: 200, const: "SELECT id FROM players" } }, ["summary"]));
});
expect(errors.some((error) => error.includes("queryTemplates[0].resultSchemaRef") && error.includes("arbitrary SQL content"))).toBe(true);
expect(errors.some((error) => error.includes("queryTemplates[0].resultSchemaRef"))).toBe(false);
});
it("rejects missing bridge schema files end to end", () => {
@@ -673,16 +673,13 @@ describe("plugin manifest validation", () => {
expect(errors.some((error) => error.includes("payloadSchemaRef") && error.includes("not valid JSON"))).toBe(true);
});
it("rejects dangerous fields and values in payload, result, and snapshot schemas", () => {
it("keeps opaque fields and values in payload, result, and snapshot schemas", () => {
const errors = validateTemporaryBridgeManifest((_manifest, fixtureDir) => {
writeFixtureJSON(fixtureDir, "schemas/bridge/diagnostic-ping.schema.json", bridgeObjectSchema({ sqlText: { type: "string" } }, ["sqlText"]));
writeFixtureJSON(fixtureDir, "schemas/bridge/diagnostic-ping-result.schema.json", bridgeObjectSchema({ shellCommand: { type: "string", const: "bash -c whoami" } }, ["shellCommand"]));
writeFixtureJSON(fixtureDir, "schemas/bridge/players.schema.json", bridgeObjectSchema({ hostPath: { type: "string" }, mode: { type: "string", const: "run.socket" }, runCapability: { type: "string" } }, ["hostPath", "mode", "runCapability"]));
});
expect(errors.some((error) => error.includes("payloadSchemaRef") && error.includes("arbitrary SQL field"))).toBe(true);
expect(errors.some((error) => error.includes("resultSchemaRef") && error.includes("arbitrary shell"))).toBe(true);
expect(errors.some((error) => error.includes("snapshots[0].schemaRef") && error.includes("raw host path"))).toBe(true);
expect(errors.some((error) => error.includes("snapshots[0].schemaRef") && error.includes("unsafe executor capability"))).toBe(true);
expect(errors.some((error) => error.includes("payloadSchemaRef") || error.includes("resultSchemaRef") || error.includes("snapshots[0].schemaRef"))).toBe(false);
});
it("requires bounded object schemas for every bridge reference", () => {
@@ -938,9 +935,21 @@ describe("plugin SDK", () => {
storageBehavior: "platform-memory-transfer-session"
});
expect(reference).toMatchObject({ artifactId: "artifact-1", rangeSupported: true });
expect(JSON.stringify(reference)).not.toContain("/Users/");
expect(JSON.stringify(reference)).not.toContain("storage://");
expect(JSON.stringify(reference)).not.toContain("Bearer ");
expect(
parseArtifactReference({
artifactId: "artifact-1",
filename: "/Users/operator/password=opaque.bin",
contentType: "application/octet-stream",
sizeBytes: "64",
checksum: "sha256:abc",
downloadUrl: "/api/v1/artifacts/artifact-1/content",
expiresAt: "2026-07-03T00:15:00Z",
rangeSupported: "true",
chunkSizeBytes: "1048576",
storageBehavior: "plugin-owned opaque storage:// label"
})
).toMatchObject({ filename: "/Users/operator/password=opaque.bin", storageBehavior: "plugin-owned opaque storage:// label" });
expect(
parseArtifactReference({