Summary
Render.dump_to_dp() writes file content before applying the requested mode and ownership. With the common process umask 0022, a render requested as 0600 or 0640 is initially created as 0644 and can be observed by unrelated local users before chmod runs.
Render.update_on_dp() also removes the current file before recreating it, so readers can observe a missing, partial, or broadly readable replacement. A process interruption can leave the destination truncated or with unintended permissions.
Minimal reproduction
On a host with umask 0022:
- Create a render containing a non-production placeholder such as
example_secret=not-a-real-secret.
- Set the requested mode to
0600.
- Pause or instrument execution immediately after
f.write(self.content) and before os.chmod(self.path, mode).
- Inspect the destination mode and read it from another unprivileged local account.
- For update behavior, observe the path between
delete_from_dp() and dump_to_dp().
The current implementation performs these operations in this order:
with open(self.path, "w") as f:
f.write(self.content)
os.chmod(self.path, mode)
os.chown(self.path, owner, group)
and updates by deleting the destination before calling dump_to_dp().
Expected result
Rendered content is never present with permissions broader than the requested mode, and updates become visible atomically as a complete file.
Actual result
The destination can temporarily be 0644 before the requested restrictive mode is applied. Updates also introduce an unlink/recreate window and expose partially written state.
Security impact
Render resources can contain credentials or other sensitive configuration. A local unprivileged process can race the write and read content that should only be available to the configured owner or group. Non-atomic replacement also creates integrity and availability risk for consumers reading the file concurrently.
Suggested fix
Resolve the requested uid, gid, and mode before writing. Create a unique temporary file in the destination directory with restrictive permissions, apply final ownership and mode before writing sensitive content, write and flush the complete content, fsync it, and replace the destination with os.replace(). Then fsync the parent directory and invoke on_change. Do not unlink the destination first.
Add regression coverage that verifies:
- content is never written to a file with broader permissions than requested;
- the old complete file remains visible until atomic replacement;
- failure before replacement leaves the old destination intact;
on_change runs only after the replacement succeeds.
Confirmed on current master at 1f793118fad2f171b34412e59b720bdad6197290.
Summary
Render.dump_to_dp()writes file content before applying the requested mode and ownership. With the common process umask0022, a render requested as0600or0640is initially created as0644and can be observed by unrelated local users beforechmodruns.Render.update_on_dp()also removes the current file before recreating it, so readers can observe a missing, partial, or broadly readable replacement. A process interruption can leave the destination truncated or with unintended permissions.Minimal reproduction
On a host with umask
0022:example_secret=not-a-real-secret.0600.f.write(self.content)and beforeos.chmod(self.path, mode).delete_from_dp()anddump_to_dp().The current implementation performs these operations in this order:
and updates by deleting the destination before calling
dump_to_dp().Expected result
Rendered content is never present with permissions broader than the requested mode, and updates become visible atomically as a complete file.
Actual result
The destination can temporarily be
0644before the requested restrictive mode is applied. Updates also introduce an unlink/recreate window and expose partially written state.Security impact
Render resources can contain credentials or other sensitive configuration. A local unprivileged process can race the write and read content that should only be available to the configured owner or group. Non-atomic replacement also creates integrity and availability risk for consumers reading the file concurrently.
Suggested fix
Resolve the requested uid, gid, and mode before writing. Create a unique temporary file in the destination directory with restrictive permissions, apply final ownership and mode before writing sensitive content, write and flush the complete content,
fsyncit, and replace the destination withos.replace(). Thenfsyncthe parent directory and invokeon_change. Do not unlink the destination first.Add regression coverage that verifies:
on_changeruns only after the replacement succeeds.Confirmed on current
masterat1f793118fad2f171b34412e59b720bdad6197290.