mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(infra): restore symlink rejection in tryReadSecretFileSync (#84711)
* fix(infra): restore symlink rejection in tryReadSecretFileSync
The local wrapper added in 9e4eca00ff swallowed all errors from
@openclaw/fs-safe@0.2.7's tryReadSecretFileSync via a bare try/catch,
silently downgrading every rejectSymlink: true caller (Telegram, LINE,
Zalo, IRC, Nextcloud Talk credential files) to accept symlinked
credential files. It also broke the infra-state CI shard's symlink
expectation that #84595 had just realigned with the new fail-closed
upstream contract.
Restore the direct re-export so the upstream contract surfaces:
undefined for blank/missing/not-found, FsSafeError for symlink,
oversize, non-regular file, and hardlink validation failures.
* test(plugins): align stale symlink tests with fail-closed contract
5 token/account resolver tests still asserted the pre-fs-safe-0.2.7
"silent skip" behavior (token: "", source: "none") on rejected symlinks;
they passed only because the swallow-all wrapper in secret-file.ts hid
the throw. Restoring the upstream fail-closed contract surfaces the
throw, so update the tests to expect FsSafeError.
inspectTelegramAccount reports credential status (its return type has an
explicit configured_unavailable state for "configured but unreadable"),
so its callsite is the right boundary to catch the FsSafeError and map
it to configured_unavailable rather than letting the throw bubble.
Affected:
- extensions/zalo/src/token.test.ts
- extensions/line/src/accounts.test.ts
- extensions/telegram/src/token.test.ts
- extensions/irc/src/accounts.test.ts
- extensions/nextcloud-talk/src/setup.test.ts
- extensions/telegram/src/account-inspect.ts (catch + report status)
This commit is contained in:
@@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- WhatsApp: update Baileys to `7.0.0-rc12`.
|
||||
- Dependencies: update `@openclaw/fs-safe` to `0.2.7` so OpenClaw's default Python-helper-off policy keeps best-effort Node write fallbacks for private stores, secret writes, run logs, and media attachments on Linux/macOS.
|
||||
- Infra/secrets: restore the fail-closed contract for `tryReadSecretFileSync` so credential loaders that pass `rejectSymlink: true` (Telegram, LINE, Zalo, IRC, Nextcloud Talk tokens) refuse symlinked credential files instead of silently accepting them, and the infra-state CI shard's secret-file symlink test passes again. Thanks @romneyda.
|
||||
- Browser: honor the configured image sanitization limit for screenshots and labeled snapshots so browser-captured images follow the same resize policy as other image results. (#84595)
|
||||
- Doctor: remove unrecognized `models.providers.*.models[*].compat.thinkingFormat` values during `doctor --fix` so stale provider model config can validate after upgrade. Fixes #77803.
|
||||
- Status: show the configured default, session-selected model, reason, clear hint, and docs link when a session remains pinned to a model that differs from `agents.defaults.model.primary`.
|
||||
|
||||
@@ -163,9 +163,7 @@ describe("resolveIrcAccount", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const account = resolveIrcAccount({ cfg });
|
||||
expect(account.password).toBe("");
|
||||
expect(account.passwordSource).toBe("none");
|
||||
expect(() => resolveIrcAccount({ cfg })).toThrow(/IRC password file.*must not be a symlink/);
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
|
||||
@@ -196,10 +196,9 @@ describe("LINE accounts", () => {
|
||||
},
|
||||
};
|
||||
|
||||
const account = resolveLineAccount({ cfg });
|
||||
expect(account.channelAccessToken).toBe("");
|
||||
expect(account.channelSecret).toBe("");
|
||||
expect(account.tokenSource).toBe("none");
|
||||
expect(() => resolveLineAccount({ cfg })).toThrow(
|
||||
/LINE credential file.*must not be a symlink/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -387,9 +387,9 @@ describe("resolveNextcloudTalkAccount", () => {
|
||||
},
|
||||
} as CoreConfig;
|
||||
|
||||
const account = resolveNextcloudTalkAccount({ cfg });
|
||||
expect(account.secret).toBe("");
|
||||
expect(account.secretSource).toBe("none");
|
||||
expect(() => resolveNextcloudTalkAccount({ cfg })).toThrow(
|
||||
/Nextcloud Talk bot secret file.*must not be a symlink/,
|
||||
);
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
|
||||
@@ -38,9 +38,18 @@ function inspectTokenFile(pathValue: unknown): {
|
||||
if (!tokenFile) {
|
||||
return null;
|
||||
}
|
||||
const token = tryReadSecretFileSync(tokenFile, "Telegram bot token", {
|
||||
rejectSymlink: true,
|
||||
});
|
||||
let token: string | undefined;
|
||||
try {
|
||||
token = tryReadSecretFileSync(tokenFile, "Telegram bot token", {
|
||||
rejectSymlink: true,
|
||||
});
|
||||
} catch {
|
||||
return {
|
||||
token: "",
|
||||
tokenSource: "tokenFile",
|
||||
tokenStatus: "configured_unavailable",
|
||||
};
|
||||
}
|
||||
return {
|
||||
token: token ?? "",
|
||||
tokenSource: "tokenFile",
|
||||
|
||||
@@ -101,9 +101,9 @@ describe("resolveTelegramToken", () => {
|
||||
fs.symlinkSync(tokenFile, tokenLink);
|
||||
|
||||
const cfg = { channels: { telegram: { tokenFile: tokenLink } } } as OpenClawConfig;
|
||||
const res = resolveTelegramToken(cfg);
|
||||
expect(res.token).toBe("");
|
||||
expect(res.source).toBe("none");
|
||||
expect(() => resolveTelegramToken(cfg)).toThrow(
|
||||
/channels\.telegram\.tokenFile.*must not be a symlink/,
|
||||
);
|
||||
});
|
||||
|
||||
it("does not fall back to config when tokenFile is missing", () => {
|
||||
|
||||
@@ -84,9 +84,7 @@ describe("resolveZaloToken", () => {
|
||||
const cfg = {
|
||||
tokenFile: tokenLink,
|
||||
} as ZaloConfig;
|
||||
const res = resolveZaloToken(cfg);
|
||||
expect(res.token).toBe("");
|
||||
expect(res.source).toBe("none");
|
||||
expect(() => resolveZaloToken(cfg)).toThrow(/Zalo token file.*must not be a symlink/);
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import "./fs-safe-defaults.js";
|
||||
import {
|
||||
readSecretFileSync as readSecretFileSyncImpl,
|
||||
tryReadSecretFileSync as tryReadSecretFileSyncImpl,
|
||||
} from "@openclaw/fs-safe/secret";
|
||||
import { readSecretFileSync as readSecretFileSyncImpl } from "@openclaw/fs-safe/secret";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
|
||||
export {
|
||||
@@ -10,22 +7,11 @@ export {
|
||||
PRIVATE_SECRET_DIR_MODE,
|
||||
PRIVATE_SECRET_FILE_MODE,
|
||||
readSecretFileSync,
|
||||
tryReadSecretFileSync,
|
||||
type SecretFileReadOptions,
|
||||
} from "@openclaw/fs-safe/secret";
|
||||
export { writeSecretFileAtomic as writePrivateSecretFileAtomic } from "@openclaw/fs-safe/secret";
|
||||
|
||||
export function tryReadSecretFileSync(
|
||||
filePath: string | undefined,
|
||||
label: string,
|
||||
options: Parameters<typeof tryReadSecretFileSyncImpl>[2] = {},
|
||||
): string | undefined {
|
||||
try {
|
||||
return tryReadSecretFileSyncImpl(filePath, label, options);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export type SecretFileReadResult =
|
||||
| {
|
||||
ok: true;
|
||||
|
||||
Reference in New Issue
Block a user