惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
H
Help Net Security
腾讯CDC
宝玉的分享
宝玉的分享
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LangChain Blog
爱范儿
爱范儿
T
The Blog of Author Tim Ferriss
J
Java Code Geeks
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
MyScale Blog
MyScale Blog
Engineering at Meta
Engineering at Meta
N
Netflix TechBlog - Medium
D
Docker
V
V2EX
Last Week in AI
Last Week in AI
G
Google Developers Blog
IT之家
IT之家
C
Check Point Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
博客园 - 叶小钗
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 聂微东

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(gateway.tls): reject empty/whitespace certPath and ke...
miorbnli · 2026-06-23 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -0,0 +1,47 @@

1+

// Schema-level tests for gateway.tls certPath and keyPath validation.

2+

import { describe, expect, it } from "vitest";

3+

import { validateConfigObject } from "./validation.js";

4+
5+

describe("gateway.tls schema", () => {

6+

it("rejects empty certPath", () => {

7+

const res = validateConfigObject({ gateway: { tls: { enabled: true, certPath: "" } } });

8+

expect(res.ok).toBe(false);

9+

if (!res.ok) {

10+

expect(res.issues[0]?.path).toMatch(/certPath/);

11+

}

12+

});

13+
14+

it("rejects whitespace-only keyPath", () => {

15+

const res = validateConfigObject({ gateway: { tls: { enabled: true, keyPath: " " } } });

16+

expect(res.ok).toBe(false);

17+

});

18+
19+

it("accepts a non-empty certPath", () => {

20+

const res = validateConfigObject({

21+

gateway: { tls: { enabled: true, certPath: "/etc/ssl/cert.pem" } },

22+

});

23+

expect(res.ok).toBe(true);

24+

});

25+
26+

it("preserves exact bytes of a non-empty certPath (no silent trim)", () => {

27+

const res = validateConfigObject({

28+

gateway: { tls: { enabled: true, certPath: " /etc/ssl/cert.pem " } },

29+

});

30+

expect(res.ok).toBe(true);

31+

if (res.ok) {

32+

// Schema must validate without transforming the string; runtime path

33+

// resolution owns normalization, so leading/trailing spaces are preserved.

34+

expect(res.config.gateway?.tls?.certPath).toBe(" /etc/ssl/cert.pem ");

35+

}

36+

});

37+
38+

it("preserves exact bytes of a non-empty keyPath (no silent trim)", () => {

39+

const res = validateConfigObject({

40+

gateway: { tls: { enabled: true, keyPath: " /etc/ssl/private/server.key " } },

41+

});

42+

expect(res.ok).toBe(true);

43+

if (res.ok) {

44+

expect(res.config.gateway?.tls?.keyPath).toBe(" /etc/ssl/private/server.key ");

45+

}

46+

});

47+

});

Original file line numberDiff line numberDiff line change

@@ -1123,8 +1123,18 @@ export const OpenClawSchema = z

11231123

.object({

11241124

enabled: z.boolean().optional(),

11251125

autoGenerate: z.boolean().optional(),

1126-

certPath: z.string().optional(),

1127-

keyPath: z.string().optional(),

1126+

// Reject blank values without transforming the string. Trimming here would

1127+

// silently rewrite a legitimate filesystem path that contains leading or

1128+

// trailing spaces and persist the trimmed value into validated config;

1129+

// runtime path resolution (resolveUserPath) owns all normalization.

1130+

certPath: z

1131+

.string()

1132+

.optional()

1133+

.refine((v) => v === undefined || v.trim().length > 0, "certPath must not be blank"),

1134+

keyPath: z

1135+

.string()

1136+

.optional()

1137+

.refine((v) => v === undefined || v.trim().length > 0, "keyPath must not be blank"),

11281138

caPath: z.string().optional(),

11291139

})

11301140

.optional(),

Original file line numberDiff line numberDiff line change

@@ -149,4 +149,47 @@ describe("loadGatewayTlsRuntime", () => {

149149

expect(result.keyPath).toBe(keyPath);

150150

expect(result.error).toContain("gateway tls: failed to load cert");

151151

});

152+
153+

it("falls back to default paths when certPath and keyPath are empty strings", async () => {

154+

const result = await loadGatewayTlsRuntime({

155+

enabled: true,

156+

certPath: "",

157+

keyPath: "",

158+

autoGenerate: false,

159+

});

160+
161+

// Empty paths must not reach downstream — they must be replaced with defaults.

162+

expect(result.certPath).toBeTruthy();

163+

expect(result.certPath).not.toBe("");

164+

expect(result.keyPath).toBeTruthy();

165+

expect(result.keyPath).not.toBe("");

166+

});

167+
168+

it("falls back to default paths when certPath and keyPath are whitespace-only", async () => {

169+

const result = await loadGatewayTlsRuntime({

170+

enabled: true,

171+

certPath: " ",

172+

keyPath: "\t",

173+

autoGenerate: false,

174+

});

175+
176+

expect(result.certPath).toBeTruthy();

177+

expect(result.certPath).not.toBe(" ");

178+

expect(result.keyPath).toBeTruthy();

179+

expect(result.keyPath).not.toBe("\t");

180+

});

181+
182+

it("does not fall back for non-empty paths with leading/trailing spaces", async () => {

183+

const result = await loadGatewayTlsRuntime({

184+

enabled: true,

185+

certPath: " /etc/ssl/cert.pem ",

186+

keyPath: " /etc/ssl/private/server.key ",

187+

autoGenerate: false,

188+

});

189+
190+

// Non-empty paths are passed through verbatim; resolveUserPath owns

191+

// normalization (it trims), so they must not fall back to default names.

192+

expect(result.certPath).not.toContain("gateway-cert.pem");

193+

expect(result.keyPath).not.toContain("gateway-key.pem");

194+

});

152195

});

Original file line numberDiff line numberDiff line change

@@ -80,8 +80,20 @@ export async function loadGatewayTlsRuntime(

8080
8181

const autoGenerate = cfg.autoGenerate !== false;

8282

const baseDir = path.join(CONFIG_DIR, "gateway", "tls");

83-

const certPath = resolveUserPath(cfg.certPath ?? path.join(baseDir, "gateway-cert.pem"));

84-

const keyPath = resolveUserPath(cfg.keyPath ?? path.join(baseDir, "gateway-key.pem"));

83+

// Only blank/whitespace values fall back to the default. Any non-empty path is

84+

// passed through verbatim so resolveUserPath owns all normalization (it trims

85+

// and expands ~); trimming here would duplicate it and silently rewrite paths

86+

// that contain leading/trailing spaces.

87+

const certPath = resolveUserPath(

88+

typeof cfg.certPath === "string" && cfg.certPath.trim()

89+

? cfg.certPath

90+

: path.join(baseDir, "gateway-cert.pem"),

91+

);

92+

const keyPath = resolveUserPath(

93+

typeof cfg.keyPath === "string" && cfg.keyPath.trim()

94+

? cfg.keyPath

95+

: path.join(baseDir, "gateway-key.pem"),

96+

);

8597

const caPath = cfg.caPath ? resolveUserPath(cfg.caPath) : undefined;

8698
8799

const hasCert = await pathExists(certPath);