Repository navigation
security(admission): make audit append path link-safe and private #250
Description
Finding
Fresh review of Agent Artifact Admission at exact parent feat/agent-artifact-admission@473f689fa6fd0c9662e4709e5d6257715371bf36 found a second local-filesystem authority gap after the policy-file integrity lane.
FileAuditSink::open_append_only currently uses ordinary OpenOptions::create(true).append(true).open(path). On Unix this follows a final symbolic link and creates a new audit file under the process umask rather than an explicit evidence confidentiality/integrity mode. The audit stream intentionally contains minimized but security-relevant actor/workspace/source/artifact/policy evidence. An attacker able to pre-place the configured audit filename as a symlink can redirect append writes to an unintended target; a permissive umask can also make newly created security evidence visible beyond the intended service/security-operator boundary.
This is Wardnet's own security-evidence persistence boundary. It does not move storage/IAM authority into Wardnet, create a log shipper, duplicate quarantine isolation, EgressWeave transport policy, contextual-orchestrator behavior, AppGuardrail analysis or Context Graph truth.
Hostile RED acceptance
Stack after the current configuration-permission child is integrated into #129; do not create parallel source churn while that exact-head RED is waiting for compute.
On a Unix test fixture:
- Create an unrelated regular target containing a sentinel, create the configured audit path as a symlink to that target, then append one valid record through
FileAuditSink. The append must fail closed and the sentinel target must remain byte-for-byte unchanged. - Create a fresh audit path under a temporary directory while the process umask permits broader creation. After one successful append, the resulting regular audit file must have no group/other permission bits.
- Existing append-order, sync, oversized-record and unavailable-parent controls must remain GREEN.
A runner/bootstrap/rustfmt failure is not semantic RED.
Minimum GREEN acceptance
Use the OS-native open/create boundary rather than a pre-open symlink_metadata check that introduces a TOCTOU window. On Unix, open the final audit file without following a symlink and create it with service-private permissions; after open, verify the descriptor names a regular file before writing. Keep O_APPEND semantics, the existing process-local serialization lock, flush + sync_data, bounded line encoding and stable non-secret AuditError::StorageUnavailable behavior. Do not truncate or rewrite existing evidence.
If a portable equivalent cannot preserve the same security property on another platform, fail closed rather than silently falling back to link-following evidence writes; document the supported deployment boundary.
Traceability
- MITRE CWE-59, Improper Link Resolution Before File Access ('Link Following'): link following can let an attacker redirect file modification and bypass protection mechanisms.
- Linux
open(2)/openat2(2):O_NOFOLLOWprevents following the final pathname component; stronger resolution constraints are available throughopenat2where an implementation chooses to use them. - NIST SP 800-53 Rev. 5 CM-5: access restrictions for system changes must be defined and enforced; audit/evidence files are part of the security-control implementation and must not be writable through unintended filesystem aliases.
Re-read the current primary references when doctoring the repair. Keep ADR/runbook/threat-model statements code-current without editing docs/product-technical-gap-baseline.md outside its sole writer #130.
Activity
seonghobae commentedon Sep 10, 2026
#249 is now ordinarily integrated into the canonical Agent Artifact Admission branch at merge commit
fb82aafefb6f014875867407766b7d5e1d9dfcfbafter exact child509e6331f33533b1618bed7463dd2d4c83e7d84areached CI34442206881SUCCESS, Fuzz34442206842SUCCESS, CodeRabbit status SUCCESS, and zero unresolved review threads. No force update or protected-main claim.The #250 test-first lane is now Draft #251, based exactly on
feat/agent-artifact-admission@fb82aaf..., with test-only head8b65dcd8febff005a18fa105ce779e4e22ddda74. Production source is byte-identical to the parent. The hostile contract covers final-symlink redirection and private creation under an isolatedumask 000; current CI34443639338and Fuzz34443639327are queued and are non-passing until semantic execution. No source repair will be applied before a valid semantic RED.seonghobae commentedon Sep 10, 2026
Completion receipt: PR #251 exact
f527a4753037357ce7d67c78e75dda59acbe5ce5carried the complete #250 hostile contract. Test-only8b65dcd8febff005a18fa105ce779e4e22ddda74produced semantic RED for final-symlink redirection/private creation underumask 000; the Linux production repair uses a singleO_NOFOLLOWappend/create open with mode0600, verifies the opened descriptor is regular, preserves serialization/bounded encoding/flush+sync_data/no-truncate/stable storage errors, and fails the file sink closed off Linux. Exact CI34444655764and Fuzz34444655776reached SUCCESS with zero review threads, and #251 was ordinarily integrated into canonical #129 before the follow-on special-file availability case. No valid #250 delta remains outside the canonical lineage.