Bind config fragment gates to one no-follow open descriptor
When gating an application-owned config fragment before atomic rename publish, open the target once with no-follow semantics and run ownership and expected-byte checks through that descriptor so stat and read cannot diverge across a symlink swap.
Bind config fragment gates to one no-follow open descriptor
When to use
Use this when you already follow ownership and expected-byte gating before temp-then-rename publish on an application-owned configuration fragment, and the managed directory may be shared, agent-writable, or otherwise not fully trusted.
Adjacent guidance covers the base replace flow, the publish gate immediately before rename, and writer-serialization limits. This skill covers only the descriptor binding step: making ownership and byte checks observe the same directory entry.
The failure it prevents
Path-string gates split metadata and content across separate lookups:
- Gate A stats the path string and sees a regular file with the expected owner and mode.
- An attacker or racing actor replaces the path with a symlink to a different file, or swaps which inode the name resolves to.
- Gate B reads bytes through the path string and compares them to the baseline.
- The two gates may have inspected different inodes. Ownership can pass while bytes come from elsewhere, or bytes can match while the entry type is no longer a regular file.
Reasoned example (not an executed test): an agent stats managed/overlay.toml, sees application ownership, then a local process replaces that name with a symlink to a user-owned template with identical bytes. A separate path read still passes the expected-bytes gate, and rename publish replaces the symlink name rather than the file the agent believed it validated.
Assumes
The caller already:
- Treats the fragment as application-owned inside a declared managed directory.
- Captures an expected-byte baseline in the same trusted operation.
- Writes a temp file on the same filesystem and publishes with rename.
- Re-runs publish gates immediately before rename when work between gates is non-trivial.
Procedure
- Open the target through no-follow semantics. Use the platform equivalent of opening without following the final symlink component. If the final component is a symlink, fail closed instead of following it.
- Run the ownership gate on the open descriptor. Read metadata from the descriptor (for example
fstat), not from a separate path stat:
- Confirm regular-file type.
- Confirm owner identity and mode match the application contract.
- Confirm size is within allowed bounds.
- Run the expected-bytes gate on the same descriptor. Read raw bytes from that descriptor (for example
readorpreadfrom offset zero) and compare to the fixed baseline. Do not open the path string again for a second read in the same gate pass.
- Repeat descriptor-bound gates at publish time. Immediately before rename, open again with no-follow semantics and repeat steps 2 and 3 against the same fixed baseline. Do not reuse metadata or bytes captured before temp preparation unless the publish gate just re-ran on a fresh descriptor open.
- Prefer directory-relative opens when the contract allows. Open the managed directory as a directory descriptor, then open the fragment relative to that descriptor with no-follow semantics. This reduces races where the path string is repointed outside the managed tree between checks.
- On any descriptor gate failure, fail closed. Do not rename. Remove the attempt temp. Surface a concurrent edit, symlink substitution, or stale baseline outcome.
What this does not provide
- Not a substitute for publish-gate timing. Descriptor binding closes the stat-read split within one open; it does not remove the need to re-run gates immediately before rename when another actor may edit between temp write and publish.
- Not writer compare-and-swap. Two writers can still pass descriptor-bound gates against the same baseline and race on rename.
- Not rename-by-descriptor. Publish still uses path or directory-relative rename primitives. The descriptor gates prove which entry you validated; rename must still target that same managed name immediately after the final gate pass.
Failure policy
- Never stat a path string and read bytes through a separate path open in the same gate pass.
- Never follow symlinks at the target component when the contract expects a regular file.
- Never treat matching bytes as proof the entry is still a regular file if type was checked on a different lookup than the read.
Claims and evidence
The descriptor-binding sequence follows from standard symlink and time-of-check reasoning applied to split path lookups. The symlink substitution timeline is a reasoned example only; no filesystem attack tests were executed for this skill.