Skip to content

Commit e2a4814

Browse files
committed
test(secret-file): cover NickServ + account-level symlinks, narrow inspect catch
Followup nits from the #84711 review: - Narrow the inspectTokenFile catch in extensions/telegram/src/account-inspect.ts to FsSafeError so only fs-safe validation throws map to configured_unavailable; any other throw (programmer error, unexpected I/O) is rethrown. - Add a regression test for the IRC NickServ password file symlink rejection path (extensions/irc/src/accounts.ts:118), paralleling the existing top-level passwordFile test. - Add a regression test for the Telegram account-level tokenFile symlink rejection path (extensions/telegram/src/token.ts:149), paralleling the existing channel-level tokenFile test. Behavior was already correct after #84711; this just locks coverage and tightens the catch.
1 parent 90fd26b commit e2a4814

3 files changed

Lines changed: 52 additions & 1 deletion

File tree

extensions/irc/src/accounts.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,31 @@ describe("resolveIrcAccount", () => {
167167
fs.rmSync(dir, { recursive: true, force: true });
168168
});
169169

170+
it.runIf(process.platform !== "win32")("rejects symlinked NickServ password files", () => {
171+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-irc-nickserv-"));
172+
const passwordFile = path.join(dir, "nickserv-password.txt");
173+
const passwordLink = path.join(dir, "nickserv-password-link.txt");
174+
fs.writeFileSync(passwordFile, "nickserv-pass\n", "utf8");
175+
fs.symlinkSync(passwordFile, passwordLink);
176+
177+
const cfg = asConfig({
178+
channels: {
179+
irc: {
180+
host: "irc.example.com",
181+
nick: "claw",
182+
nickserv: {
183+
passwordFile: passwordLink,
184+
},
185+
},
186+
},
187+
});
188+
189+
expect(() => resolveIrcAccount({ cfg })).toThrow(
190+
/IRC NickServ password file.*must not be a symlink/,
191+
);
192+
fs.rmSync(dir, { recursive: true, force: true });
193+
});
194+
170195
it("preserves shared NickServ config when an account overrides one NickServ field", () => {
171196
const account = resolveIrcAccount({
172197
cfg: asConfig({

extensions/telegram/src/account-inspect.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
normalizeSecretInputString,
1010
} from "openclaw/plugin-sdk/secret-input";
1111
import { coerceSecretRef } from "openclaw/plugin-sdk/secret-input-runtime";
12+
import { FsSafeError } from "openclaw/plugin-sdk/security-runtime";
1213
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
1314
import {
1415
mergeTelegramAccountConfig,
@@ -43,7 +44,10 @@ function inspectTokenFile(pathValue: unknown): {
4344
token = tryReadSecretFileSync(tokenFile, "Telegram bot token", {
4445
rejectSymlink: true,
4546
});
46-
} catch {
47+
} catch (error) {
48+
if (!(error instanceof FsSafeError)) {
49+
throw error;
50+
}
4751
return {
4852
token: "",
4953
tokenSource: "tokenFile",

extensions/telegram/src/token.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,28 @@ describe("resolveTelegramToken", () => {
106106
);
107107
});
108108

109+
it.runIf(process.platform !== "win32")("rejects symlinked account-level tokenFile paths", () => {
110+
vi.stubEnv("TELEGRAM_BOT_TOKEN", "");
111+
const dir = createTempDir();
112+
const tokenFile = path.join(dir, "token.txt");
113+
const tokenLink = path.join(dir, "token-link.txt");
114+
fs.writeFileSync(tokenFile, "file-token\n", "utf-8");
115+
fs.symlinkSync(tokenFile, tokenLink);
116+
117+
const cfg = {
118+
channels: {
119+
telegram: {
120+
accounts: {
121+
work: { tokenFile: tokenLink },
122+
},
123+
},
124+
},
125+
} as OpenClawConfig;
126+
expect(() => resolveTelegramToken(cfg, { accountId: "work" })).toThrow(
127+
/channels\.telegram\.accounts\.work\.tokenFile.*must not be a symlink/,
128+
);
129+
});
130+
109131
it("does not fall back to config when tokenFile is missing", () => {
110132
vi.stubEnv("TELEGRAM_BOT_TOKEN", "");
111133
const dir = createTempDir();

0 commit comments

Comments
 (0)