File size: 4,933 Bytes
253e783
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/** Tests node-host exec policy evaluation and approval decisions. */
import { describe, expect, it } from "vitest";
import { evaluateSystemRunPolicy, resolveExecApprovalDecision } from "./exec-policy.js";

type EvaluatePolicyParams = Parameters<typeof evaluateSystemRunPolicy>[0];
type EvaluatePolicyDecision = ReturnType<typeof evaluateSystemRunPolicy>;

const buildPolicyParams = (overrides: Partial<EvaluatePolicyParams>): EvaluatePolicyParams => {
  return {
    security: "allowlist",
    ask: "off",
    analysisOk: true,
    allowlistSatisfied: true,
    approvalDecision: null,
    approved: false,
    isWindows: false,
    cmdInvocation: false,
    shellWrapperInvocation: false,
    ...overrides,
  };
};

const expectDeniedDecision = (decision: EvaluatePolicyDecision) => {
  expect(decision.allowed).toBe(false);
  if (decision.allowed) {
    throw new Error("expected denied decision");
  }
  return decision;
};

const expectAllowedDecision = (decision: EvaluatePolicyDecision) => {
  expect(decision.allowed).toBe(true);
  if (!decision.allowed) {
    throw new Error("expected allowed decision");
  }
  return decision;
};

describe("resolveExecApprovalDecision", () => {
  it("accepts known approval decisions", () => {
    expect(resolveExecApprovalDecision("allow-once")).toBe("allow-once");
    expect(resolveExecApprovalDecision("allow-always")).toBe("allow-always");
  });

  it("normalizes unknown approval decisions to null", () => {
    expect(resolveExecApprovalDecision("deny")).toBeNull();
    expect(resolveExecApprovalDecision(undefined)).toBeNull();
  });
});

describe("evaluateSystemRunPolicy", () => {
  it("denies when security mode is deny", () => {
    const denied = expectDeniedDecision(
      evaluateSystemRunPolicy(buildPolicyParams({ security: "deny" })),
    );
    expect(denied.eventReason).toBe("security=deny");
    expect(denied.errorMessage).toBe("SYSTEM_RUN_DISABLED: security=deny");
  });

  it("requires approval when ask policy requires it", () => {
    const denied = expectDeniedDecision(
      evaluateSystemRunPolicy(buildPolicyParams({ ask: "always" })),
    );
    expect(denied.eventReason).toBe("approval-required");
    expect(denied.requiresAsk).toBe(true);
  });

  it("still requires approval when ask=always even with durable trust", () => {
    const denied = expectDeniedDecision(
      evaluateSystemRunPolicy(
        buildPolicyParams({
          security: "full",
          ask: "always",
          durableApprovalSatisfied: true,
        }),
      ),
    );
    expect(denied.eventReason).toBe("approval-required");
    expect(denied.requiresAsk).toBe(true);
  });

  it("allows allowlist miss when explicit approval is provided", () => {
    const allowed = expectAllowedDecision(
      evaluateSystemRunPolicy(
        buildPolicyParams({
          ask: "on-miss",
          analysisOk: false,
          allowlistSatisfied: false,
          approvalDecision: "allow-once",
        }),
      ),
    );
    expect(allowed.approvedByAsk).toBe(true);
  });

  it("denies allowlist misses without approval", () => {
    const denied = expectDeniedDecision(
      evaluateSystemRunPolicy(buildPolicyParams({ analysisOk: false, allowlistSatisfied: false })),
    );
    expect(denied.eventReason).toBe("allowlist-miss");
    expect(denied.errorMessage).toBe("SYSTEM_RUN_DENIED: allowlist miss");
  });

  it("keeps POSIX shell wrapper decisions tied to allowlist analysis", () => {
    const allowed = expectAllowedDecision(
      evaluateSystemRunPolicy(buildPolicyParams({ shellWrapperInvocation: true })),
    );
    expect(allowed.shellWrapperBlocked).toBe(false);
    expect(allowed.analysisOk).toBe(true);
    expect(allowed.allowlistSatisfied).toBe(true);
  });

  it("keeps Windows-specific guidance for cmd.exe wrappers", () => {
    const denied = expectDeniedDecision(
      evaluateSystemRunPolicy(
        buildPolicyParams({ isWindows: true, cmdInvocation: true, shellWrapperInvocation: true }),
      ),
    );
    expect(denied.shellWrapperBlocked).toBe(true);
    expect(denied.windowsShellWrapperBlocked).toBe(true);
    expect(denied.errorMessage).toContain("Windows shell wrappers like cmd.exe /c");
  });

  it("does not block Windows cmd.exe invocations without inline shell-wrapper transport", () => {
    const allowed = expectAllowedDecision(
      evaluateSystemRunPolicy(
        buildPolicyParams({ isWindows: true, cmdInvocation: true, shellWrapperInvocation: false }),
      ),
    );
    expect(allowed.shellWrapperBlocked).toBe(false);
    expect(allowed.windowsShellWrapperBlocked).toBe(false);
  });

  it("allows execution when policy checks pass", () => {
    const allowed = expectAllowedDecision(
      evaluateSystemRunPolicy(buildPolicyParams({ ask: "on-miss" })),
    );
    expect(allowed.requiresAsk).toBe(false);
    expect(allowed.analysisOk).toBe(true);
    expect(allowed.allowlistSatisfied).toBe(true);
  });
});