Severity: P1 critical
Problem
The Write/Edit guards decide whether a file is protected by matching the raw file_path string. They do not resolve symlinks before checking whether the target is under .codearbiter/.
Source evidence:
plugins/ca/hooks/pre-write.py:25 reads tool_input.file_path and normalizes separators only.
plugins/ca/hooks/pre-write.py:29-42 blocks only when is_audit_log(fpath) or is_decisions_path(fpath) matches that raw path.
plugins/ca/hooks/pre-edit.py:29-34 and pre-edit.py:64-70 do the same raw-path checks.
_hooklib.repo_rel() already resolves real paths in plugins/ca/hooks/_hooklib.py:216-231, but pre-write/pre-edit do not use it.
A symlink whose visible path does not contain .codearbiter/ can point directly at .codearbiter, and the Write/Edit tools will follow the symlink after the hook allows the operation.
Reproduction
In a throwaway enabled repo:
- Create
.codearbiter/overrides.log containing seed.
- Create a symlink
alias -> .codearbiter.
- Invoke
pre-write.py with file_path set to <repo>/alias/overrides.log.
- Invoke
pre-edit.py with file_path set to the same symlinked path.
Observed locally:
pre-write.py <repo>/alias/overrides.log
exit 0
stderr
pre-edit.py <repo>/alias/overrides.log
exit 0
stderr
pre-write.py <repo>/.codearbiter/overrides.log
exit 2
stderr BLOCKED [H-05]: The .codearbiter audit logs ... are append-only ...
The canonical path blocks, while the symlink alias to the same target is allowed.
Impact
An agent can overwrite or edit append-only audit logs and ADR files through a symlink alias, bypassing H-05 and H-11. This breaks the audit-trail and immutable-decision guarantees after a single unguarded symlink setup step.
Smallest credible fix
Resolve protected file targets before classification:
- In pre-write/pre-edit, compute the repo-relative real path with the same
repo_rel()/realpath approach used elsewhere.
- Apply H-05/H-11 to both the raw path and the resolved repo-relative target.
- Add tests for a symlinked directory and a symlinked file targeting
.codearbiter/overrides.log and .codearbiter/decisions/*.md.
- Consider blocking symlink creation that targets protected
.codearbiter paths through the Bash guard as defense in depth.
Duplicate check
Checked existing issues with:
gh issue list --repo arbiterForge/codeArbiter --state all --search "symlink audit log overrides.log pre-write pre-edit ADR decisions bypass"
No matching issue was returned.
Severity: P1 critical
Problem
The Write/Edit guards decide whether a file is protected by matching the raw
file_pathstring. They do not resolve symlinks before checking whether the target is under.codearbiter/.Source evidence:
plugins/ca/hooks/pre-write.py:25readstool_input.file_pathand normalizes separators only.plugins/ca/hooks/pre-write.py:29-42blocks only whenis_audit_log(fpath)oris_decisions_path(fpath)matches that raw path.plugins/ca/hooks/pre-edit.py:29-34andpre-edit.py:64-70do the same raw-path checks._hooklib.repo_rel()already resolves real paths inplugins/ca/hooks/_hooklib.py:216-231, but pre-write/pre-edit do not use it.A symlink whose visible path does not contain
.codearbiter/can point directly at.codearbiter, and the Write/Edit tools will follow the symlink after the hook allows the operation.Reproduction
In a throwaway enabled repo:
.codearbiter/overrides.logcontainingseed.alias -> .codearbiter.pre-write.pywithfile_pathset to<repo>/alias/overrides.log.pre-edit.pywithfile_pathset to the same symlinked path.Observed locally:
The canonical path blocks, while the symlink alias to the same target is allowed.
Impact
An agent can overwrite or edit append-only audit logs and ADR files through a symlink alias, bypassing H-05 and H-11. This breaks the audit-trail and immutable-decision guarantees after a single unguarded symlink setup step.
Smallest credible fix
Resolve protected file targets before classification:
repo_rel()/realpathapproach used elsewhere..codearbiter/overrides.logand.codearbiter/decisions/*.md..codearbiterpaths through the Bash guard as defense in depth.Duplicate check
Checked existing issues with:
No matching issue was returned.