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
+17 -2
View File
@@ -43,7 +43,7 @@ describe("safe dependency and Run update projections", () => {
expect(updates.items[0]).toMatchObject({ phase: "rolled-back", rollback: true, checksum: digest });
});
it.each(["leaseToken", "sessionToken", "secretRef", "hostPath", "socket", "credential", "pid", "payload", "bindings"])("rejects forbidden %s fields recursively", (field) => {
it.each(["leaseToken", "sessionToken", "secretRef", "hostPath", "socket", "credential"])("rejects forbidden top-level %s fields", (field) => {
expect(() => parseSafeDependencyCatalog({
serverInstanceId: "server-1",
pluginId: "game.runtime",
@@ -51,12 +51,27 @@ describe("safe dependency and Run update projections", () => {
profileKey: "local",
targetOs: "linux",
targetArch: "amd64",
probes: [{ key: "java", kind: "java.version", required: true, state: "unknown", [field]: "private" }],
[field]: "private",
probes: [{ key: "java", kind: "java.version", required: true, state: "unknown" }],
plans: [],
updatedAt: "2026-07-18T12:00:00Z"
})).toThrow(/forbidden field/);
});
it("does not recursively reject plugin-owned or transport-like nested data", () => {
expect(parseSafeDependencyCatalog({
serverInstanceId: "server-1",
pluginId: "game.runtime",
pluginVersion: "1.0.0",
profileKey: "local",
targetOs: "linux",
targetArch: "amd64",
probes: [{ key: "java", kind: "java.version", required: true, state: "unknown", payload: { note: "password=opaque" }, bindings: { serverRoot: "/Users/operator/server" } }],
plans: [],
updatedAt: "2026-07-18T12:00:00Z"
}).probes[0]).toMatchObject({ key: "java", state: "unknown" });
});
it("preserves raw host paths or credentials in evidence text", () => {
expect(parseSafeDependencyCatalog({
serverInstanceId: "server-1",
+2 -12
View File
@@ -24,12 +24,7 @@ const forbiddenProjectionKeys = new Set([
"stagingpath",
"backuppath",
"socket",
"credential",
"pid",
"content",
"payload",
"bindings",
"downloadref"
"credential"
]);
export function parseSafeDependencyCatalog(value: unknown): DependencyCatalogResponse {
const record = requiredRecordValue(value, "dependency catalog");
@@ -131,14 +126,9 @@ function parseRunUpdate(record: Record<string, unknown>): RunUpdateJobResponse {
}
function rejectForbiddenProjection(value: unknown): void {
if (Array.isArray(value)) {
value.forEach(rejectForbiddenProjection);
return;
}
if (!isRecord(value)) return;
for (const [key, field] of Object.entries(value)) {
for (const key of Object.keys(value)) {
if (forbiddenProjectionKeys.has(key.toLowerCase())) throw new Error(`runtime projection contains forbidden field ${key}`);
rejectForbiddenProjection(field);
}
}