mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-04 13:51:30 +00:00
fix(config): redact Nostr privateKey in config views (#58177)
* wip(config): preserve nostr redaction progress * fix(config): add private key redaction fallback * fix(config): align nostr privateKey secret input handling * fix(config): require resolved nostr private keys
This commit is contained in:
@@ -8762,7 +8762,70 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
additionalProperties: false,
|
||||
},
|
||||
privateKey: {
|
||||
type: "string",
|
||||
anyOf: [
|
||||
{
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
oneOf: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
source: {
|
||||
type: "string",
|
||||
const: "env",
|
||||
},
|
||||
provider: {
|
||||
type: "string",
|
||||
pattern: "^[a-z][a-z0-9_-]{0,63}$",
|
||||
},
|
||||
id: {
|
||||
type: "string",
|
||||
pattern: "^[A-Z][A-Z0-9_]{0,127}$",
|
||||
},
|
||||
},
|
||||
required: ["source", "provider", "id"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
source: {
|
||||
type: "string",
|
||||
const: "file",
|
||||
},
|
||||
provider: {
|
||||
type: "string",
|
||||
pattern: "^[a-z][a-z0-9_-]{0,63}$",
|
||||
},
|
||||
id: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["source", "provider", "id"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
source: {
|
||||
type: "string",
|
||||
const: "exec",
|
||||
},
|
||||
provider: {
|
||||
type: "string",
|
||||
pattern: "^[a-z][a-z0-9_-]{0,63}$",
|
||||
},
|
||||
id: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["source", "provider", "id"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
relays: {
|
||||
type: "array",
|
||||
@@ -8826,6 +8889,11 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
uiHints: {
|
||||
privateKey: {
|
||||
sensitive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
pluginId: "qqbot",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { REDACTED_SENTINEL, redactConfigSnapshot } from "./redact-snapshot.js";
|
||||
import { makeSnapshot, restoreRedactedValues } from "./redact-snapshot.test-helpers.js";
|
||||
import { redactSnapshotTestHints as mainSchemaHints } from "./redact-snapshot.test-hints.js";
|
||||
import { buildConfigSchema } from "./schema.js";
|
||||
|
||||
describe("realredactConfigSnapshot_real", () => {
|
||||
it("main schema redact works (samples)", () => {
|
||||
@@ -34,4 +35,25 @@ describe("realredactConfigSnapshot_real", () => {
|
||||
expect(restored.agents.defaults.memorySearch.remote.apiKey).toBe("1234");
|
||||
expect(restored.agents.list[0].memorySearch.remote.apiKey).toBe("6789");
|
||||
});
|
||||
|
||||
it("redacts bundled channel private keys from generated schema hints", () => {
|
||||
const hints = buildConfigSchema().uiHints;
|
||||
const snapshot = makeSnapshot({
|
||||
channels: {
|
||||
nostr: {
|
||||
privateKey: "nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5",
|
||||
relays: ["wss://relay.example.com"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = redactConfigSnapshot(snapshot, hints);
|
||||
const channels = result.config.channels as Record<string, Record<string, unknown>>;
|
||||
expect(channels.nostr.privateKey).toBe(REDACTED_SENTINEL);
|
||||
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config, hints);
|
||||
expect(restored.channels.nostr.privateKey).toBe(
|
||||
"nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -841,6 +841,30 @@ describe("redactConfigSnapshot", () => {
|
||||
expectGatewayAuthFieldValue(result, "password", REDACTED_SENTINEL);
|
||||
});
|
||||
|
||||
it("redacts privateKey paths even when absent from uiHints (defense in depth)", () => {
|
||||
const hints: ConfigUiHints = {
|
||||
"some.other.path": { sensitive: true },
|
||||
};
|
||||
const snapshot = makeSnapshot({
|
||||
channels: {
|
||||
nostr: {
|
||||
privateKey: "nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5",
|
||||
relays: ["wss://relay.example.com"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = redactConfigSnapshot(snapshot, hints);
|
||||
const channels = result.config.channels as Record<string, Record<string, unknown>>;
|
||||
expect(channels.nostr.privateKey).toBe(REDACTED_SENTINEL);
|
||||
expect(channels.nostr.relays).toEqual(["wss://relay.example.com"]);
|
||||
|
||||
const restored = restoreRedactedValues(result.config, snapshot.config, hints);
|
||||
expect(restored.channels.nostr.privateKey).toBe(
|
||||
"nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5",
|
||||
);
|
||||
});
|
||||
|
||||
it("redacts and restores dynamic env catchall secrets when uiHints miss the path", () => {
|
||||
const hints: ConfigUiHints = {
|
||||
"some.other.path": { sensitive: true },
|
||||
|
||||
@@ -47,6 +47,8 @@ describe("isSensitiveConfigPath", () => {
|
||||
expect(isSensitiveConfigPath("channels.irc.nickserv.password")).toBe(true);
|
||||
expect(isSensitiveConfigPath("channels.feishu.encryptKey")).toBe(true);
|
||||
expect(isSensitiveConfigPath("channels.feishu.accounts.default.encryptKey")).toBe(true);
|
||||
expect(isSensitiveConfigPath("channels.nostr.privateKey")).toBe(true);
|
||||
expect(isSensitiveConfigPath("channels.nostr.accounts.default.privateKey")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ const SENSITIVE_PATTERNS = [
|
||||
/secret/i,
|
||||
/api.?key/i,
|
||||
/encrypt.?key/i,
|
||||
/private.?key/i,
|
||||
/serviceaccount(?:ref)?$/i,
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user