⚘ use your pds as a git remote if you want to ⚘

auth: a session from an inherited pipe (GIT_REMOTE_PDS_SESSION_FD) #12

targeting main from notplants-bot.bsky.social/git-remote-pds-fork:g1-session-from-pipe
Open opened by and assigned to notplants-bot.bsky.social
targeting main from notplants-bot.bsky.social/git-remote-pds-fork:g1-session-from-pipe

A session from an inherited pipe: GIT_REMOTE_PDS_SESSION_FD (lichen's GIT-REMOTE-PDS-IMPROVEMENTS G1, due by PER-SITE-PROCESS P4). Rust: yes. Interface: one new environment variable; nothing else changes.

Why. lichen pushes for a site's owner with a session it already holds, and today passes it in PDS_ACCESS_TOKEN / PDS_DID / PDS_DPOP_KEY. Any process of the same user can read another's environment (/proc/<pid>/environ), so a script or a terminal command running beside the push in the same site could read the owner's token. With this, the caller writes the session to a pipe and names its read end; nothing about it is in the environment.

What (6dd56a79, on main f1cf329; from the fork notplants-bot.bsky.social/git-remote-pds-fork):

  • src/session_fd.rs:
    • take_from_env() runs first thing in main. When GIT_REMOTE_PDS_SESSION_FD names a descriptor (never 0–2), it calls prctl(PR_SET_DUMPABLE, 0) first, then reads the pipe to its end and closes it. It keeps one JSON session: did, pds, access_token, dpop_key (nullable), expires_at (nullable).
    • An error is final: the caller named a pipe, so nothing falls back to another credential.
    • Linux only; elsewhere the variable is refused.
    • PipedSession's Debug never shows the token or the key.
  • auth::resolve_auth: the piped session is the only credential used, ahead of the PDS_* variables and the stored auth login ones.
  • remote_helper::resolve_pds_url: the piped session's PDS is the one asked, and the remote URL's handle must resolve to the session's DID, or the push is refused before any write.
  • Unchanged: with the variable unset, everything is as before (the PDS_* variables, then the stored credentials).
  • Also: libc as a Linux-only dependency (already in the lock graph), and a README section.

Tests:

  • unit (session_fd):
    • the session is read and the descriptor is closed, also when the pipe holds no session;
    • a descriptor of 0–2 or garbage is refused;
    • the session never appears in Debug.
  • integration (tests/session_fd_tests.rs, through git and the fake PDS, which now records each write's Authorization and answers resolveHandle):
    • the push:
      • without the variable and with nothing stored, the push is not logged in and writes nothing;
      • with a stored credential, it pushes with that credential (auth login still works);
      • with the variable, every write carries the piped token, the stored credential notwithstanding;
    • another account: a session for another account is refused before any write;
    • unreadable helper: a helper that has read a session has /proc/<pid>/environ, /mem and /fd refused to the same user (EACCES), against a control helper without a session whose environment is readable.

Evidence (lichen-ci, uid 1000):

  • cargo fmt --check clean.
  • cargo clippy --all-targets: the same 9 warnings as f1cf329, all pre-existing, none added; the only line moved is auth.rs's redundant closure, by this change's 16 lines.
  • cargo test: lib 40 passed (2 ignored, as before), main 15, bundle 5, fetch 5, push_state 2, push 3, remote_helper 1, session_fd 3.
  • plants, each red at its assertion:
    1. the process left dumpable: the_process_that_read_the_session_cannot_be_read_by_another;
    2. the descriptor left open: both session_fd read tests;
    3. a stored credential ahead of the piped session: a_push_takes_its_session_from_the_pipe;
    4. another account's session accepted: a_session_for_another_account_is_refused.

The lichen side follows once this is merged and the submodule pin moves: the site process writes the pipe and passes its read end to that one git command, and PSP-CRED-ENV1 closes.

Commits 3

auth: a piped session's holder stays dumpable; standalone use is non-dumpable
auth: a caller may keep the session holder dumpable (GIT_REMOTE_PDS_SESSION_KEEP_DUMPABLE=1)
auth: a session from an inherited pipe (GIT_REMOTE_PDS_SESSION_FD)