mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(policy): drop unreachable sensitive-logging redaction check (#113993)
The policy/data-handling-redaction-disabled check could never emit a finding: scanPolicyDataHandling records the sensitiveLoggingRedaction evidence with a hardcoded value true, and the finding builder only fired on value !== true. Redaction is unconditional in src/logging/redact.ts, which hardcodes tools mode and reads only redactPatterns, so no config can turn it off. Delete the check, its finding builder, check id, and the validateOnly fix class that existed solely for it. Keep the public dataHandling.sensitiveLogging.requireRedaction policy key: it is a policy.jsonc contract, it still drives openclaw policy compare baseline strictness, and its shape stays validated. Make the key's satisfied status explicit instead of silent: the policy rule declares satisfiedByInvariant pointing at the evidence source policy state records (oc://openclaw.invariant/logging/redaction), which openclaw policy check emits in dataHandling evidence and the attestation. A metadata test asserts every rule names either its checks or its invariant, never both and never neither, and that policy state actually emits the declared source. Also drop the stale logging.redactSensitive entry from the policy config coverage manifest; that config key is retired.
This commit is contained in:
committed by
GitHub
parent
71cb266c1f
commit
08138cabe0
+13
-9
@@ -196,11 +196,11 @@ Cross-cutting notes not obvious from the rule tables below:
|
||||
- Secret and auth-profile evidence records provider/source posture and
|
||||
SecretRef metadata only, never raw values. Policy does not read or attest
|
||||
per-agent credential stores such as `auth-profiles.json`.
|
||||
- Data-handling evidence is config-level posture only (redaction mode,
|
||||
telemetry capture toggle, session maintenance mode, transcript-indexing
|
||||
setting). It does not inspect logs, telemetry exports, transcripts, or
|
||||
memory files, and a clean result does not prove that no personal data or
|
||||
secrets exist in them.
|
||||
- Data-handling evidence is config-level posture (telemetry capture toggle,
|
||||
session maintenance mode, transcript-indexing setting) plus the always-on log
|
||||
redaction invariant. It does not inspect logs, telemetry exports,
|
||||
transcripts, or memory files, and a clean result does not prove that no
|
||||
personal data or secrets exist in them.
|
||||
- Routing probes reuse OpenClaw's runtime binding resolver. Routing evidence
|
||||
records only the probe id, resolved agent, match kind, and redacted binding
|
||||
metadata. It never records peer, account, guild, team, or role identifiers.
|
||||
@@ -411,7 +411,7 @@ allowlist such as `["all"]`.
|
||||
|
||||
| Policy field | Observed state | Use when |
|
||||
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| `dataHandling.sensitiveLogging.requireRedaction` | Sensitive log redaction, which is always on | Set to `true` to record the requirement; OpenClaw always satisfies it. |
|
||||
| `dataHandling.sensitiveLogging.requireRedaction` | Runtime invariant `oc://openclaw.invariant/logging/redaction` | Set to `true` to record the requirement; OpenClaw always satisfies it. |
|
||||
| `dataHandling.telemetry.denyContentCapture` | `diagnostics.otel.captureContent` | Set to `true` to reject telemetry content capture. |
|
||||
| `dataHandling.retention.requireSessionMaintenance` | `session.maintenance.mode` | Set to `true` to require effective session maintenance mode `enforce`. |
|
||||
| `dataHandling.memory.denySessionTranscriptIndexing` | `memory.qmd.sessions.enabled`, `memory.search.experimental.sessionMemory`, and per-agent overrides | Set to `true` to reject session transcript indexing into memory. |
|
||||
@@ -845,7 +845,6 @@ the interval.
|
||||
| `policy/sandbox-container-runtime-socket-mount` | A container-backed sandbox or browser mount exposes the container runtime socket. |
|
||||
| `policy/sandbox-container-unconfined-profile` | Container sandbox profile is unconfined when policy denies it. |
|
||||
| `policy/sandbox-browser-cdp-source-range-missing` | Sandbox browser CDP source range is missing when policy requires one. |
|
||||
| `policy/data-handling-redaction-disabled` | Sensitive logging redaction is disabled when policy requires it. |
|
||||
| `policy/data-handling-telemetry-content-capture` | Telemetry content capture is enabled when policy denies it. |
|
||||
| `policy/data-handling-session-retention-not-enforced` | Session retention maintenance is not enforced when policy requires it. |
|
||||
| `policy/data-handling-session-transcript-memory-enabled` | Session transcript memory indexing is enabled when policy denies it. |
|
||||
@@ -1013,8 +1012,13 @@ Scoped elevated-tools repairs are detect-only. Scoped data-handling repairs are
|
||||
also skipped when the finding reports shared telemetry config, because changing
|
||||
the shared setting would affect more than the scoped policy target.
|
||||
|
||||
`dataHandling.sensitiveLogging.requireRedaction` has no repair. Sensitive log
|
||||
redaction is unconditional, so the check only validates the policy declaration.
|
||||
`dataHandling.sensitiveLogging.requireRedaction` has no check and no repair.
|
||||
Sensitive log redaction is unconditional in OpenClaw, so nothing can report it
|
||||
as disabled. The key stays a supported policy rule: `openclaw policy` validates
|
||||
its shape, `openclaw policy compare` still requires a candidate policy to be at
|
||||
least as strict as the baseline for it, and `openclaw policy check` records the
|
||||
runtime invariant `oc://openclaw.invariant/logging/redaction` in the
|
||||
`dataHandling` evidence and attestation as proof the requirement is satisfied.
|
||||
|
||||
Scoped required-deny repairs are skipped when the finding reports inherited
|
||||
root `tools.deny`, because adding the required tool to root config would affect
|
||||
|
||||
@@ -47,7 +47,6 @@ export const CHECK_IDS = {
|
||||
policySandboxContainerRuntimeSocketMount: "policy/sandbox-container-runtime-socket-mount",
|
||||
policySandboxContainerUnconfinedProfile: "policy/sandbox-container-unconfined-profile",
|
||||
policySandboxBrowserCdpSourceRangeMissing: "policy/sandbox-browser-cdp-source-range-missing",
|
||||
policyDataHandlingRedactionDisabled: "policy/data-handling-redaction-disabled",
|
||||
policyDataHandlingTelemetryContentCapture: "policy/data-handling-telemetry-content-capture",
|
||||
policyDataHandlingSessionRetentionNotEnforced:
|
||||
"policy/data-handling-session-retention-not-enforced",
|
||||
@@ -120,7 +119,6 @@ export const POLICY_CHECK_IDS = [
|
||||
CHECK_IDS.policySandboxContainerRuntimeSocketMount,
|
||||
CHECK_IDS.policySandboxContainerUnconfinedProfile,
|
||||
CHECK_IDS.policySandboxBrowserCdpSourceRangeMissing,
|
||||
CHECK_IDS.policyDataHandlingRedactionDisabled,
|
||||
CHECK_IDS.policyDataHandlingTelemetryContentCapture,
|
||||
CHECK_IDS.policyDataHandlingSessionRetentionNotEnforced,
|
||||
CHECK_IDS.policyDataHandlingSessionTranscriptMemory,
|
||||
|
||||
@@ -110,21 +110,9 @@ function dataHandlingFindingsForRule(
|
||||
return [];
|
||||
}
|
||||
const findings: HealthFinding[] = [];
|
||||
if (readPolicyBoolean(dataHandling, ["sensitiveLogging", "requireRedaction"]) === true) {
|
||||
findings.push(
|
||||
...dataHandlingEntries(evidence, "sensitiveLoggingRedaction")
|
||||
.filter(evidenceFilter)
|
||||
.filter((entry) => entry.value !== true)
|
||||
.map((entry) =>
|
||||
dataHandlingFinding(entry, {
|
||||
checkId: CHECK_IDS.policyDataHandlingRedactionDisabled,
|
||||
message: "Sensitive logging redaction is disabled.",
|
||||
requirement: `oc://${policyDocName}/${requirementBase}/sensitiveLogging/requireRedaction`,
|
||||
fixHint: "Sensitive log redaction is always enabled; update policy after review.",
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
// dataHandling.sensitiveLogging.requireRedaction has no check here on purpose: redaction is
|
||||
// an unconditional runtime invariant (src/logging/redact.ts), so policy state records it as
|
||||
// satisfied evidence (oc://openclaw.invariant/logging/redaction) instead of a finding.
|
||||
if (readPolicyBoolean(dataHandling, ["telemetry", "denyContentCapture"]) === true) {
|
||||
findings.push(
|
||||
...dataHandlingEntries(evidence, "telemetryContentCapture")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Policy doctor fix metadata classifies findings before patch builders exist.
|
||||
import { CHECK_IDS, POLICY_CHECK_IDS } from "./check-ids.js";
|
||||
|
||||
type PolicyFixClass = "automatic" | "reviewRequired" | "manual" | "validateOnly" | "unsupported";
|
||||
type PolicyFixClass = "automatic" | "reviewRequired" | "manual" | "unsupported";
|
||||
|
||||
type PolicyFixMetadata = {
|
||||
readonly checkId: (typeof POLICY_CHECK_IDS)[number];
|
||||
@@ -327,15 +327,6 @@ const POLICY_FIX_METADATA = [
|
||||
configTargets: ["agents.sandbox.browser"],
|
||||
},
|
||||
),
|
||||
// Sensitive log redaction is an unconditional runtime invariant (src/logging/redact.ts),
|
||||
// so this requirement has no config target to patch. Adding one would emit a retired
|
||||
// config key that strict validation rejects.
|
||||
m(
|
||||
CHECK_IDS.policyDataHandlingRedactionDisabled,
|
||||
"validateOnly",
|
||||
"Update the policy requirement; sensitive log redaction is always enabled.",
|
||||
{ policyPath: ["dataHandling", "sensitiveLogging", "requireRedaction"] },
|
||||
),
|
||||
m(
|
||||
CHECK_IDS.policyDataHandlingTelemetryContentCapture,
|
||||
"automatic",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Policy doctor metadata tests cover rule metadata.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { scanPolicyDataHandling } from "../policy-state-data.js";
|
||||
import { CHECK_IDS, POLICY_CHECK_IDS } from "./check-ids.js";
|
||||
import { POLICY_FIX_METADATA_BY_CHECK_ID } from "./fix-metadata.js";
|
||||
import { POLICY_RULE_METADATA, type PolicyRuleMetadata } from "./metadata.js";
|
||||
@@ -207,10 +208,6 @@ describe("policy doctor metadata", () => {
|
||||
.get("unsupported")
|
||||
?.map((rule) => rule.checkId)
|
||||
.toSorted(),
|
||||
validateOnly: grouped
|
||||
.get("validateOnly")
|
||||
?.map((rule) => rule.checkId)
|
||||
.toSorted(),
|
||||
}).toEqual({
|
||||
automatic: [
|
||||
"policy/agents-tool-not-denied",
|
||||
@@ -285,7 +282,33 @@ describe("policy doctor metadata", () => {
|
||||
"policy/tools-profile-unapproved",
|
||||
],
|
||||
unsupported: ["policy/sandbox-container-posture-unobservable"],
|
||||
validateOnly: ["policy/data-handling-redaction-disabled"],
|
||||
});
|
||||
});
|
||||
|
||||
it("declares how every policy rule is enforced", () => {
|
||||
const rules = POLICY_RULE_METADATA as readonly PolicyRuleMetadata[];
|
||||
// Every rule names either its doctor checks or the invariant that satisfies it, never
|
||||
// both and never neither, so an accepted policy key cannot enforce nothing in silence.
|
||||
expect(
|
||||
rules
|
||||
.filter((rule) => rule.checkIds.length > 0 === (rule.satisfiedByInvariant !== undefined))
|
||||
.map((rule) => rule.policyPath.join(".")),
|
||||
).toEqual([]);
|
||||
const invariantSources = new Set(scanPolicyDataHandling({}).map((entry) => entry.source));
|
||||
expect(
|
||||
rules
|
||||
.filter((rule) => rule.satisfiedByInvariant !== undefined)
|
||||
.map((rule) => [
|
||||
rule.policyPath.join("."),
|
||||
rule.satisfiedByInvariant,
|
||||
invariantSources.has(rule.satisfiedByInvariant ?? ""),
|
||||
]),
|
||||
).toEqual([
|
||||
[
|
||||
"dataHandling.sensitiveLogging.requireRedaction",
|
||||
"oc://openclaw.invariant/logging/redaction",
|
||||
true,
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,6 +24,12 @@ export type PolicyRuleMetadata = {
|
||||
| "string"
|
||||
| "string-list";
|
||||
readonly checkIds: readonly (typeof POLICY_CHECK_IDS)[number][];
|
||||
/**
|
||||
* Evidence source of the runtime invariant that satisfies this rule unconditionally.
|
||||
* Set only when `checkIds` is empty, so a rule can never enforce nothing without saying
|
||||
* why; `metadata.test.ts` asserts the pairing and that policy state emits the source.
|
||||
*/
|
||||
readonly satisfiedByInvariant?: string;
|
||||
readonly emptyList?: PolicyEmptyListSemantics;
|
||||
readonly allowedValues?: readonly string[];
|
||||
readonly caseSensitive?: boolean;
|
||||
@@ -339,10 +345,14 @@ export const POLICY_RULE_METADATA = [
|
||||
scopeSelectors: ["channelIds"],
|
||||
},
|
||||
{
|
||||
// Redaction is unconditional in src/logging/redact.ts, so no doctor check can fail for
|
||||
// this rule. The key stays a policy contract: `openclaw policy compare` still enforces
|
||||
// baseline strictness, and policy state records the invariant below as satisfied.
|
||||
policyPath: ["dataHandling", "sensitiveLogging", "requireRedaction"],
|
||||
strictness: "requires-true",
|
||||
valueType: "boolean",
|
||||
checkIds: [CHECK_IDS.policyDataHandlingRedactionDisabled],
|
||||
checkIds: [],
|
||||
satisfiedByInvariant: "oc://openclaw.invariant/logging/redaction",
|
||||
},
|
||||
{
|
||||
policyPath: ["dataHandling", "telemetry", "denyContentCapture"],
|
||||
|
||||
@@ -301,7 +301,6 @@ describe("registerPolicyDoctorChecks", () => {
|
||||
"policy/sandbox-container-runtime-socket-mount",
|
||||
"policy/sandbox-container-unconfined-profile",
|
||||
"policy/sandbox-browser-cdp-source-range-missing",
|
||||
"policy/data-handling-redaction-disabled",
|
||||
"policy/data-handling-telemetry-content-capture",
|
||||
"policy/data-handling-session-retention-not-enforced",
|
||||
"policy/data-handling-session-transcript-memory-enabled",
|
||||
@@ -1562,7 +1561,6 @@ describe("registerPolicyDoctorChecks", () => {
|
||||
remote: { allow: false },
|
||||
},
|
||||
dataHandling: {
|
||||
sensitiveLogging: { requireRedaction: true },
|
||||
telemetry: { denyContentCapture: true },
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -7,18 +7,6 @@ import type { PolicyDoctorCheckDeps } from "../types.js";
|
||||
export function createPolicyDataAuthChecks(deps: PolicyDoctorCheckDeps): readonly HealthCheck[] {
|
||||
const { evaluatePolicy, findingsForCheck } = deps;
|
||||
|
||||
const policyDataHandlingRedactionDisabledCheck: HealthCheck = {
|
||||
id: CHECK_IDS.policyDataHandlingRedactionDisabled,
|
||||
kind: "plugin",
|
||||
description: "Sensitive logging redaction remains enabled when policy requires it.",
|
||||
source: "policy",
|
||||
async detect(ctx) {
|
||||
return findingsForCheck(
|
||||
await evaluatePolicy(ctx),
|
||||
CHECK_IDS.policyDataHandlingRedactionDisabled,
|
||||
);
|
||||
},
|
||||
};
|
||||
const policyDataHandlingTelemetryContentCaptureCheck: HealthCheck = {
|
||||
id: CHECK_IDS.policyDataHandlingTelemetryContentCapture,
|
||||
kind: "plugin",
|
||||
@@ -118,7 +106,6 @@ export function createPolicyDataAuthChecks(deps: PolicyDoctorCheckDeps): readonl
|
||||
};
|
||||
|
||||
return [
|
||||
policyDataHandlingRedactionDisabledCheck,
|
||||
policyDataHandlingTelemetryContentCaptureCheck,
|
||||
policyDataHandlingSessionRetentionNotEnforcedCheck,
|
||||
policyDataHandlingSessionTranscriptMemoryCheck,
|
||||
|
||||
@@ -52,6 +52,9 @@ export function scanPolicyDataHandling(
|
||||
cfg: Record<string, unknown>,
|
||||
): readonly PolicyDataHandlingEvidence[] {
|
||||
const entries: PolicyDataHandlingEvidence[] = [];
|
||||
// Redaction has no config surface: src/logging/redact.ts always redacts. This invariant
|
||||
// record is how dataHandling.sensitiveLogging.requireRedaction reports as satisfied in
|
||||
// `openclaw policy check` evidence and the attestation, since no doctor check can fail.
|
||||
entries.push({
|
||||
id: "logging-redaction",
|
||||
kind: "sensitiveLoggingRedaction",
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
"gateway.nodes.commands.deny.*",
|
||||
"gateway.tailscale.mode",
|
||||
"gateway.tailscale.preserveFunnel",
|
||||
"logging.redactSensitive",
|
||||
"memory.qmd.sessions.enabled",
|
||||
"mcp.servers.*.command",
|
||||
"mcp.servers.*.transport",
|
||||
@@ -174,14 +173,6 @@
|
||||
"policy": "ingress.session.requireDmScope",
|
||||
"reason": "Policy observes direct-message session isolation scope.",
|
||||
},
|
||||
{
|
||||
"pattern": "logging.redactSensitive",
|
||||
"status": "observed",
|
||||
"area": "dataHandling",
|
||||
"policy": "dataHandling.sensitiveLogging.requireRedaction",
|
||||
"reason": "Policy observes sensitive log redaction posture.",
|
||||
"allowNoSchemaPath": true,
|
||||
},
|
||||
{
|
||||
"pattern": "diagnostics.otel.captureContent",
|
||||
"status": "observed",
|
||||
|
||||
Reference in New Issue
Block a user