mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 01:37:27 +00:00
fix(security): harden account-key handling against prototype pollution
This commit is contained in:
@@ -20,6 +20,15 @@ describe("account id normalization", () => {
|
||||
expect(normalizeAccountId(" Prod/US East ")).toBe("prod-us-east");
|
||||
});
|
||||
|
||||
it("rejects prototype-pollution key vectors", () => {
|
||||
expect(normalizeAccountId("__proto__")).toBe(DEFAULT_ACCOUNT_ID);
|
||||
expect(normalizeAccountId("constructor")).toBe(DEFAULT_ACCOUNT_ID);
|
||||
expect(normalizeAccountId("prototype")).toBe(DEFAULT_ACCOUNT_ID);
|
||||
expect(normalizeOptionalAccountId("__proto__")).toBeUndefined();
|
||||
expect(normalizeOptionalAccountId("constructor")).toBeUndefined();
|
||||
expect(normalizeOptionalAccountId("prototype")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("preserves optional semantics without forcing default", () => {
|
||||
expect(normalizeOptionalAccountId(undefined)).toBeUndefined();
|
||||
expect(normalizeOptionalAccountId(" ")).toBeUndefined();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isBlockedObjectKey } from "../infra/prototype-keys.js";
|
||||
|
||||
export const DEFAULT_ACCOUNT_ID = "default";
|
||||
|
||||
const VALID_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
||||
@@ -17,12 +19,20 @@ function canonicalizeAccountId(value: string): string {
|
||||
.slice(0, 64);
|
||||
}
|
||||
|
||||
function normalizeCanonicalAccountId(value: string): string | undefined {
|
||||
const canonical = canonicalizeAccountId(value);
|
||||
if (!canonical || isBlockedObjectKey(canonical)) {
|
||||
return undefined;
|
||||
}
|
||||
return canonical;
|
||||
}
|
||||
|
||||
export function normalizeAccountId(value: string | undefined | null): string {
|
||||
const trimmed = (value ?? "").trim();
|
||||
if (!trimmed) {
|
||||
return DEFAULT_ACCOUNT_ID;
|
||||
}
|
||||
return canonicalizeAccountId(trimmed) || DEFAULT_ACCOUNT_ID;
|
||||
return normalizeCanonicalAccountId(trimmed) || DEFAULT_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
export function normalizeOptionalAccountId(value: string | undefined | null): string | undefined {
|
||||
@@ -30,5 +40,5 @@ export function normalizeOptionalAccountId(value: string | undefined | null): st
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
return canonicalizeAccountId(trimmed) || undefined;
|
||||
return normalizeCanonicalAccountId(trimmed) || undefined;
|
||||
}
|
||||
|
||||
19
src/routing/account-lookup.test.ts
Normal file
19
src/routing/account-lookup.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveAccountEntry } from "./account-lookup.js";
|
||||
|
||||
describe("resolveAccountEntry", () => {
|
||||
it("resolves direct and case-insensitive account keys", () => {
|
||||
const accounts = {
|
||||
default: { id: "default" },
|
||||
Business: { id: "business" },
|
||||
};
|
||||
expect(resolveAccountEntry(accounts, "default")).toEqual({ id: "default" });
|
||||
expect(resolveAccountEntry(accounts, "business")).toEqual({ id: "business" });
|
||||
});
|
||||
|
||||
it("ignores prototype-chain values", () => {
|
||||
const inherited = { default: { id: "polluted" } };
|
||||
const accounts = Object.create(inherited) as Record<string, { id: string }>;
|
||||
expect(resolveAccountEntry(accounts, "default")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
14
src/routing/account-lookup.ts
Normal file
14
src/routing/account-lookup.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export function resolveAccountEntry<T>(
|
||||
accounts: Record<string, T> | undefined,
|
||||
accountId: string,
|
||||
): T | undefined {
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
if (Object.hasOwn(accounts, accountId)) {
|
||||
return accounts[accountId];
|
||||
}
|
||||
const normalized = accountId.toLowerCase();
|
||||
const matchKey = Object.keys(accounts).find((key) => key.toLowerCase() === normalized);
|
||||
return matchKey ? accounts[matchKey] : undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user