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:
Vincent Koc
2026-03-31 19:55:03 +09:00
committed by GitHub
parent efe9183f9d
commit 57700d716f
10 changed files with 200 additions and 10 deletions

View File

@@ -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",

View File

@@ -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",
);
});
});

View File

@@ -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 },

View File

@@ -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);
});
});

View File

@@ -137,6 +137,7 @@ const SENSITIVE_PATTERNS = [
/secret/i,
/api.?key/i,
/encrypt.?key/i,
/private.?key/i,
/serviceaccount(?:ref)?$/i,
];