{"repo":"NousResearch/hermes-agent","public_id":"15934","display_label":"Redact false positives on code files fix","summary":"A bug was identified where ENV/JSON regex patterns in secret redaction caused false positives on source code files. These members address adding a code_file parameter and restricting redaction logic to avoid corrupting code output.","subsystem":null,"counts":{"members":4,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":19715,"merged_at":"2026-05-04T11:56:29+00:00"},"resolution":{"state":"resolved","by":"pr-19715"},"freshness":{"state":"verified","last_verified_at":"2026-05-04T11:56:29+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.5},"fix_edges":[{"issue_number":15934,"pr_number":19715,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-04T11:56:29+00:00"}],"members":[{"kind":"issue","number":15934,"title":"bug(redact): ENV/JSON regex patterns cause false positives on source code files","url":null,"state":"closed"},{"kind":"pr","number":15923,"title":"fix(redact): skip ENV/JSON patterns on code files to avoid false positives","url":null,"state":"closed","close_reason":{"category":"silent_close","implication":"active"},"merged":false},{"kind":"pr","number":16127,"title":"fix(redact): add code_file param to skip false-positive ENV/JSON patterns","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#19715","number":19715}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":19715,"title":"fix(redact): add code_file param to skip false-positive ENV/JSON patterns","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/redact.py","tools/file_tools.py"],"fix_hunks":[{"file":"agent/redact.py","hunks":["@@ -305,13 +305,18 @@ def _redact_form_body(text: str) -> str:\n     return _redact_query_string(text.strip())\n \n \n-def redact_sensitive_text(text: str, *, force: bool = False) -> str:\n+def redact_sensitive_text(text: str, *, force: bool = False, code_file: bool = False) -> str:\n     \"\"\"Apply all redaction patterns to a block of text.\n \n     Safe to call on any string -- non-matching text passes through unchanged.\n     Disabled by default — enable via security.redact_secrets: true in config.yaml.\n     Set force=True for safety boundaries that must never return raw secrets\n     regardless of the user's global logging redaction preference.\n+\n+    Set code_file=True to skip the ENV-assignment and JSON-field regex\n+    patterns when the text is known to be source code (e.g. MAX_TOKENS=***\n+    constants, \"apiKey\": \"test\" fixtures). Prefix patterns, auth headers,\n+    private keys, DB connstrings, JWTs, and URL secrets are still redacted.\n     \"\"\"\n     if text is None:\n         return None","@@ -325,17 +330,18 @@ def redact_sensitive_text(text: str, *, force: bool = False) -> str:\n     # Known prefixes (sk-, ghp_, etc.)\n     text = _PREFIX_RE.sub(lambda m: _mask_token(m.group(1)), text)\n \n-    # ENV assignments: OPENAI_API_KEY=[redacted:secret-assignment]\n-    def _redact_env(m):\n-        name, quote, value = m.group(1), m.group(2), m.group(3)\n-        return f\"{name}={quote}{_mask_token(value)}{quote}\"\n-    text = _ENV_ASSIGN_RE.sub(_redact_env, text)\n-\n-    # JSON fields: \"apiKey\": \"value\"\n-    def _redact_json(m):\n-        key, value = m.group(1), m.group(2)\n-        return f'{key}: \"{_mask_token(value)}\"'\n-    text = _JSON_FIELD_RE.sub(_redact_json, text)\n+    # ENV assignments: OPENAI_API_KEY=***  (skip for code files — false positives)\n+    if not code_file:\n+        def _redact_env(m):\n+            name, quote, value = m.group(1), m.group(2), m.group(3)\n+            return f\"{name}={quote}{_mask_token(value)}{quote}\"\n+        text = _ENV_ASSIGN_RE.sub(_redact_env, text)\n+\n+        # JSON fields: \"apiKey\": \"***\"  (skip for code files — false positives)\n+        def _redact_json(m):\n+            key, value = m.group(1), m.group(2)\n+            return f'{key}: \"{_mask_token(value)}\"'\n+        text = _JSON_FIELD_RE.sub(_redact_json, text)\n \n     # Authorization headers\n     text = _AUTH_HEADER_RE.sub("]},{"file":"tools/file_tools.py","hunks":["@@ -570,7 +570,7 @@ def read_file_tool(path: str, offset: int = 1, limit: int = 500, task_id: str =\n \n         # ── Redact secrets (after guard check to skip oversized content) ──\n         if result.content:\n-            result.content = redact_sensitive_text(result.content)\n+            result.content = redact_sensitive_text(result.content, code_file=True)\n             result_dict[\"content\"] = result.content\n \n         # Large-file hint: if the file is big and the caller didn't ask","@@ -993,7 +993,7 @@ def search_tool(pattern: str, target: str = \"content\", path: str = \".\",\n         if hasattr(result, 'matches'):\n             for m in result.matches:\n                 if hasattr(m, 'content') and m.content:\n-                    m.content = redact_sensitive_text(m.content)\n+                    m.content = redact_sensitive_text(m.content, code_file=True)\n         result_dict = result.to_dict()\n \n         if count >= 3:\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}