Skip to content

fix(security): write config atomically with 0700 dir / 0600 file - #29

Merged
duyetbot merged 1 commit into
mainfrom
cursor/004-config-atomic-0600-d2d5
Aug 28, 2026
Merged

fix(security): write config atomically with 0700 dir / 0600 file#29
duyetbot merged 1 commit into
mainfrom
cursor/004-config-atomic-0600-d2d5

Conversation

@duyet

@duyet duyet commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #19 (Plan 004). write_config now creates ~/.anyrouter with mode 0700 (unix), writes config.yaml.tmp, chmods it 0600, then renames over the destination so a crash cannot truncate the live file that holds api_key / management_key / relay_token.

Signature of write_config(config: &Config, path: &Path) -> Result<(), String> is unchanged. No keyring. Windows ACL hardening is out of scope (#[cfg(unix)] only).

Drift check

git diff --stat 61ee3c7..HEAD -- src/config.rs src/key.rs at plan time: src/config.rs gained the extra-field serialize round-trip only. Live write_config matched the issue excerpt, so this proceeded.

Other fs::write(...config.yaml...) sites are test fixtures in src/upgrade.rs (auto_update_config_false_disables, resolve_channel_reads_config_then_env). Production credential writes still funnel through write_config. Those fixtures were not changed.

Path::new("/x/config.yaml").with_extension("yaml.tmp") yields /x/config.yaml.tmp (not config.yaml.yaml.tmp).

Dir chmod uses let mut perms (the plan snippet omitted mut; PermissionsExt::set_mode needs it, matching the file-chmod block and upgrade.rs).

Reviewer entry

  • src/config.rs write_config — dir 0700, temp + rename, file 0600
  • two new unit tests: owner-only perms (unix-gated body) and no leftover tmp

Checklist

  • Version stays on 0.1.x (do not introduce 0.2 / 1.0)
  • Do not auto-merge release-please PRs

Verification

  • grep -n set_mode src/config.rs — exactly two sites (dir 0o700, file 0o600)
  • pub fn write_config(config: &Config, path: &Path) -> Result<(), String> unchanged
  • only src/config.rs modified
  • cargo test --locked --lib config — pass (incl. the two new tests)
  • cargo test --locked --all-targets — 156 lib + 52 cli + 7 release_lock
  • cargo clippy --locked --all-targets — no new warnings in src/config.rs
Open in Web Open in Cursor 

write_config used create_dir_all + in-place fs::write, so umask left
~/.anyrouter at 0755 and config.yaml at 0644, and a crash mid-write
truncated the file that holds api_key / management_key / relay_token.

After creating the dir, set 0700 on unix. Serialize to config.yaml.tmp,
chmod 0600, then rename over the destination (remove the tmp on rename
failure). Signature of write_config is unchanged.

Closes #19

Co-authored-by: Duyet Le <me@duyet.net>
Co-authored-by: duyetbot <bot@duyet.net>
@duyet
duyet marked this pull request as ready for review August 28, 2026 04:42
@github-actions

Copy link
Copy Markdown
Contributor

anyr size and startup

Startup is wall time for a cold anyr --version / anyr --help (median of 21 runs). Size is the stripped release binary, or the .wasm for the browser demo.

Size budget: anyr-linux-x86_64 must stay ≤ 4.0 MiB (4194304 bytes) stripped.

Asset Kind Size --version median --help median
anyr-darwin-arm64 native 2.7 MiB 6.29 ms 4.68 ms
anyr-darwin-x86_64 native 2.9 MiB 31.7 ms 29.3 ms
anyr-linux-arm64 native 2.6 MiB 0.7 ms 0.72 ms
anyr-linux-x86_64 native 3.0 MiB 1.02 ms 1.01 ms
anyr-windows-x86_64.exe native 2.5 MiB 9.19 ms 9.24 ms
anyr.wasm wasm 175.1 KiB

Budget check: ok

raw timings
[
  {
    "asset": "anyr-darwin-arm64",
    "kind": "native",
    "path": "/Users/runner/work/cli/cli/anyr-darwin-arm64",
    "bytes": 2803528,
    "size": "2.7 MiB",
    "version": "0.1.11 (built 2026-08-28 04:43:32)",
    "target": "aarch64-apple-darwin",
    "os": "macos-latest",
    "startup_version": {
      "n": 21,
      "min_ms": 5.24,
      "median_ms": 6.29,
      "p95_ms": 10.06,
      "mean_ms": 6.79
    },
    "startup_help": {
      "n": 21,
      "min_ms": 3.41,
      "median_ms": 4.68,
      "p95_ms": 5.7,
      "mean_ms": 4.64
    }
  },
  {
    "asset": "anyr-darwin-x86_64",
    "kind": "native",
    "path": "/Users/runner/work/cli/cli/anyr-darwin-x86_64",
    "bytes": 3014704,
    "size": "2.9 MiB",
    "version": "0.1.11 (built 2026-08-28 04:43:27)",
    "target": "x86_64-apple-darwin",
    "os": "macos-latest",
    "startup_version": {
      "n": 21,
      "min_ms": 29.04,
      "median_ms": 31.7,
      "p95_ms": 36.05,
      "mean_ms": 32.36
    },
    "startup_help": {
      "n": 21,
      "min_ms": 28.15,
      "median_ms": 29.3,
      "p95_ms": 30.22,
      "mean_ms": 29.35
    }
  },
  {
    "asset": "anyr-linux-arm64",
    "kind": "native",
    "path": "/home/runner/work/cli/cli/anyr-linux-arm64",
    "bytes": 2757672,
    "size": "2.6 MiB",
    "version": "0.1.11 (built 2026-08-28 04:42:51)",
    "target": "aarch64-unknown-linux-gnu",
    "os": "ubuntu-24.04-arm",
    "startup_version": {
      "n": 21,
      "min_ms": 0.67,
      "median_ms": 0.7,
      "p95_ms": 0.81,
      "mean_ms": 0.72
    },
    "startup_help": {
      "n": 21,
      "min_ms": 0.69,
      "median_ms": 0.72,
      "p95_ms": 0.79,
      "mean_ms": 0.73
    }
  },
  {
    "asset": "anyr-linux-x86_64",
    "kind": "native",
    "path": "/home/runner/work/cli/cli/anyr-linux-x86_64",
    "bytes": 3112584,
    "size": "3.0 MiB",
    "version": "0.1.11 (built 2026-08-28 04:42:51)",
    "target": "x86_64-unknown-linux-gnu",
    "os": "ubuntu-latest",
    "startup_version": {
      "n": 21,
      "min_ms": 0.99,
      "median_ms": 1.02,
      "p95_ms": 1.07,
      "mean_ms": 1.02
    },
    "startup_help": {
      "n": 21,
      "min_ms": 0.98,
      "median_ms": 1.01,
      "p95_ms": 1.11,
      "mean_ms": 1.02
    }
  },
  {
    "asset": "anyr-windows-x86_64.exe",
    "kind": "native",
    "path": "D:\\a\\cli\\cli\\anyr-windows-x86_64.exe",
    "bytes": 2577920,
    "size": "2.5 MiB",
    "version": "0.1.11 (built 2026-08-28T04:44:13Z)",
    "target": "x86_64-pc-windows-msvc",
    "os": "windows-latest",
    "startup_version": {
      "n": 21,
      "min_ms": 8.98,
      "median_ms": 9.19,
      "p95_ms": 9.74,
      "mean_ms": 9.27
    },
    "startup_help": {
      "n": 21,
      "min_ms": 8.96,
      "median_ms": 9.24,
      "p95_ms": 10.67,
      "mean_ms": 9.44
    }
  },
  {
    "asset": "anyr.wasm",
    "kind": "wasm",
    "path": "/home/runner/work/cli/cli/target/wasm-pkg/anyr_cli_bg.wasm",
    "bytes": 179312,
    "size": "175.1 KiB",
    "version": "wasm",
    "target": "wasm32-unknown-unknown",
    "os": ""
  }
]

@duyetbot
duyetbot merged commit 9ca7ac7 into main Aug 28, 2026
9 checks passed
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.

Plan 004: Write the config atomically with owner-only permissions

3 participants