Skip to content

fix: Create key and seed files with owner-only permissions - #284

Merged
raymondk merged 3 commits into
masterfrom
lwshang/secret-file-perms
Nov 5, 2026
Merged

raymondk merged 3 commits into
masterfrom
lwshang/secret-file-perms

Conversation

@lwshang

@lwshang lwshang commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Summary

quill generate (PEM and seed files) and quill decrypt-pem wrote secret material with plain std::fs::write:

  • File modes depended on the umask, so with the common 022 the files ended up 0644.
  • Symbolic links at the output path were followed. The "exists and overwrite is not set" check used Path::exists(), which returns false for a dangling symlink.

This PR adds a write_secret_file helper in src/main.rs and routes all three write sites through it. The helper:

  • refuses to write if the output path is a symbolic link, whether or not its target exists;
  • without an overwrite flag, errors on any existing entry; with one, replaces only a regular file;
  • creates the file with create_new (O_EXCL) and, on Unix, mode 0o600.

The early checks in generate now use symlink_metadata, so a dangling link is rejected before the password prompt.

Behavior change: decrypt-pem still overwrites an existing regular file, but now refuses to write through a symlink.

Testing

  • New test generate_secret_files checks the 0600 mode (both on creation and on overwrite) and checks that a dangling symlink is rejected with and without --overwrite-pem-file.
  • Existing generate tests pass.

🤖 Generated with Claude Code

`quill generate` and `quill decrypt-pem` wrote secret material with
`std::fs::write`, so file modes depended on the umask (typically 0644)
and symbolic links at the output path were followed. The existence check
used `Path::exists()`, which reports false for a dangling symlink.

Route all secret writes through a helper that refuses symlinks, only
replaces regular files when overwriting is requested, and creates the
file with `create_new` and mode 0600 on Unix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Critical overwrite paths can delete an existing secret if opening its replacement fails or replace a seed before rejecting a PEM symlink, causing data loss and seed/PEM inconsistency.

2 open issues
What changed in this PR

This PR hardens secret-file output for quill generate and quill decrypt-pem by centralizing secure file creation.

Changes:

  • Creates secret files exclusively with owner-only permissions on Unix and rejects symbolic-link destinations.
  • Updates generation checks to detect dangling symlinks before prompting when overwrite is disabled.
  • Adds permission and symlink regression tests and documents the behavior change.
File Description
tests/​output/​root.rs Adds Unix permission and dangling-symlink tests.
src/​main.rs Introduces the secure-write helper and uses it for file output.
src/​commands/​generate.rs Routes seed and PEM writes through the helper and updates existence checks.
CHANGELOG.md Documents restricted permissions and symlink rejection.

Review effort: Balanced

Comment thread src/commands/generate.rs Resolved
Comment thread src/main.rs Resolved
- `generate` checks both the seed and PEM destinations before writing
  either, so a rejected PEM path can no longer clobber an existing seed.
- Overwriting writes to a 0600 temp file in the same directory and then
  renames it over the destination, so the old secret survives any
  failure before the rename.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Temporary filenames include the full destination basename, causing otherwise valid output paths to fail with ENAMETOOLONG during overwrite generation and decrypt-pem file output.

1 open issue
1 previously missed issue

In code that hasn't changed since last review

Low severity Test that overwrite repairs existing file permissions

tests/​output/​root.rs:150

The overwrite case starts with files that are already 0600, so it would also pass if overwriting regressed to std::fs::write and retained insecure permissions on an existing file. Set both files to 0644 before overwriting and assert 0600 for both afterward to cover the permission-repair behavior this PR introduces.

2 resolved since last review

Review effort: Balanced

Comment thread src/main.rs Resolved
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lwshang
lwshang marked this pull request as ready for review November 5, 2026 08:45
@lwshang
lwshang requested a review from a team as a code owner November 5, 2026 08:45
@zeropath-ai

zeropath-ai Bot commented Nov 5, 2026

Copy link
Copy Markdown

✅ No security or compliance issues detected. Reviewed everything up to ea99322.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► src/main.rs
    Add check_secret_path and create_secret_file helpers for secure secret file handling
► src/main.rs
    Implement write_secret_file function to safely write secret material with overwrite handling
► src/main.rs
    Implement create_secret_file to create files with owner-only permissions and safe write/rollback
Enhancement ► tests/output/root.rs
    Add generate_secret_files test to verify secret file creation, permissions, overwrite behavior, and symlink handling

@raymondk
raymondk merged commit 1109c55 into master Nov 5, 2026
33 checks passed
@lwshang lwshang mentioned this pull request Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants