mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
fix(security): audit oauth lock hash
This commit is contained in:
@@ -66,11 +66,13 @@ describe("resolveOAuthRefreshLockPath", () => {
|
||||
|
||||
it("is immune to simple concat collisions at the provider/profile boundary", () => {
|
||||
// With a plain `${provider}:${profileId}` hash input, the pair
|
||||
// ("a", "b:c") would collide with ("a:b", "c"). The NUL separator
|
||||
// in the hash input rules that out.
|
||||
// ("a", "b:c") would collide with ("a:b", "c"). Tuple encoding rules that out.
|
||||
expect(resolveOAuthRefreshLockPath("a", "b:c")).not.toBe(
|
||||
resolveOAuthRefreshLockPath("a:b", "c"),
|
||||
);
|
||||
expect(resolveOAuthRefreshLockPath("a", "\x00b")).not.toBe(
|
||||
resolveOAuthRefreshLockPath("a\x00", "b"),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps lock filenames short for long profile ids", () => {
|
||||
|
||||
@@ -47,10 +47,10 @@ export function resolveAuthStatePathForDisplay(agentDir?: string): string {
|
||||
|
||||
/**
|
||||
* Resolve the path of the cross-agent, per-profile OAuth refresh coordination
|
||||
* lock. The filename hashes `provider\0profileId` so it is filesystem-safe
|
||||
* lock. The filename hashes a JSON tuple of `[provider, profileId]` so it is filesystem-safe
|
||||
* for arbitrary unicode/control-character inputs and always bounded in
|
||||
* length. The NUL separator makes it impossible to collide two distinct
|
||||
* `(provider, profileId)` pairs by string concatenation.
|
||||
* length. Tuple encoding makes it impossible to collide two distinct
|
||||
* `(provider, profileId)` pairs by separator-sensitive string concatenation.
|
||||
*
|
||||
* This lock is the serialization point that prevents the `refresh_token_reused`
|
||||
* storm when N agents share one OAuth profile (see issue #26322): every agent
|
||||
@@ -63,13 +63,10 @@ export function resolveAuthStatePathForDisplay(agentDir?: string): string {
|
||||
* test fixture, etc.) do not needlessly serialize against each other.
|
||||
*/
|
||||
export function resolveOAuthRefreshLockPath(provider: string, profileId: string): string {
|
||||
const hash = createHash("sha256");
|
||||
const lockKey = JSON.stringify([provider, profileId]);
|
||||
// This hashes provider/profile identifiers into a path-safe lock name; it is
|
||||
// not password storage or credential verification.
|
||||
hash.update(provider, "utf8");
|
||||
hash.update("\u0000", "utf8"); // NUL separator: unambiguous boundary.
|
||||
// codeql[js/insufficient-password-hash]
|
||||
hash.update(profileId, "utf8");
|
||||
const safeId = `sha256-${hash.digest("hex")}`;
|
||||
const safeId = `sha256-${createHash("sha256").update(lockKey, "utf8").digest("hex")}`;
|
||||
return path.join(resolveStateDir(), "locks", "oauth-refresh", safeId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user