{"record":{"id":"5a1f5c1ea15bb8b0","repo":"NousResearch/hermes-agent","slug":"refusing-to-start-could-not-pre-create-audit-log","errorCode":null,"errorMessage":"Refusing to start: could not pre-create audit log {audit_path} with restrictive permissions ({exc}).  Move or chmod any existing file at that path and retry.","messagePattern":"Refusing to start: could not pre-create audit log (.+?) with restrictive permissions \\((.+?)\\)\\.  Move or chmod any existing file at that path and retry\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"agent/proxy_sources/iron_proxy.py","lineNumber":1352,"sourceCode":"    file is non-load-bearing until the version bump — but the qualified\n    message keeps operators from wiring monitoring to a path that can't\n    exist.\n    \"\"\"\n\n    try:\n        # Use os.open + O_CREAT to avoid races on the chmod.\n        open_flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND\n        if hasattr(os, \"O_NOFOLLOW\"):\n            open_flags |= os.O_NOFOLLOW\n        fd = os.open(str(audit_path), open_flags, 0o600)\n        try:\n            # Tighten perms even if the file already existed under a\n            # slacker umask.\n            os.fchmod(fd, 0o600)\n        finally:\n            os.close(fd)\n    except OSError as exc:\n        raise RuntimeError(\n            f\"Refusing to start: could not pre-create audit log \"\n            f\"{audit_path} with restrictive permissions ({exc}).  \"\n            f\"Move or chmod any existing file at that path and retry.\"\n        ) from exc\n\n\ndef write_proxy_config(config: Dict) -> Path:\n    \"\"\"Serialize the config dict to ``<hermes_home>/proxy/proxy.yaml``.\n\n    Uses ``yaml.safe_dump`` so we never emit Python tags.\n    \"\"\"\n\n    try:\n        import yaml  # PyYAML is already a Hermes dep\n    except ImportError as exc:\n        raise RuntimeError(\n            \"PyYAML is required to write the iron-proxy config but is not \"\n            \"installed.\"","sourceCodeStart":1334,"sourceCodeEnd":1370,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/agent/proxy_sources/iron_proxy.py#L1334-L1370","documentation":"Before launching the daemon, start_proxy() pre-creates the audit log with O_WRONLY|O_CREAT|O_APPEND (+O_NOFOLLOW where available) and force-chmods it 0600, so the audit trail is never world-readable and never follows a planted symlink. If the os.open/fchmod fails (wrong permissions on an existing file, symlink attack ELOOP, unwritable directory, SELinux denial), startup is refused rather than proceeding with a weakened audit trail.","triggerScenarios":"start_proxy() / `hermes egress start` when the audit log path exists with wrong ownership/perms (e.g. created earlier by root, or 0644 with a sticky context), is a symlink (O_NOFOLLOW → ELOOP), or its parent dir is not writable by the current user.","commonSituations":"Running the daemon once under sudo then again as the normal user; restored backups that lost ownership; security modules (SELinux) denying the open; a same-uid attacker planting a symlink.","solutions":["Inspect the path in the message: `ls -l <audit_path>` — if it's a symlink or foreign-owned file, remove or chown/chmod it (chmod 600) and retry.","Ensure the parent proxy state directory is owned by the user running Hermes.","Don't run `hermes egress start` under mixed sudo/non-sudo invocations; pick one identity."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import os\nfrom pathlib import Path\n\ndef audit_log_writable(path: Path) -> bool:\n    if path.is_symlink():\n        return False\n    if path.exists():\n        st = path.stat()\n        return os.geteuid() == st.st_uid and (st.st_mode & 0o600 == 0o600)\n    return os.access(path.parent, os.W_OK)","typeGuard":null,"tryCatchPattern":"try:\n    start_proxy()\nexcept RuntimeError as e:\n    if \"audit log\" in str(e):\n        # fix perms/ownership of the named path, then retry\n        raise","preventionTips":["Run all egress commands as one identity — never mix sudo and non-sudo against the same state dir.","Ensure the proxy state dir is user-owned before first start.","Never symlink the audit log path."],"tags":["filesystem","permissions","security","iron-proxy"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}