Skip to content

Commit 9ec9fbf

Browse files
committed
refactor(whatsapp): use async fs-safe credential checks
1 parent de743c5 commit 9ec9fbf

6 files changed

Lines changed: 33 additions & 28 deletions

File tree

extensions/whatsapp/src/auth-store.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { getChildLogger } from "openclaw/plugin-sdk/runtime-env";
77
import { defaultRuntime, type RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
88
import { resolveOAuthDir } from "./auth-store.runtime.js";
99
import {
10+
assertWebCredsPathRegularFileOrMissing,
1011
hasWebCredsSync,
11-
isWebCredsPathRegularFileOrMissing,
1212
readWebCredsJsonRaw,
1313
readWebCredsJsonRawSync,
1414
resolveWebCredsBackupPath,
@@ -71,7 +71,9 @@ export async function restoreCredsFromBackupIfNeeded(authDir: string): Promise<b
7171
try {
7272
const credsPath = resolveWebCredsPath(authDir);
7373
const backupPath = resolveWebCredsBackupPath(authDir);
74-
if (!isWebCredsPathRegularFileOrMissing(credsPath)) {
74+
try {
75+
await assertWebCredsPathRegularFileOrMissing(credsPath);
76+
} catch {
7577
return false;
7678
}
7779
const raw = readCredsJsonRaw(credsPath);

extensions/whatsapp/src/creds-files.ts

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import path from "node:path";
22
import {
3+
assertNoSymlinkParents,
34
assertNoSymlinkParentsSync,
45
readRegularFile,
56
readRegularFileSync,
7+
statRegularFile,
68
statRegularFileSync,
79
} from "openclaw/plugin-sdk/security-runtime";
810

@@ -14,22 +16,30 @@ export function resolveWebCredsBackupPath(authDir: string): string {
1416
return path.join(authDir, "creds.json.bak");
1517
}
1618

17-
function assertWebCredsParentPathSafe(filePath: string): void {
19+
function resolveWebCredsParentCheck(filePath: string) {
1820
const dir = path.resolve(path.dirname(filePath));
19-
assertNoSymlinkParentsSync({
21+
return {
2022
rootDir: path.parse(dir).root,
2123
targetPath: dir,
2224
allowMissing: true,
2325
allowRootChildSymlink: true,
2426
requireDirectories: true,
2527
messagePrefix: "WhatsApp credential file path",
26-
});
28+
} as const;
2729
}
2830

29-
export function assertWebCredsPathRegularFileOrMissing(filePath: string): void {
31+
async function assertWebCredsParentPathSafe(filePath: string): Promise<void> {
32+
await assertNoSymlinkParents(resolveWebCredsParentCheck(filePath));
33+
}
34+
35+
function assertWebCredsParentPathSafeSync(filePath: string): void {
36+
assertNoSymlinkParentsSync(resolveWebCredsParentCheck(filePath));
37+
}
38+
39+
export async function assertWebCredsPathRegularFileOrMissing(filePath: string): Promise<void> {
3040
try {
31-
assertWebCredsParentPathSafe(filePath);
32-
statRegularFileSync(filePath);
41+
await assertWebCredsParentPathSafe(filePath);
42+
await statRegularFile(filePath);
3343
} catch (error) {
3444
throw new Error(
3545
`WhatsApp credential file path is unsafe; creds.json must be a regular file or missing: ${filePath}`,
@@ -38,18 +48,9 @@ export function assertWebCredsPathRegularFileOrMissing(filePath: string): void {
3848
}
3949
}
4050

41-
export function isWebCredsPathRegularFileOrMissing(filePath: string): boolean {
42-
try {
43-
assertWebCredsPathRegularFileOrMissing(filePath);
44-
return true;
45-
} catch {
46-
return false;
47-
}
48-
}
49-
5051
export function readWebCredsJsonRawSync(filePath: string): string | null {
5152
try {
52-
assertWebCredsParentPathSafe(filePath);
53+
assertWebCredsParentPathSafeSync(filePath);
5354
const { buffer, stat } = readRegularFileSync({
5455
filePath,
5556
});
@@ -61,7 +62,7 @@ export function readWebCredsJsonRawSync(filePath: string): string | null {
6162

6263
export async function readWebCredsJsonRaw(filePath: string): Promise<string | null> {
6364
try {
64-
assertWebCredsParentPathSafe(filePath);
65+
await assertWebCredsParentPathSafe(filePath);
6566
const { buffer, stat } = await readRegularFile({
6667
filePath,
6768
});
@@ -73,7 +74,7 @@ export async function readWebCredsJsonRaw(filePath: string): Promise<string | nu
7374

7475
export function statWebCredsFileSync(filePath: string): { mtimeMs: number; size: number } | null {
7576
try {
76-
assertWebCredsParentPathSafe(filePath);
77+
assertWebCredsParentPathSafeSync(filePath);
7778
const result = statRegularFileSync(filePath);
7879
if (result.missing || result.stat.size <= 1) {
7980
return null;
@@ -90,7 +91,7 @@ export function statWebCredsFileSync(filePath: string): { mtimeMs: number; size:
9091
export function hasWebCredsRegularFileSync(authDir: string): boolean {
9192
try {
9293
const credsPath = resolveWebCredsPath(authDir);
93-
assertWebCredsParentPathSafe(credsPath);
94+
assertWebCredsParentPathSafeSync(credsPath);
9495
return !statRegularFileSync(credsPath).missing;
9596
} catch {
9697
return false;

extensions/whatsapp/src/creds-persistence.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export async function writeWebCredsRawAtomically(params: {
1818
content: string;
1919
tempPrefix: string;
2020
}): Promise<void> {
21-
assertWebCredsPathRegularFileOrMissing(params.filePath);
21+
await assertWebCredsPathRegularFileOrMissing(params.filePath);
2222
await replaceFileAtomic({
2323
filePath: params.filePath,
2424
content: params.content,
@@ -28,7 +28,7 @@ export async function writeWebCredsRawAtomically(params: {
2828
syncTempFile: true,
2929
syncParentDir: true,
3030
beforeRename: async ({ filePath }) => {
31-
assertWebCredsPathRegularFileOrMissing(filePath);
31+
await assertWebCredsPathRegularFileOrMissing(filePath);
3232
},
3333
});
3434
}

extensions/whatsapp/src/session.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ const WHATSAPP_WEBSOCKET_PROXY_TARGET = "https://mmg.whatsapp.net/";
7272
const CREDS_FLUSH_TIMEOUT_MESSAGE =
7373
"Queued WhatsApp creds save did not finish before auth bootstrap; skipping repair and continuing with primary creds.";
7474

75-
function rejectUnsafeWebCredsPath(authDir: string): void {
76-
assertWebCredsPathRegularFileOrMissing(resolveWebCredsPath(authDir));
75+
async function rejectUnsafeWebCredsPath(authDir: string): Promise<void> {
76+
await assertWebCredsPathRegularFileOrMissing(resolveWebCredsPath(authDir));
7777
}
7878

7979
function enqueueSaveCreds(
@@ -148,17 +148,17 @@ export async function createWaSocket(
148148
);
149149
const logger = toPinoLikeLogger(baseLogger, verbose ? "info" : "silent");
150150
const authDir = resolveUserPath(opts.authDir ?? resolveDefaultWebAuthDir());
151-
rejectUnsafeWebCredsPath(authDir);
151+
await rejectUnsafeWebCredsPath(authDir);
152152
await ensureDir(authDir);
153153
const sessionLogger = getChildLogger({ module: "web-session" });
154154
const queueResult = await waitForCredsSaveQueueWithTimeout(authDir);
155155
if (queueResult === "timed_out") {
156156
sessionLogger.warn({ authDir }, CREDS_FLUSH_TIMEOUT_MESSAGE);
157157
} else {
158-
rejectUnsafeWebCredsPath(authDir);
158+
await rejectUnsafeWebCredsPath(authDir);
159159
await restoreCredsFromBackupIfNeeded(authDir);
160160
}
161-
rejectUnsafeWebCredsPath(authDir);
161+
await rejectUnsafeWebCredsPath(authDir);
162162
const { state } = await useMultiFileAuthState(authDir);
163163
const saveCreds = async () => {
164164
await writeCredsJsonAtomically(authDir, state.creds);

src/infra/fs-safe.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export {
3030
readRegularFile,
3131
readRegularFileSync,
3232
resolveRegularFileAppendFlags,
33+
statRegularFile,
3334
statRegularFileSync,
3435
} from "@openclaw/fs-safe/advanced";
3536
export {

src/plugin-sdk/security-runtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export {
3737
readRegularFileSync,
3838
resolveRegularFileAppendFlags,
3939
root,
40+
statRegularFile,
4041
statRegularFileSync,
4142
writeExternalFileWithinRoot,
4243
withTimeout,

0 commit comments

Comments
 (0)