fix(auth-profiles): accept mode/apiKey aliases to prevent silent credential loss

Users following openclaw.json auth.profiles examples (which use 'mode' for
the credential type) would write their auth-profiles.json entries with:
  { provider: "anthropic", mode: "api_key", apiKey: "sk-ant-..." }

The actual auth-profiles.json schema uses:
  { provider: "anthropic", type: "api_key", key: "sk-ant-..." }

coerceAuthStore() and coerceLegacyStore() validated entries strictly on
typed.type, silently skipping any entry that used the mode/apiKey spelling.
The user would get 'No API key found for provider anthropic' with no hint
about the field name mismatch.

Add normalizeRawCredentialEntry() which, before validation:
- coerces mode → type when type is absent
- coerces apiKey → key when key is absent

Both functions now call the normalizer before the type guard so
mode/apiKey entries are loaded and resolved correctly.

Fixes #26916
This commit is contained in:
lbo728
2026-02-26 08:27:59 +09:00
committed by Peter Steinberger
parent 85b075d0cc
commit 7e7ca43a79
2 changed files with 56 additions and 2 deletions

View File

@@ -122,4 +122,36 @@ describe("ensureAuthProfileStore", () => {
fs.rmSync(root, { recursive: true, force: true });
}
});
it("accepts mode/apiKey aliases so users who follow openclaw.json format are not silently broken", () => {
// A common mistake: users write auth-profiles.json using the same field names
// as openclaw.json auth.profiles ("mode" + "apiKey") instead of the canonical
// auth-profiles.json fields ("type" + "key"). The parser now normalises both.
const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-auth-alias-"));
try {
const storeWithAliases = {
version: AUTH_STORE_VERSION,
profiles: {
"anthropic:work": {
provider: "anthropic",
mode: "api_key", // alias for "type"
apiKey: "sk-ant-alias-test", // alias for "key"
},
},
};
fs.writeFileSync(
path.join(agentDir, "auth-profiles.json"),
`${JSON.stringify(storeWithAliases, null, 2)}\n`,
"utf8",
);
const store = ensureAuthProfileStore(agentDir);
const profile = store.profiles["anthropic:work"];
expect(profile).toBeDefined();
expect(profile?.type).toBe("api_key");
expect((profile as { key?: string }).key).toBe("sk-ant-alias-test");
} finally {
fs.rmSync(agentDir, { recursive: true, force: true });
}
});
});