Files
sshfs_connect/DECISIONS.md
T
nevaforget d01a358f35
Update PKGBUILD version / update-pkgver (push) Successful in 3s
refactor: harden ssh_config handling, mount path, and CLI UX from audit findings
Three rounds of audit-driven hardening, fully documented in DECISIONS.md:

- argv hardening: validate HostName/User/IdentityFile via allowlist regexes,
  parse Port via strconv.Atoi, surface ssh_config parse errors instead of
  silently swallowing them. Switch -o kernel_cache to auto_cache for network-
  FS correctness, pin StrictHostKeyChecking=accept-new.
- LOW-severity cleanup: -v verbose flag (default output is just the mount
  path), run_editor returns errors and main exits 7 on failure, ABOUTME
  headers, golang.org/x/sys v0.43.0 (go 1.25.0).
- Defense-in-depth + UX: rxIdentityFile first-character anchor rejects
  leading "-"/"."/":"/etc., verify_mount_dir resolves base via EvalSymlinks
  and refuses pre-existing symlinks at the mount path, flag.Usage shows the
  positional <Host> argument, run_editor uses cmd.Start() so cold-start
  Sublime does not block the terminal.
- CI: empty-PKGVER guard in update-pkgver workflow.
- Tests: verify_mount_dir path-traversal + symlink-reject coverage,
  rxHostUser/rxIdentityFile boundary cases.
2026-04-26 11:24:45 +02:00

79 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Decisions
## 2026-04-26 Audit remediation: cache flag, host-key policy, argv hardening
- **Who**: Dom, ClaudeCode
- **Why**: Audit found `kernel_cache` causes stale reads on a network FS;
`StrictHostKeyChecking` was implicit (depended on system default);
`ssh_config`-sourced strings flowed into the sshfs argv without validation,
allowing comma- or leading-dash injection if `~/.ssh/config` is attacker-influenced.
- **Tradeoffs**:
- `auto_cache` invalidates the page cache on `open(2)` when mtime/size
differ → marginal perf hit vs. correctness on a network FS.
- `accept-new` for `StrictHostKeyChecking` preserves first-connection UX
(TOFU) but locks subsequent reconnects to the recorded key.
- Allowlist regexes for HostName/User/IdentityFile are restrictive but match
real-world SSH naming. Rejected inputs surface a clear error.
- **How**:
- Replaced `-o kernel_cache` with `-o auto_cache` in `mount_sshfs`.
- Added explicit `-o StrictHostKeyChecking=accept-new`.
- Added `lookup_ssh_field` (uses `GetStrict`, surfaces parse errors) and
`validate_ssh_field` with per-field allowlists; Port parsed via `strconv.Atoi`.
- Added `main_test.go` covering `verify_mount_dir` and the allowlist regexes.
- Hardened `.gitea/workflows/update-pkgver.yaml` with an empty-PKGVER guard.
## 2026-04-26 LOW-severity audit findings: cleanup pass + one Won't-Fix
- **Who**: Dom, ClaudeCode
- **Why**: Followup on the LOW-severity findings from the same audit.
- **How**:
- Q-L1: dead `if len(port) == 0 { port = "22" }` block already removed by Q-M1
refactor (port now flows through `lookup_ssh_field``strconv.Atoi`).
- Q-L2: `run_editor` returns `error` and `main` exits 7 on editor failure
instead of swallowing it.
- Q-L3: ABOUTME header added to `main.go` per global CLAUDE.md convention.
- P-L1: `golang.org/x/sys` bumped `v0.1.0 → v0.43.0` via `go get … && go mod tidy`.
`go` directive moved from 1.23.4 to 1.25.0 (required by the new dep).
- S-L2: added `-v` flag; default output now prints only the mount path.
`-v` restores the previous full HostName/User/Port/IdentityFile/Mount block.
- **Won't-Fix**:
- **S-L1** — `.gitea/workflows/update-pkgver.yaml` clones over
`http://gitea:3000/...`. This URL only resolves inside the Docker network
between the Gitea-act-runner container and the Gitea container. External
traffic still terminates TLS at the reverse proxy. An attacker on the
internal Docker bridge has already compromised the host; TLS between
containers does not help in that scenario. Documenting as accepted risk.
## 2026-04-26 Second-round audit: defense-in-depth + UX
- **Who**: Dom, ClaudeCode
- **Why**: Re-audit (Quality + Performance + Security in parallel) surfaced five
remaining LOW findings after agent-output gegencheck. Two MEDIUM claims were
filtered out as false positives: missing `Port` already defaults to `"22"` via
`ssh_config.Default()` (verified in library source), and `lookup_ssh_field`
does not parse `~/.ssh/config` four times — `kevinburke/ssh_config` caches via
`sync.Once`, so four `GetStrict` calls = one parse + three in-memory walks.
An additional MEDIUM claim about a regex range bug (`%-` interpreted as range)
was empirically refuted (only `-` matched, not the implied range), but a
legitimate defense-in-depth gap remained: the regex still allowed leading `-`
in IdentityFile values.
- **Tradeoffs**:
- `run_editor` switched to `cmd.Start()` — drops editor exit-status
propagation in exchange for non-blocking UX on Sublime cold-start. Errors
from missing binary or fork failure are still returned.
- `verify_mount_dir` now `MkdirAll`s the base separately before
`EvalSymlinks`. `EvalSymlinks` requires the path to exist; without the
upfront `MkdirAll`, first run on a fresh system would fail.
- `rxIdentityFile` first-character class restricted to `[A-Za-z0-9/~]`. Plain
relative paths like `.ssh/id` are rejected — unusual but technically valid
in `ssh_config`. If a real config uses relative IdentityFile values, the
user gets a clear error and can switch to absolute or `~`-prefixed.
- **How**:
- **S-L1**: anchored `rxIdentityFile` to `^[A-Za-z0-9/~][A-Za-z0-9._@/:+=~%-]*$`.
- **S-L2**: `verify_mount_dir` resolves base via `filepath.EvalSymlinks` and
rejects existing mount paths that are symlinks (`os.Lstat`).
- **Q-L1**: `flag.Usage` override now prints the positional `<Host>` argument
in the help output.
- **Q-L2**: inline comment at the `run_editor` call site documents the
deliberate fall-through on already-mounted hosts.
- **P-L2**: `run_editor` uses `cmd.Start()` instead of `cmd.Run()`.
- Tests extended: rxIdentityFile boundary cases, `TestVerifyMountDirRejectsSymlink`,
existing `TestVerifyMountDir` paths now compared against `EvalSymlinks(base)`.