test: clear file-transfer policy broad matchers

This commit is contained in:
Peter Steinberger
2026-05-10 13:03:14 +01:00
parent a84014db49
commit e07c33d82d

View File

@@ -50,23 +50,35 @@ function withConfig(fileTransfer: Record<string, unknown> | undefined) {
}
}
function expectResultFields(result: unknown, fields: Record<string, unknown>) {
expect(typeof result).toBe("object");
expect(result).not.toBeNull();
if (typeof result !== "object" || result === null) {
throw new Error("policy result was not an object");
}
const record = result as Record<string, unknown>;
for (const [key, value] of Object.entries(fields)) {
expect(record[key]).toEqual(value);
}
}
describe("evaluateFilePolicy — default deny", () => {
it("returns NO_POLICY when no plugin config block is present", () => {
getRuntimeConfigMock.mockReturnValue({});
const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
expect(r).toMatchObject({ ok: false, code: "NO_POLICY", askable: false });
expectResultFields(r, { ok: false, code: "NO_POLICY", askable: false });
});
it("returns NO_POLICY when plugin policy block is missing", () => {
getRuntimeConfigMock.mockReturnValue({ plugins: { entries: { "file-transfer": {} } } });
const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
expect(r).toMatchObject({ ok: false, code: "NO_POLICY" });
expectResultFields(r, { ok: false, code: "NO_POLICY" });
});
it("returns NO_POLICY when no entry exists for the node and no '*' fallback", () => {
withConfig({ "other-node": { allowReadPaths: ["/tmp/**"] } });
const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
expect(r).toMatchObject({ ok: false, code: "NO_POLICY" });
expectResultFields(r, { ok: false, code: "NO_POLICY" });
});
it("prefers the current runtime config over a stale passed plugin config", () => {
@@ -93,7 +105,7 @@ describe("evaluateFilePolicy — default deny", () => {
},
},
});
expect(r).toMatchObject({ ok: true, reason: "matched-allow" });
expectResultFields(r, { ok: true, reason: "matched-allow" });
});
});
@@ -107,7 +119,7 @@ describe("evaluateFilePolicy — '..' traversal short-circuit", () => {
kind: "read",
path: "/allowed/../etc/passwd",
});
expect(r).toMatchObject({ ok: false, code: "POLICY_DENIED", askable: false });
expectResultFields(r, { ok: false, code: "POLICY_DENIED", askable: false });
expect(r.ok ? "" : r.reason).toMatch(/\.\./);
});
@@ -120,7 +132,7 @@ describe("evaluateFilePolicy — '..' traversal short-circuit", () => {
kind: "read",
path: "/tmp/foo/..",
});
expect(r).toMatchObject({ ok: false, code: "POLICY_DENIED" });
expectResultFields(r, { ok: false, code: "POLICY_DENIED" });
});
it("rejects bare '..'", () => {
@@ -128,7 +140,7 @@ describe("evaluateFilePolicy — '..' traversal short-circuit", () => {
n1: { allowReadPaths: ["/**"] },
});
const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: ".." });
expect(r).toMatchObject({ ok: false, code: "POLICY_DENIED" });
expectResultFields(r, { ok: false, code: "POLICY_DENIED" });
});
});
@@ -145,7 +157,7 @@ describe("evaluateFilePolicy — denyPaths always wins", () => {
kind: "read",
path: "/tmp/.ssh/id_rsa",
});
expect(r).toMatchObject({ ok: false, code: "POLICY_DENIED", askable: false });
expectResultFields(r, { ok: false, code: "POLICY_DENIED", askable: false });
expect(r.ok ? "" : r.reason).toMatch(/deny/);
});
@@ -161,7 +173,7 @@ describe("evaluateFilePolicy — denyPaths always wins", () => {
kind: "read",
path: "/var/secrets/api.key",
});
expect(r).toMatchObject({ ok: false, code: "POLICY_DENIED", askable: false });
expectResultFields(r, { ok: false, code: "POLICY_DENIED", askable: false });
});
});
@@ -183,26 +195,27 @@ describe("evaluateFilePolicy — allow matching", () => {
n1: { allowReadPaths: ["/tmp/**"], maxBytes: 1024 },
});
const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
expect(r).toMatchObject({ ok: true, maxBytes: 1024 });
expectResultFields(r, { ok: true, maxBytes: 1024 });
});
it("uses kind=write to consult allowWritePaths, not allowReadPaths", () => {
withConfig({
n1: { allowReadPaths: ["/tmp/**"], allowWritePaths: ["/srv/**"] },
});
expect(evaluateFilePolicy({ nodeId: "n1", kind: "write", path: "/srv/out.txt" })).toMatchObject(
{ ok: true },
);
expect(evaluateFilePolicy({ nodeId: "n1", kind: "write", path: "/tmp/out.txt" })).toMatchObject(
{ ok: false, code: "POLICY_DENIED" },
);
expectResultFields(evaluateFilePolicy({ nodeId: "n1", kind: "write", path: "/srv/out.txt" }), {
ok: true,
});
expectResultFields(evaluateFilePolicy({ nodeId: "n1", kind: "write", path: "/tmp/out.txt" }), {
ok: false,
code: "POLICY_DENIED",
});
});
it("propagates followSymlinks=false by default and =true when configured", () => {
withConfig({
n1: { allowReadPaths: ["/tmp/**"] },
});
expect(evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" })).toMatchObject({
expectResultFields(evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" }), {
ok: true,
followSymlinks: false,
});
@@ -210,7 +223,7 @@ describe("evaluateFilePolicy — allow matching", () => {
withConfig({
n2: { allowReadPaths: ["/tmp/**"], followSymlinks: true },
});
expect(evaluateFilePolicy({ nodeId: "n2", kind: "read", path: "/tmp/x" })).toMatchObject({
expectResultFields(evaluateFilePolicy({ nodeId: "n2", kind: "read", path: "/tmp/x" }), {
ok: true,
followSymlinks: true,
});
@@ -221,26 +234,28 @@ describe("evaluateFilePolicy — allow matching", () => {
withConfig({
n1: { allowReadPaths: ["~/Screenshots/**"] },
});
expect(
expectResultFields(
evaluateFilePolicy({
nodeId: "n1",
kind: "read",
path: path.join(home, "Screenshots", "shot.png"),
}),
).toMatchObject({ ok: true });
{ ok: true },
);
});
it("matches Windows node paths without gateway-local path semantics", () => {
withConfig({
n1: { allowReadPaths: ["C:/Users/me/**"] },
});
expect(
expectResultFields(
evaluateFilePolicy({
nodeId: "n1",
kind: "read",
path: "C:\\Users\\me\\file.txt",
}),
).toMatchObject({ ok: true });
{ ok: true },
);
});
});
@@ -250,7 +265,7 @@ describe("evaluateFilePolicy — ask modes", () => {
n1: { ask: "on-miss", allowReadPaths: ["/var/log/**"] },
});
const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
expect(r).toMatchObject({
expectResultFields(r, {
ok: false,
code: "POLICY_DENIED",
askable: true,
@@ -268,7 +283,7 @@ describe("evaluateFilePolicy — ask modes", () => {
},
});
const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
expect(r).toMatchObject({
expectResultFields(r, {
ok: false,
code: "POLICY_DENIED",
askable: true,
@@ -283,7 +298,7 @@ describe("evaluateFilePolicy — ask modes", () => {
n1: { ask: "on-miss", allowReadPaths: ["/tmp/**"] },
});
const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
expect(r).toMatchObject({ ok: true, reason: "matched-allow" });
expectResultFields(r, { ok: true, reason: "matched-allow" });
});
it("ask=always always returns ask-always (prompt on every call)", () => {
@@ -291,7 +306,7 @@ describe("evaluateFilePolicy — ask modes", () => {
n1: { ask: "always", allowReadPaths: ["/tmp/**"] },
});
const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
expect(r).toMatchObject({ ok: true, reason: "ask-always", askMode: "always" });
expectResultFields(r, { ok: true, reason: "ask-always", askMode: "always" });
});
it("ask=off returns non-askable POLICY_DENIED on miss", () => {
@@ -299,7 +314,7 @@ describe("evaluateFilePolicy — ask modes", () => {
n1: { ask: "off", allowReadPaths: ["/var/log/**"] },
});
const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
expect(r).toMatchObject({ ok: false, code: "POLICY_DENIED", askable: false });
expectResultFields(r, { ok: false, code: "POLICY_DENIED", askable: false });
});
it("invalid ask values normalize to off", () => {
@@ -307,7 +322,7 @@ describe("evaluateFilePolicy — ask modes", () => {
n1: { ask: "sometimes", allowReadPaths: ["/var/log/**"] },
});
const r = evaluateFilePolicy({ nodeId: "n1", kind: "read", path: "/tmp/x" });
expect(r).toMatchObject({ ok: false, askable: false });
expectResultFields(r, { ok: false, askable: false });
});
});
@@ -316,28 +331,30 @@ describe("evaluateFilePolicy — node-id resolution", () => {
withConfig({
"Lobster MacBook": { allowReadPaths: ["/tmp/**"] },
});
expect(
expectResultFields(
evaluateFilePolicy({
nodeId: "node-abc-123",
nodeDisplayName: "Lobster MacBook",
kind: "read",
path: "/tmp/x",
}),
).toMatchObject({ ok: true });
{ ok: true },
);
});
it("falls back to '*' wildcard when neither id nor displayName matches", () => {
withConfig({
"*": { allowReadPaths: ["/tmp/**"] },
});
expect(
expectResultFields(
evaluateFilePolicy({
nodeId: "n1",
nodeDisplayName: "anything",
kind: "read",
path: "/tmp/x",
}),
).toMatchObject({ ok: true });
{ ok: true },
);
});
});