From 6c8fd8f7a78cb8afbe154e563656b21d70207ed6 Mon Sep 17 00:00:00 2001 From: ExoHayvan <18trevor3695@gmail.com> Date: Thu, 16 Jul 2026 18:28:27 -0400 Subject: [PATCH 1/4] chore: complete phase-1 step-149 --- docs/local-audit-event-schema.md | 10 +++- src/aethermesh_core/local_audit_event.py | 60 +++++++++++++++++++++++- tests/test_local_audit_event.py | 53 +++++++++++++++++++++ 3 files changed, 121 insertions(+), 2 deletions(-) diff --git a/docs/local-audit-event-schema.md b/docs/local-audit-event-schema.md index befd0aa..0844ad6 100644 --- a/docs/local-audit-event-schema.md +++ b/docs/local-audit-event-schema.md @@ -4,7 +4,15 @@ ## Storage and append-only rule -Store one event per UTF-8 newline-delimited JSON (JSONL) line, normally under the selected local AetherMesh home. `append_local_audit_event(path, event)` validates an event and opens the file only in append mode; it never reads, replaces, edits, or deletes prior records. Consumers must treat an existing line as immutable evidence. Local filesystem access can still alter files outside this API, so this phase does not claim tamper resistance. +Store one event per UTF-8 newline-delimited JSON (JSONL) line, normally under the selected local AetherMesh home. `append_local_audit_event(path, event)` sanitizes and validates an event before opening the file in append mode; it never reads, replaces, edits, or deletes prior records. Consumers must treat an existing line as immutable evidence. Local filesystem access can still alter files outside this API, so this phase does not claim tamper resistance. + +## Log-safety policy + +Audit events preserve intentionally safe accountability fields: node and work IDs, manifest hashes and references, validation receipt IDs, lineage, and contribution attribution. They should contain hashes, IDs, safe relative references, and short summaries instead of raw request or result content. + +`sanitize_local_audit_event` recursively replaces values under secret-bearing keys (`token`, `secret`, `password`, `privateKey`, `apiKey`, `seed`, `credential`, and `authorization`, including common compound forms) with `[REDACTED]`. Environment maps and raw request payload fields are also redacted. Absolute Unix, home-relative, and Windows paths become `local-path/` labels; callers should supply a project-relative artifact reference when that reference is needed for validation. Do not place environment-variable dumps, private keys, credentials, full prompts, request payloads, or node-local configuration bodies in audit fields. + +Sanitization is a persistence boundary, not a general secret detector. Callers must still prefer the compact schema and must not hide arbitrary secret text inside an unrelated identifier or summary field. ## Required fields diff --git a/src/aethermesh_core/local_audit_event.py b/src/aethermesh_core/local_audit_event.py index 81745c8..d90a929 100644 --- a/src/aethermesh_core/local_audit_event.py +++ b/src/aethermesh_core/local_audit_event.py @@ -11,12 +11,29 @@ import json import os +import re from collections.abc import Mapping from datetime import datetime from pathlib import Path, PureWindowsPath from typing import Any AUDIT_EVENT_SCHEMA_VERSION = 2 +AUDIT_REDACTED_VALUE = "[REDACTED]" +_SENSITIVE_KEY_PARTS = frozenset( + { + "token", + "secret", + "password", + "privatekey", + "apikey", + "seed", + "credential", + "authorization", + } +) +_PRIVATE_CONTENT_KEYS = frozenset( + {"env", "environment", "payload", "requestpayload", "rawrequestpayload"} +) AUDIT_EVENT_TYPES = frozenset( { "node_initialized", @@ -98,6 +115,12 @@ class LocalAuditEventError(ValueError): """Raised when a local audit event does not match the canonical format.""" +def sanitize_local_audit_event(event: Mapping[str, Any]) -> dict[str, Any]: + """Return an audit-safe copy without secrets or host-specific absolute paths.""" + + return {key: _sanitize_audit_value(value, key=key) for key, value in event.items()} + + def validate_local_audit_event(event: Mapping[str, Any]) -> dict[str, Any]: """Validate and return one canonical local audit event without writing it.""" @@ -193,7 +216,7 @@ def append_local_audit_event( ) -> dict[str, Any]: """Validate and append one event; this API never updates prior JSONL records.""" - document = validate_local_audit_event(event) + document = validate_local_audit_event(sanitize_local_audit_event(event)) audit_path = Path(path) audit_path.parent.mkdir(parents=True, exist_ok=True) with audit_path.open("a", encoding="utf-8") as handle: @@ -203,6 +226,41 @@ def append_local_audit_event( return document +def _sanitize_audit_value(value: Any, *, key: object | None = None) -> Any: + if isinstance(key, str) and _is_private_audit_key(key): + return AUDIT_REDACTED_VALUE + if isinstance(value, Mapping): + return { + child_key: _sanitize_audit_value(child_value, key=child_key) + for child_key, child_value in value.items() + } + if isinstance(value, list): + return [_sanitize_audit_value(item) for item in value] + if isinstance(value, str): + return _sanitize_local_path(value) + return value + + +def _is_private_audit_key(key: str) -> bool: + normalized = re.sub(r"[^a-z0-9]", "", key.lower()) + if normalized in _SENSITIVE_KEY_PARTS or normalized in _PRIVATE_CONTENT_KEYS: + return True + parts = { + part for part in re.split(r"[^A-Za-z0-9]+|(?<=[a-z])(?=[A-Z])", key) if part + } + return any(part.lower() in _SENSITIVE_KEY_PARTS for part in parts) + + +def _sanitize_local_path(value: str) -> str: + path_variants = (Path(value), PureWindowsPath(value)) + if not value.startswith("~") and not any( + path.is_absolute() for path in path_variants + ): + return value + name = next((path.name for path in reversed(path_variants) if path.name), "") + return f"local-path/{name}" if name else "local-path" + + def _require_text(value: object, label: str) -> None: if not isinstance(value, str) or not value.strip(): raise LocalAuditEventError(f"{label} must be a non-empty string") diff --git a/tests/test_local_audit_event.py b/tests/test_local_audit_event.py index 0cff853..3945634 100644 --- a/tests/test_local_audit_event.py +++ b/tests/test_local_audit_event.py @@ -5,13 +5,66 @@ from aethermesh_core.local_audit_event import ( AUDIT_EVENT_TYPES, + AUDIT_REDACTED_VALUE, LocalAuditEventError, append_local_audit_event, + sanitize_local_audit_event, validate_local_audit_event, ) class LocalAuditEventTests(unittest.TestCase): + def test_sanitizes_secrets_private_content_and_absolute_paths(self) -> None: + event = { + **_referenced_event(), + "related_file_paths": ["/Users/private/node/manifests/work-001.json"], + "hashes": { + "result_hash": "sha256:example", + "apiKey": "private-api-key", + "access_token": "private-token", + "environment": "HOME=/Users/private", + }, + "signatures": { + "privateKey": "private-key", + "authorization": "Bearer private-token", + "credential": "private-credential", + "password": "private-password", + "secret": "private-secret", + "seed": "private-seed", + }, + } + + sanitized = sanitize_local_audit_event(event) + + self.assertEqual(sanitized["related_file_paths"], ["local-path/work-001.json"]) + self.assertEqual(sanitized["hashes"]["result_hash"], "sha256:example") + for field in ("apiKey", "access_token", "environment"): + self.assertEqual(sanitized["hashes"][field], AUDIT_REDACTED_VALUE) + for value in sanitized["signatures"].values(): + self.assertEqual(value, AUDIT_REDACTED_VALUE) + + def test_appended_sanitized_event_keeps_audit_attribution(self) -> None: + event = { + **_referenced_event(), + "related_file_paths": ["/home/private/aethermesh/work-001.json"], + "signatures": {"token": "must-not-be-written"}, + } + with tempfile.TemporaryDirectory() as temp_dir: + path = Path(temp_dir) / "events.jsonl" + written = append_local_audit_event(path, event) + output = path.read_text(encoding="utf-8") + + self.assertNotIn("must-not-be-written", output) + self.assertNotIn("/home/private", output) + self.assertEqual(written["creator_node_id"], "creator-local-a") + self.assertEqual(written["manifest_id"], "manifest-work-001") + self.assertEqual(written["validation_receipt_id"], "receipt-work-001") + self.assertEqual(written["lineage_parent_ids"], ["work-parent-001"]) + self.assertEqual( + written["contribution_attribution_ids"], + ["local-contribution-work-001"], + ) + def test_validates_required_fields_and_allows_missing_optional_fields(self) -> None: event = _minimal_event() From f1a83564b4bd82ab73c41482907b49944443e0b6 Mon Sep 17 00:00:00 2001 From: ExoHayvan <18trevor3695@gmail.com> Date: Fri, 17 Jul 2026 23:41:59 -0400 Subject: [PATCH 2/4] fix: address automated review feedback --- src/aethermesh_core/local_audit_event.py | 2 ++ tests/test_local_audit_event.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/aethermesh_core/local_audit_event.py b/src/aethermesh_core/local_audit_event.py index d90a929..85c1ef2 100644 --- a/src/aethermesh_core/local_audit_event.py +++ b/src/aethermesh_core/local_audit_event.py @@ -245,6 +245,8 @@ def _is_private_audit_key(key: str) -> bool: normalized = re.sub(r"[^a-z0-9]", "", key.lower()) if normalized in _SENSITIVE_KEY_PARTS or normalized in _PRIVATE_CONTENT_KEYS: return True + if "apikey" in normalized or "privatekey" in normalized: + return True parts = { part for part in re.split(r"[^A-Za-z0-9]+|(?<=[a-z])(?=[A-Z])", key) if part } diff --git a/tests/test_local_audit_event.py b/tests/test_local_audit_event.py index 3945634..5ff889c 100644 --- a/tests/test_local_audit_event.py +++ b/tests/test_local_audit_event.py @@ -21,11 +21,13 @@ def test_sanitizes_secrets_private_content_and_absolute_paths(self) -> None: "hashes": { "result_hash": "sha256:example", "apiKey": "private-api-key", + "service_api_key": "private-service-api-key", "access_token": "private-token", "environment": "HOME=/Users/private", }, "signatures": { "privateKey": "private-key", + "signingPrivateKey": "private-signing-key", "authorization": "Bearer private-token", "credential": "private-credential", "password": "private-password", @@ -38,7 +40,7 @@ def test_sanitizes_secrets_private_content_and_absolute_paths(self) -> None: self.assertEqual(sanitized["related_file_paths"], ["local-path/work-001.json"]) self.assertEqual(sanitized["hashes"]["result_hash"], "sha256:example") - for field in ("apiKey", "access_token", "environment"): + for field in ("apiKey", "service_api_key", "access_token", "environment"): self.assertEqual(sanitized["hashes"][field], AUDIT_REDACTED_VALUE) for value in sanitized["signatures"].values(): self.assertEqual(value, AUDIT_REDACTED_VALUE) From 627bd51e662774ef03c24cde510d8b76dd39d593 Mon Sep 17 00:00:00 2001 From: ExoHayvan <18trevor3695@gmail.com> Date: Fri, 17 Jul 2026 23:54:07 -0400 Subject: [PATCH 3/4] fix: address automated review feedback --- src/aethermesh_core/local_audit_event.py | 34 ++++++++++++++++++------ tests/test_local_audit_event.py | 21 ++++++++++++++- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/aethermesh_core/local_audit_event.py b/src/aethermesh_core/local_audit_event.py index 85c1ef2..5e3fb3c 100644 --- a/src/aethermesh_core/local_audit_event.py +++ b/src/aethermesh_core/local_audit_event.py @@ -32,7 +32,23 @@ } ) _PRIVATE_CONTENT_KEYS = frozenset( - {"env", "environment", "payload", "requestpayload", "rawrequestpayload"} + { + "env", + "environment", + "environmentmap", + "environmentvariables", + "envmap", + "envvars", + "payload", + "rawpayload", + "rawrequest", + "rawrequestpayload", + "requestbody", + "requestpayload", + } +) +_LOCAL_PATH_IN_TEXT = re.compile( + r"(? bool: normalized = re.sub(r"[^a-z0-9]", "", key.lower()) if normalized in _SENSITIVE_KEY_PARTS or normalized in _PRIVATE_CONTENT_KEYS: return True - if "apikey" in normalized or "privatekey" in normalized: - return True - parts = { - part for part in re.split(r"[^A-Za-z0-9]+|(?<=[a-z])(?=[A-Z])", key) if part - } - return any(part.lower() in _SENSITIVE_KEY_PARTS for part in parts) + return any(part in normalized for part in _SENSITIVE_KEY_PARTS) def _sanitize_local_path(value: str) -> str: @@ -258,7 +269,14 @@ def _sanitize_local_path(value: str) -> str: if not value.startswith("~") and not any( path.is_absolute() for path in path_variants ): - return value + return _LOCAL_PATH_IN_TEXT.sub( + lambda match: _local_path_label(match.group()), value + ) + return _local_path_label(value) + + +def _local_path_label(value: str) -> str: + path_variants = (Path(value), PureWindowsPath(value)) name = next((path.name for path in reversed(path_variants) if path.name), "") return f"local-path/{name}" if name else "local-path" diff --git a/tests/test_local_audit_event.py b/tests/test_local_audit_event.py index 5ff889c..a486062 100644 --- a/tests/test_local_audit_event.py +++ b/tests/test_local_audit_event.py @@ -24,6 +24,7 @@ def test_sanitizes_secrets_private_content_and_absolute_paths(self) -> None: "service_api_key": "private-service-api-key", "access_token": "private-token", "environment": "HOME=/Users/private", + "environment_variables": "HOME=/Users/private", }, "signatures": { "privateKey": "private-key", @@ -40,7 +41,13 @@ def test_sanitizes_secrets_private_content_and_absolute_paths(self) -> None: self.assertEqual(sanitized["related_file_paths"], ["local-path/work-001.json"]) self.assertEqual(sanitized["hashes"]["result_hash"], "sha256:example") - for field in ("apiKey", "service_api_key", "access_token", "environment"): + for field in ( + "apiKey", + "service_api_key", + "access_token", + "environment", + "environment_variables", + ): self.assertEqual(sanitized["hashes"][field], AUDIT_REDACTED_VALUE) for value in sanitized["signatures"].values(): self.assertEqual(value, AUDIT_REDACTED_VALUE) @@ -67,6 +74,18 @@ def test_appended_sanitized_event_keeps_audit_attribution(self) -> None: ["local-contribution-work-001"], ) + def test_sanitizes_embedded_local_path_in_short_summary(self) -> None: + event = { + **_referenced_event(), + "error_summary": "could not read /Users/private/node/key.pem", + } + + sanitized = sanitize_local_audit_event(event) + + self.assertEqual( + sanitized["error_summary"], "could not read local-path/key.pem" + ) + def test_validates_required_fields_and_allows_missing_optional_fields(self) -> None: event = _minimal_event() From 6633924b94f8f1f3cf3cbe8c482038b4fd48bee5 Mon Sep 17 00:00:00 2001 From: ExoHayvan <18trevor3695@gmail.com> Date: Sat, 18 Jul 2026 00:04:49 -0400 Subject: [PATCH 4/4] fix: address automated review feedback --- src/aethermesh_core/local_audit_event.py | 2 +- tests/test_local_audit_event.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/aethermesh_core/local_audit_event.py b/src/aethermesh_core/local_audit_event.py index 5e3fb3c..31f2b78 100644 --- a/src/aethermesh_core/local_audit_event.py +++ b/src/aethermesh_core/local_audit_event.py @@ -48,7 +48,7 @@ } ) _LOCAL_PATH_IN_TEXT = re.compile( - r"(? None: def test_sanitizes_embedded_local_path_in_short_summary(self) -> None: event = { **_referenced_event(), - "error_summary": "could not read /Users/private/node/key.pem", + "error_summary": ( + "could not read path:/Users/private/node/key.pem or " + "file:///home/private/node/backup.pem" + ), } sanitized = sanitize_local_audit_event(event) self.assertEqual( - sanitized["error_summary"], "could not read local-path/key.pem" + sanitized["error_summary"], + "could not read path:local-path/key.pem or local-path/backup.pem", ) def test_validates_required_fields_and_allows_missing_optional_fields(self) -> None: