feat(redaction): registry-fed exact-value and key-form redaction from user pattern file - #97383
hermes-node8 wants to merge 3 commits into
Conversation
|
@kshitijk4poor — you've been working directly in agent/redact.py, so I'd especially value your take on whether this fits the current redaction direction. I've kept the integration surface narrow and aligned this revision with the newer URL-credential behavior. |
…ser pattern file Operator-controlled exact-value layer: a user-owned JSON pattern file (security.redact_patterns / HERMES_REDACT_PATTERNS) of exact literals and key-forms, masked at Hermes' existing redaction boundaries. - agent/redact.py: _redact_registry_patterns pass — additive-only, fail-safe (last-good cache), sentinel on file reads, longest-first literal pass, plain + URL-query key-form passes (query-position masking gated on redact_url_credentials, bounded at '&'/'#') - cli.py / gateway/run.py / hermes_cli/main.py: config->env bridge at the three entry points before agent.redact is imported - hermes_cli/config.py: documented security.redact_patterns key Refs NousResearch#43666 (compaction-boundary slice). No new surface classes: additive-only, ReDoS-safe (escaped literals only), no user regex.
… sentinel, URL-boundary flag semantics, config bridge e2e
…pass Under code_file=True (source-code / terminal-output contexts) upstream deliberately skips ENV-assignment masking so code constants survive and token-shaped values are masked by the built-in _PREFIX_RE pass with a visible prefix marker (NousResearch#33801). The registry pass previously re-masked KEY=value assignments unconditionally, destroying that marker and failing the upstream terminal-output transform test with a populated pattern file. Gate the key-form and URL-query sub-passes on not code_file or file_read; exact-value literals stay unconditional. file_read content is exempt because file_read implies code_file=True upstream, and .env/config reads must keep key-form masking.
667746b to
16d94dd
Compare
|
Updated implementation: the registry pass now respects the |
|
Closing this PR — we found we can accomplish what we need through the plugin redaction registry (#84927): exact values register as escaped patterns with the built-in word-boundary anchors and file-read sentinel, so no engine change is required. Thanks for the consideration. |
What does this PR do?
What this adds: exact operator-registered values and registered key-forms are masked at Hermes' existing redaction boundaries, from a user-owned JSON pattern file.
Why: Hermes' built-in applied-secret snapshot covers external-source values only; operators cannot register their own exact secrets (values that arrive through non-
KEY=valueshapes, or short secrets exact matching can't safely touch).Safety: additive-only (no pattern file → the pass is a no-op; under-masking is structurally impossible) · fail-safe (broken file → last-good set, never an unmasked gap) · sentinel-protected (masked values can never be written back) · display-only (never modifies
.env, vault, or config) · per-instance ($HERMES_HOME-resolved default).Scope: this PR adds the source/value layer; it does not modify at-rest data and does not implement egress-boundary wiring (#72778 is a deliberately separate follow-up — the two compose rather than conflict).
Related Issue
Refs #43666— addresses the compaction-boundary slice of the persistence-boundary redaction gap (sibling PR fix(security): redact database URI passwords and mutable reasoning #43940 covers the DB-URI / mutable-reasoning slice). The issue has multiple open slices, so this is intentionally not a full "Fixes".Type of Change
Changes Made
Minimal integration surface — 5 code files + 2 test files:
agent/redact.py— one new processing stage:_redact_registry_patterns(pattern-file load, mtime-keyed cache, fail-safe last-good, sentinel on file reads, longest-match-first literal pass, plain + URL-query key-form passes). Implementation is concentrated in the existing redaction module: no new subsystem, no new abstraction hierarchy, no new CLI, no new format persisted by Hermes; existing redaction semantics are unchanged when the pass is disabled.cli.py/gateway/run.py/hermes_cli/main.py— four small wiring changes: bridgesecurity.redact_patterns(config.yaml fallback; the env var still wins) toHERMES_REDACT_PATTERNSat the three process entry points, beforeagent.redactis imported — mirroring the existingsecurity.redact_secretsbridge.hermes_cli/config.py— documents the new key in the commented config template.tests/agent/test_redact_registry_patterns.py+tests/hermes_cli/test_redact_patterns_bridge.py— 26 tests: mask styles, fail-safe/cache/sentinel behavior, key-form forms, URL-boundary semantics (query-position key-form masking gated onredact_url_credentials, bounded at the next&/#; registered exact values unconditional), and the config→env bridge end-to-end.Security surface
No new network, IPC, subprocess, or write-side surface is introduced. The new security-relevant input is the operator-controlled pattern file — inside the agent's existing trust envelope (the same class as
config.yaml/.env), processed fail-safe and additive-only. The generated matching expressions are ReDoS-resistant by construction: registered key names are escaped literals. Arbitrary user-supplied regex is not accepted by this PR.Why not a plugin?
Hermes' plugin redaction registry (#84927) lets plugin authors register token formats. The guarantees this feature requires — pass ordering relative to the built-in masker, URL-boundary semantics at engine call sites, unconditional execution at every boundary, operator-managed exact-value state — are not expressible through the current plugin contract (registration, not control).
How to Test
{"literals": ["my-exact-value"], "key_patterns": {"PIN": true}}.HERMES_REDACT_PATTERNSat it (or setsecurity.redact_patternsin config.yaml).pytest tests/agent/test_redact.py tests/agent/test_compaction_redaction_boundaries.py tests/agent/test_tool_call_arg_no_redaction.py tests/hermes_cli/test_redact_config_bridge.py tests/agent/test_redact_registry_patterns.py tests/hermes_cli/test_redact_patterns_bridge.py tests/tools/test_terminal_output_transform_hook.py(fork PRs: CI needs a maintainer's workflow approval to run — until then, exact repro:)
env -u HERMES_HOME HERMES_REDACT_PATTERNS=<pattern-file> python -m pytest tests/agent/test_redact.py tests/agent/test_compaction_redaction_boundaries.py tests/agent/test_tool_call_arg_no_redaction.py tests/hermes_cli/test_redact_config_bridge.py tests/agent/test_redact_registry_patterns.py tests/hermes_cli/test_redact_patterns_bridge.py tests/tools/test_terminal_output_transform_hook.py -qValidation
18a76be1, 2026-09-01): redaction-surface suite 142/142 with a populated pattern file, including the upstream terminal-output transform hook (test_terminal_output_transform_still_runs_strip_and_redact) and the PR's registry-pass + config-bridge tests.code_filecontract (Secret redaction corrupts code syntax in tool output (write_file, execute_code, terminal) #33801): gated onnot code_file or file_read, so terminal/source-code output keeps its token-prefix markers; exact-value literals stay unconditional.$HERMES_HOME/state/redaction/redact_patterns.json(0600, per-instance); file-read content is masked with a non-reusable sentinel ([Bug]: redact_sensitive_text corrupts API keys in config files when read via read_file/search_files, causing 401 #35519 semantics), so the pattern file itself can never be written back over its own values.Checklist