

























@@ -114,6 +114,7 @@ const runExecProcessMock = vi.hoisted(() => vi.fn());
114114const sendExecApprovalFollowupResultMock = vi.hoisted(() =>
115115vi.fn<SendExecApprovalFollowupResult>(async () => undefined),
116116);
117+const shouldResolveExecApprovalUnavailableInlineMock = vi.hoisted(() => vi.fn(() => false));
117118const enforceStrictInlineEvalApprovalBoundaryMock = vi.hoisted(() =>
118119vi.fn<StrictInlineEvalBoundary>((value) => ({
119120approvedByAsk: value.approvedByAsk,
@@ -168,7 +169,7 @@ vi.mock("./bash-tools.exec-host-shared.js", () => ({
168169enforceStrictInlineEvalApprovalBoundary: enforceStrictInlineEvalApprovalBoundaryMock,
169170resolveApprovalDecisionOrUndefined: resolveApprovalDecisionOrUndefinedMock,
170171sendExecApprovalFollowupResult: sendExecApprovalFollowupResultMock,
171-shouldResolveExecApprovalUnavailableInline: vi.fn(() => false),
172+shouldResolveExecApprovalUnavailableInline: shouldResolveExecApprovalUnavailableInlineMock,
172173}));
173174174175vi.mock("./bash-tools.exec-runtime.js", () => ({
@@ -296,6 +297,8 @@ describe("processGatewayAllowlist", () => {
296297recordAllowlistMatchesUseMock.mockReset();
297298resolveApprovalDecisionOrUndefinedMock.mockReset();
298299resolveApprovalDecisionOrUndefinedMock.mockResolvedValue(undefined);
300+shouldResolveExecApprovalUnavailableInlineMock.mockReset();
301+shouldResolveExecApprovalUnavailableInlineMock.mockReturnValue(false);
299302resolveExecHostApprovalContextMock.mockReset();
300303resolveExecHostApprovalContextMock.mockReturnValue({
301304approvals: { allowlist: [], file: { version: 1, agents: {} } },
@@ -449,6 +452,87 @@ describe("processGatewayAllowlist", () => {
449452expect(serialized).not.toContain("agent-1");
450453});
451454455+it("emits a denied security event for inline unavailable approval denials", async () => {
456+shouldResolveExecApprovalUnavailableInlineMock.mockReturnValue(true);
457+createExecApprovalDecisionStateMock.mockReturnValue({
458+baseDecision: { timedOut: false },
459+approvedByAsk: false,
460+deniedReason: "user-denied",
461+});
462+enforceStrictInlineEvalApprovalBoundaryMock.mockReturnValue({
463+approvedByAsk: false,
464+deniedReason: "user-denied",
465+});
466+const captured = captureSecurityEvents();
467+468+try {
469+await expect(
470+runGatewayAllowlist({
471+command: "deploy --token raw-secret-value",
472+agentId: "agent-1",
473+}),
474+).rejects.toThrow("denied");
475+} finally {
476+captured.stop();
477+}
478+479+expect(captured.events).toHaveLength(2);
480+expect(captured.events[1]).toMatchObject({
481+action: "exec.approval.denied",
482+outcome: "denied",
483+severity: "medium",
484+reason: "user-denied",
485+policy: { id: "exec.approval", decision: "deny", reason: "user-denied" },
486+attributes: {
487+has_agent_id: true,
488+},
489+});
490+const serialized = JSON.stringify(captured.events);
491+expect(serialized).not.toContain("deploy");
492+expect(serialized).not.toContain("raw-secret-value");
493+expect(serialized).not.toContain("agent-1");
494+});
495+496+it("emits an approved security event for inline unavailable approval approvals", async () => {
497+shouldResolveExecApprovalUnavailableInlineMock.mockReturnValue(true);
498+createExecApprovalDecisionStateMock.mockReturnValue({
499+baseDecision: { timedOut: false },
500+approvedByAsk: true,
501+deniedReason: null,
502+});
503+enforceStrictInlineEvalApprovalBoundaryMock.mockReturnValue({
504+approvedByAsk: true,
505+deniedReason: null,
506+});
507+const captured = captureSecurityEvents();
508+509+let result: Awaited<ReturnType<typeof runGatewayAllowlist>>;
510+try {
511+result = await runGatewayAllowlist({
512+command: "echo ok",
513+agentId: "agent-1",
514+});
515+} finally {
516+captured.stop();
517+}
518+519+expect(result!).toEqual({
520+execCommandOverride: undefined,
521+allowWithoutEnforcedCommand: true,
522+});
523+expect(captured.events).toHaveLength(2);
524+expect(captured.events[1]).toMatchObject({
525+action: "exec.approval.approved",
526+outcome: "success",
527+severity: "medium",
528+policy: { id: "exec.approval", decision: "allow" },
529+attributes: {
530+has_agent_id: true,
531+},
532+});
533+expect(JSON.stringify(captured.events)).not.toContain("agent-1");
534+});
535+452536it("auto-reviews simple read-only approval misses without prompting", async () => {
453537requiresExecApprovalMock.mockReturnValue(true);
454538evaluateShellAllowlistMock.mockReturnValue({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。