




















1+import { describe, expect, it } from "vitest";
2+import type { OpenClawConfig } from "../../../config/types.openclaw.js";
3+import { repairHooksTokenReuseGatewayAuth } from "./hooks-token-reuse-repair.js";
4+5+const ROTATED_HOOKS_TOKEN = "rotated-hooks-token-1234567890";
6+7+function repair(cfg: OpenClawConfig, env: NodeJS.ProcessEnv = {}) {
8+return repairHooksTokenReuseGatewayAuth(cfg, env, () => ROTATED_HOOKS_TOKEN);
9+}
10+11+describe("repairHooksTokenReuseGatewayAuth", () => {
12+it("rotates hooks.token when it reuses active gateway token auth from env", async () => {
13+const result = await repair(
14+{
15+hooks: {
16+enabled: true,
17+token: "shared-gateway-token-1234567890",
18+},
19+},
20+{
21+OPENCLAW_GATEWAY_TOKEN: "shared-gateway-token-1234567890",
22+} as NodeJS.ProcessEnv,
23+);
24+25+expect(result.config.hooks?.token).toBe(ROTATED_HOOKS_TOKEN);
26+expect(result.changes).toContain(
27+"Rotated hooks.token because it reused active Gateway shared-secret auth. Update external hook senders to use the new hooks.token.",
28+);
29+});
30+31+it("rotates hooks.token when it reuses gateway password auth", async () => {
32+const result = await repair({
33+gateway: {
34+auth: {
35+mode: "password",
36+password: "shared-gateway-password-1234567890", // pragma: allowlist secret
37+},
38+},
39+hooks: {
40+enabled: true,
41+token: "shared-gateway-password-1234567890",
42+},
43+});
44+45+expect(result.config.hooks?.token).toBe(ROTATED_HOOKS_TOKEN);
46+});
47+48+it("rotates hooks.token when it reuses gateway password auth from a SecretRef", async () => {
49+const result = await repair(
50+{
51+secrets: {
52+providers: {
53+default: { source: "env" },
54+},
55+},
56+gateway: {
57+auth: {
58+mode: "password",
59+password: { source: "env", provider: "default", id: "GW_PASSWORD" },
60+},
61+},
62+hooks: {
63+enabled: true,
64+token: "shared-gateway-password-1234567890",
65+},
66+},
67+{
68+GW_PASSWORD: "shared-gateway-password-1234567890", // pragma: allowlist secret
69+} as NodeJS.ProcessEnv,
70+);
71+72+expect(result.config.hooks?.token).toBe(ROTATED_HOOKS_TOKEN);
73+});
74+75+it("does not abort when active gateway auth SecretRef is unavailable", async () => {
76+const cfg = {
77+secrets: {
78+providers: {
79+default: { source: "env" },
80+},
81+},
82+gateway: {
83+auth: {
84+mode: "password",
85+password: { source: "env", provider: "default", id: "MISSING_GW_PASSWORD" },
86+},
87+},
88+hooks: {
89+enabled: true,
90+token: "shared-gateway-password-1234567890",
91+},
92+} satisfies OpenClawConfig;
93+94+await expect(repair(cfg, {} as NodeJS.ProcessEnv)).resolves.toEqual({
95+config: cfg,
96+changes: [],
97+});
98+});
99+100+it("does not execute gateway auth SecretRefs during repair", async () => {
101+const cfg = {
102+secrets: {
103+providers: {
104+vault: {
105+source: "exec",
106+command: "node",
107+args: ["-e", "process.stdout.write('shared-gateway-password-1234567890')"],
108+},
109+},
110+},
111+gateway: {
112+auth: {
113+mode: "password",
114+password: { source: "exec", provider: "vault", id: "GW_PASSWORD" },
115+},
116+},
117+hooks: {
118+enabled: true,
119+token: "shared-gateway-password-1234567890",
120+},
121+} satisfies OpenClawConfig;
122+123+await expect(repair(cfg, {} as NodeJS.ProcessEnv)).resolves.toEqual({
124+config: cfg,
125+changes: [],
126+});
127+});
128+129+it("rotates hooks.token when it reuses trusted-proxy local password fallback", async () => {
130+const result = await repair({
131+gateway: {
132+auth: {
133+mode: "trusted-proxy",
134+trustedProxy: { userHeader: "x-forwarded-user" },
135+password: "trusted-proxy-local-password-1234567890", // pragma: allowlist secret
136+},
137+},
138+hooks: {
139+enabled: true,
140+token: "trusted-proxy-local-password-1234567890",
141+},
142+});
143+144+expect(result.config.hooks?.token).toBe(ROTATED_HOOKS_TOKEN);
145+});
146+147+it("rotates hooks.token when it reuses trusted-proxy password auth from a SecretRef", async () => {
148+const result = await repair(
149+{
150+secrets: {
151+providers: {
152+default: { source: "env" },
153+},
154+},
155+gateway: {
156+auth: {
157+mode: "trusted-proxy",
158+trustedProxy: { userHeader: "x-forwarded-user" },
159+password: { source: "env", provider: "default", id: "GW_PASSWORD" },
160+},
161+},
162+hooks: {
163+enabled: true,
164+token: "trusted-proxy-local-password-1234567890",
165+},
166+},
167+{
168+GW_PASSWORD: "trusted-proxy-local-password-1234567890", // pragma: allowlist secret
169+} as NodeJS.ProcessEnv,
170+);
171+172+expect(result.config.hooks?.token).toBe(ROTATED_HOOKS_TOKEN);
173+});
174+175+it("does not rotate disabled hooks or distinct hook tokens", async () => {
176+const disabled = {
177+gateway: {
178+auth: {
179+mode: "token",
180+token: "shared-gateway-token-1234567890",
181+},
182+},
183+hooks: {
184+enabled: false,
185+token: "shared-gateway-token-1234567890",
186+},
187+} satisfies OpenClawConfig;
188+const distinct = {
189+gateway: {
190+auth: {
191+mode: "token",
192+token: "shared-gateway-token-1234567890",
193+},
194+},
195+hooks: {
196+enabled: true,
197+token: "distinct-hooks-token-1234567890",
198+},
199+} satisfies OpenClawConfig;
200+201+await expect(repair(disabled)).resolves.toEqual({ config: disabled, changes: [] });
202+await expect(repair(distinct)).resolves.toEqual({ config: distinct, changes: [] });
203+});
204+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。