mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(security): strip partial API token from status labels (#33262)
Merged via squash.
Prepared head SHA: 5fe81704e6
Co-authored-by: cu1ch3n <80438676+cu1ch3n@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06
This commit is contained in:
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Security/auth labels: remove token and API-key snippets from user-facing auth status labels so `/status` and `/models` do not expose credential fragments. (#33262) thanks @cu1ch3n.
|
||||
- Docs/security hardening guidance: document Docker `DOCKER-USER` + UFW policy and add cross-linking from Docker install docs for VPS/public-host setups. (#27613) thanks @dorukardahan.
|
||||
- Docs/security threat-model links: replace relative `.md` links with Mintlify-compatible root-relative routes in security docs to prevent broken internal navigation. (#27698) thanks @clawdoo.
|
||||
- iOS/Voice timing safety: guard system speech start/finish callbacks to the active utterance to avoid misattributed start events during rapid stop/restart cycles. (#33304) thanks @mbelinky; original implementation direction by @ngutman.
|
||||
|
||||
@@ -25,13 +25,14 @@ describe("resolveModelAuthLabel", () => {
|
||||
resolveAuthProfileDisplayLabelMock.mockReset();
|
||||
});
|
||||
|
||||
it("does not throw when token profile only has tokenRef", () => {
|
||||
it("does not include token value in label for token profiles", () => {
|
||||
ensureAuthProfileStoreMock.mockReturnValue({
|
||||
version: 1,
|
||||
profiles: {
|
||||
"github-copilot:default": {
|
||||
type: "token",
|
||||
provider: "github-copilot",
|
||||
token: "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
tokenRef: { source: "env", provider: "default", id: "GITHUB_TOKEN" },
|
||||
},
|
||||
},
|
||||
@@ -45,10 +46,12 @@ describe("resolveModelAuthLabel", () => {
|
||||
sessionEntry: { authProfileOverride: "github-copilot:default" } as never,
|
||||
});
|
||||
|
||||
expect(label).toContain("token ref(env:GITHUB_TOKEN)");
|
||||
expect(label).toBe("token (github-copilot:default)");
|
||||
expect(label).not.toContain("ghp_");
|
||||
expect(label).not.toContain("ref(");
|
||||
});
|
||||
|
||||
it("masks short api-key profile values", () => {
|
||||
it("does not include api-key value in label for api-key profiles", () => {
|
||||
const shortSecret = "abc123";
|
||||
ensureAuthProfileStoreMock.mockReturnValue({
|
||||
version: 1,
|
||||
@@ -69,8 +72,30 @@ describe("resolveModelAuthLabel", () => {
|
||||
sessionEntry: { authProfileOverride: "openai:default" } as never,
|
||||
});
|
||||
|
||||
expect(label).toContain("api-key");
|
||||
expect(label).toContain("...");
|
||||
expect(label).toBe("api-key (openai:default)");
|
||||
expect(label).not.toContain(shortSecret);
|
||||
expect(label).not.toContain("...");
|
||||
});
|
||||
|
||||
it("shows oauth type with profile label", () => {
|
||||
ensureAuthProfileStoreMock.mockReturnValue({
|
||||
version: 1,
|
||||
profiles: {
|
||||
"anthropic:oauth": {
|
||||
type: "oauth",
|
||||
provider: "anthropic",
|
||||
},
|
||||
},
|
||||
} as never);
|
||||
resolveAuthProfileOrderMock.mockReturnValue(["anthropic:oauth"]);
|
||||
resolveAuthProfileDisplayLabelMock.mockReturnValue("anthropic:oauth");
|
||||
|
||||
const label = resolveModelAuthLabel({
|
||||
provider: "anthropic",
|
||||
cfg: {},
|
||||
sessionEntry: { authProfileOverride: "anthropic:oauth" } as never,
|
||||
});
|
||||
|
||||
expect(label).toBe("oauth (anthropic:oauth)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { SessionEntry } from "../config/sessions.js";
|
||||
import { maskApiKey } from "../utils/mask-api-key.js";
|
||||
import {
|
||||
ensureAuthProfileStore,
|
||||
resolveAuthProfileDisplayLabel,
|
||||
@@ -9,28 +8,6 @@ import {
|
||||
import { getCustomProviderApiKey, resolveEnvApiKey } from "./model-auth.js";
|
||||
import { normalizeProviderId } from "./model-selection.js";
|
||||
|
||||
function formatApiKeySnippet(apiKey: string): string {
|
||||
const compact = apiKey.replace(/\s+/g, "");
|
||||
if (!compact) {
|
||||
return "unknown";
|
||||
}
|
||||
return maskApiKey(compact);
|
||||
}
|
||||
|
||||
function formatCredentialSnippet(params: {
|
||||
value: string | undefined;
|
||||
ref: { source: string; id: string } | undefined;
|
||||
}): string {
|
||||
const value = typeof params.value === "string" ? params.value.trim() : "";
|
||||
if (value) {
|
||||
return formatApiKeySnippet(value);
|
||||
}
|
||||
if (params.ref) {
|
||||
return `ref(${params.ref.source}:${params.ref.id})`;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
export function resolveModelAuthLabel(params: {
|
||||
provider?: string;
|
||||
cfg?: OpenClawConfig;
|
||||
@@ -69,13 +46,9 @@ export function resolveModelAuthLabel(params: {
|
||||
return `oauth${label ? ` (${label})` : ""}`;
|
||||
}
|
||||
if (profile.type === "token") {
|
||||
return `token ${formatCredentialSnippet({ value: profile.token, ref: profile.tokenRef })}${
|
||||
label ? ` (${label})` : ""
|
||||
}`;
|
||||
return `token${label ? ` (${label})` : ""}`;
|
||||
}
|
||||
return `api-key ${formatCredentialSnippet({ value: profile.key, ref: profile.keyRef })}${
|
||||
label ? ` (${label})` : ""
|
||||
}`;
|
||||
return `api-key${label ? ` (${label})` : ""}`;
|
||||
}
|
||||
|
||||
const envKey = resolveEnvApiKey(providerKey);
|
||||
@@ -83,12 +56,12 @@ export function resolveModelAuthLabel(params: {
|
||||
if (envKey.source.includes("OAUTH_TOKEN")) {
|
||||
return `oauth (${envKey.source})`;
|
||||
}
|
||||
return `api-key ${formatApiKeySnippet(envKey.apiKey)} (${envKey.source})`;
|
||||
return `api-key (${envKey.source})`;
|
||||
}
|
||||
|
||||
const customKey = getCustomProviderApiKey(params.cfg, providerKey);
|
||||
if (customKey) {
|
||||
return `api-key ${formatApiKeySnippet(customKey)} (models.json)`;
|
||||
return `api-key (models.json)`;
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
|
||||
@@ -55,6 +55,7 @@ describe("registerPluginCommand", () => {
|
||||
{
|
||||
name: "demo_cmd",
|
||||
description: "Demo command",
|
||||
acceptsArgs: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user