fix(config-audit): redact key-valued secret flags (private-key, recovery-key, etc.)

Addresses clawsweeper P1 on PR #75095: the prior classifier missed
`--private-key` (Nostr setup) and `--recovery-key` (Matrix), letting
those values land in config-audit.jsonl. Add explicit names plus a
`*-key` suffix family covering private/recovery/signing/encryption/
master/session/gateway/service/hook keys. Tests cover both the explicit
flags and the heuristic family.
This commit is contained in:
koshaji
2026-05-01 10:05:09 +10:00
committed by sallyom
parent b61556df06
commit a5e82b9ab7
2 changed files with 58 additions and 4 deletions

View File

@@ -274,6 +274,54 @@ describe("config io audit helpers", () => {
]);
});
it("redacts key-valued secret flags (Nostr --private-key, Matrix --recovery-key)", () => {
const argv = [
"node",
"openclaw",
"channels",
"add",
"--channel",
"nostr",
"--private-key",
"nsec1realnostrprivatekeyvaluexyz1234567890",
"--recovery-key=EsTb-ABCD-1234-EFGH-5678-IJKL-9012-MNOP",
];
const result = redactConfigAuditArgv(argv);
expect(result).toEqual([
"node",
"openclaw",
"channels",
"add",
"--channel",
"nostr",
"--private-key",
"***",
"--recovery-key=***",
]);
});
it("redacts unknown *-key flags via the heuristic classifier (private/signing/master/etc.)", () => {
const argv = [
"node",
"openclaw",
"--my-plugin-private-key",
"tenant-private-key-material-zzz",
"--rotated-signing-key=PEM-LIKE-MATERIAL",
"--ops-master-key",
"ABCDEF1234567890",
];
const result = redactConfigAuditArgv(argv);
expect(result).toEqual([
"node",
"openclaw",
"--my-plugin-private-key",
"***",
"--rotated-signing-key=***",
"--ops-master-key",
"***",
]);
});
it("does not mask the next arg when a secret flag is followed by another option", () => {
const argv = ["openclaw", "--token", "--port", "8080"];
expect(redactConfigAuditArgv(argv)).toEqual(["openclaw", "--token", "--port", "8080"]);

View File

@@ -38,14 +38,20 @@ const SECRET_FLAG_NAMES = new Set([
"--identity-token",
"--session-token",
"--service-token",
"--private-key",
"--recovery-key",
"--gateway-key",
"--session-key",
"--active-key",
]);
// Suffix-based heuristic. Any `--…-(token|secret|password|passwd|api-key|
// apikey|api-secret|webhook|credential|bearer|pat)` is treated as a secret
// flag in addition to the explicit list. The leading `--` is required so we
// don't mismatch arbitrary positional arguments.
// apikey|api-secret|webhook|credential|bearer|pat|private-key|recovery-key|
// signing-key|encryption-key|master-key|session-key|gateway-key|service-key|
// hook-key)` is treated as a secret flag in addition to the explicit list.
// The leading `--` is required so we don't mismatch arbitrary positional args.
const SECRET_FLAG_SUFFIX_PATTERN =
/^--(?:[a-z0-9]+(?:-[a-z0-9]+)*-)?(?:token|secret|password|passwd|api[-_]?key|api[-_]?secret|webhook|credential|bearer|pat)$/;
/^--(?:[a-z0-9]+(?:-[a-z0-9]+)*-)?(?:token|secret|password|passwd|api[-_]?key|api[-_]?secret|webhook|credential|bearer|pat|private[-_]?key|recovery[-_]?key|signing[-_]?key|encryption[-_]?key|master[-_]?key|session[-_]?key|gateway[-_]?key|service[-_]?key|hook[-_]?key)$/;
function isSecretFlagName(flagName: string | null): boolean {
if (flagName === null) {