Skip to content

Pin the identity key directory to 0700 and create marker files 0600 #231

Description

@beardthelion

Summary

The node identity key file is 0600, but the directory that holds it (and all the marker/claim/quarantine files the crash-recovery protocol writes there) is created at the process umask with no mode pinning and no verification. On a directory that is group- or world-writable, every defense in the identity-key recovery path degrades, because those defenses assume a node-owned directory and nothing establishes that assumption. openssh pins ~/.ssh to 0700 for exactly this reason.

Both parts below are hardening gaps on crates/gitlawb-node/src/lib.rs, not live exploits on a default single-user host. They predate the round-seven recovery work (#194); that work is what made the directory contents (markers, claims) security-relevant, which is why they surface now.

Part 1: pin the key directory to 0700

std::fs::create_dir_all(parent) in the generate arm (lib.rs ~3120) creates the key directory with no mode. Observed after real provisioning: nested=0775 keys=0775 key=0600 (umask 0002; under 022 the dirs are 0755). No code path creates the dir with a mode, chmods it, or verifies its mode on load, so a pre-existing group/other-writable directory is silently accepted too.

Blast radius by directory mode:

  • Non-writable to the attacker (0755/0775): name disclosure only. Another local user can list .publishing. / .recovering. / .quarantined. / .superseded. names, leaking pids and crash/recovery timing. The 0600 final stays unreadable. Low severity.
  • Writable by the attacker (shared-group, or umask 0000, or a pre-existing 0777 dir): directory-write permits delete/rename of the 0600 final regardless of its file mode. An attacker can delete the key, or replace it with garbage plus a planted .publishing. marker so the next boot sees the crash signature (content-class failure + marker), quarantines, and silently regenerates a new node identity; or plant perpetually-refreshed .recovering. claims to wedge every start. The content-class and no-marker gates were built against crash residue, not a hostile directory writer.

Fix: create the directory with DirBuilder::mode(0o700) on Unix, and on the load path verify (and tighten, or fail closed) the directory mode the same way load_existing_key already does for the key file.

Part 2: create marker-class files 0600

The publish marker is opened write(true).create_new(true) with no mode (lib.rs ~2137), so it lands at umask (observed mode=0664), unlike the temp file and fallback final which both pin 0o600. Claims are renames of markers and inherit that umask mode; quarantine and superseded files are renames of the final and correctly inherit 0600 (verified: rename preserves source mode both directions).

No key material leaks today (markers and claims are empty or, since the heartbeat's set_modified fallback appends a byte, hold . bytes; every consumer matches by name only). Two modest exposures remain: a future change that writes anything sensitive into a marker or claim would ship it at umask mode and rename would propagate it silently; and under a shared-group umask (0664) a group member has write access to a live claim without directory-write, enough to keep refreshing its mtime and defeat the age-gated sweep, stalling demoted publishers and boot recovery. Under the common 022 umask (0644) only the future-content risk remains.

Fix: add OpenOptionsExt::mode(0o600) to the marker create site, matching the temp/final pattern already used a few lines away.

Verification

Confirmed by execution against head 32903c4 (probes reverted): directory modes stat'd after real provisioning; the live marker mode stat'd mid-publish via the hard-link-fallback path; rename mode-preservation confirmed both directions; the claim non-empty-after-append behavior confirmed with a failing primary stamp. The hostile-dir-writer attack chains are reasoned from directory semantics plus the verified gate code, not built as a multi-user repro.

This extends the atomic-secret-file-creation hardening from #194: that covered the key file's creation mode, but not the containing directory's mode, nor the non-key files written beside it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    crate:nodegitlawb-node — the serving node and REST APIkind:securityVulnerability fix or hardeningsev:highMajor break or real security/trust risk, no easy workaroundsubsystem:identityDID/UCAN, http-sig auth, push authorization

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions