---
name: co-fita-pam-setup
type: reference
title: "PAM macOS Setup -- Touch ID for sudo"
description: "Automated setup for Touch ID authentication on macOS sudo: integrity-verified compilation of pam-reattach, idempotent PAM deployment, and Homebrew distribution via private tap."
tags: [macos, pam, touch-id, security, authentication, homebrew, pam-reattach, automation]
timestamp: "2026-07-21"
---

# PAM macOS Setup -- Touch ID for `sudo`

A hardened, automated setup for enabling Touch ID authentication on macOS `sudo` commands. The tool compiles `pam-reattach` from source with cryptographic integrity verification, deploys it idempotently to `/etc/pam.d/sudo_local`, and distributes the result via a private Homebrew tap for easy deployment across machines.

The central problem: macOS's native `pam_tid.so` works for Terminal.app but fails inside VS Code, Cursor, tmux, and other non-native sessions. `pam-reattach` re-attaches to the Touch ID session, making biometric sudo work everywhere.

See also: [[co-fita-infrastructure]] | [[co-fita-research-operations]]

---

## 1. The Problem

macOS ships with `pam_tid.so` for Touch ID sudo authentication. However, this module only works in Terminal.app sessions that have direct access to the Biometric framework. In practice, this means:

- **VS Code / Cursor integrated terminals** -- no Touch ID
- **tmux sessions** -- no Touch ID
- **SSH sessions with agent forwarding** -- no Touch ID
- **Any non-native terminal emulator** -- no Touch ID

The `pam-reattach` library (by fabianishere) solves this by re-attaching to the macOS Biometric session from any PAM context. The challenge is deploying it correctly: the library must be compiled for the host architecture, placed in the right path, and configured in `/etc/pam.d/` without breaking the system.

---

## 2. Architecture

The setup has three layers:

```
setup.sh          ← Interactive entry point (AppleScript UI/UX)
    │
    ▼
pam_setup.py      ← Autonomous installer (PEP 723, uv run)
    │
    ├── check_integrity()    ← SHA256 verification against official release
    ├── build_pam_reattach() ← Universal binary compilation (arm64 + x86_64)
    └── deploy_and_configure() ← Idempotent PAM deployment + cleanup
```

### `setup.sh` -- Interactive Entry Point

A bash script that provides native macOS UI via `osascript` dialogs:

1. **Introduction dialog** -- explains what the tool does, warns about admin privileges
2. **Dependency check** -- verifies `uv` is installed; offers to install it via `astral.sh` if missing
3. **Privilege escalation** -- detects Touch ID availability via `bioutil -r`; either prompts for biometric auth or falls back to an AppleScript password dialog
4. **Delegates to `pam_setup.py`** -- runs the actual installer via `sudo env "PATH=$PATH" pam_setup.py`
5. **Success dialog** -- confirms Touch ID is now active

The script respects user choice: if the user prefers to run directly in Terminal (avoiding GUI prompts), they can use `sudo ./setup.sh` instead.

### `pam_setup.py` -- Autonomous Installer

A PEP 723 Python script (run via `uv run --script`) that handles the full installation lifecycle.

**Phase 1 -- Integrity Verification:**

```python
EXPECTED_VERSION = "1.3"
EXPECTED_SHA256 = "b1b735fa7832350a23457f7d36feb6ec939e5e1de987b456b6c28f5738216570"
TARBALL_URL = f"https://github.com/fabianishere/pam_reattach/archive/refs/tags/v{EXPECTED_VERSION}.tar.gz"
```

The script downloads the official release tarball, computes its SHA256, validates against the hash published by the Homebrew Registry, then performs a file-by-file comparison against the locally cloned source in `repos/pam_reattach/`. This catches both upstream tampering and local modification.

**Phase 2 -- Compilation:**

```python
clang_cmd = [
    "clang",
    "-arch", "arm64", "-arch", "x86_64",
    "-shared", "-fPIC", "-undefined", "dynamic_lookup",
    f"-I{include_path}",
    "-o", final_out,
    src_pam, src_reattach, "-lpam"
]
```

Compiles a universal binary (`arm64` + `x86_64`) using Apple Clang. No CMake required -- direct Clang invocation for simplicity and reproducibility.

**Phase 3 -- Deployment:**

The deployment is idempotent (safe to run multiple times):

1. Creates `/usr/local/lib/pam/` if it doesn't exist
2. Copies `pam_reattach.so` to `/usr/local/lib/pam/pam_reattach.so`
3. Creates `/etc/pam.d/sudo_local` with the correct PAM configuration:

```
# sudo_local: auth account password session
auth       optional       /usr/local/lib/pam/pam_reattach.so
auth       sufficient     pam_tid.so
```

4. Sets ownership to `root:wheel` with permissions `0644`
5. **Autocleanup:** Audits `/etc/pam.d/sudo` and removes any duplicate `pam_tid.so` lines left by previous manual configurations

**Why `sudo_local` instead of modifying `sudo` directly:**

macOS's System Integrity Protection (SIP) protects `/etc/pam.d/sudo`. The `sudo_local` file is the Apple-sanctioned extension point -- it is read after `sudo` and survives macOS updates. This is the correct deployment target.

---

## 3. PAM Configuration Details

### The two lines that matter

```
auth  optional   /usr/local/lib/pam/pam_reattach.so
auth  sufficient pam_tid.so
```

**Line 1 (`pam_reattach.so`, optional):**
- Re-attaches to the macOS Biometric session
- `optional` means failure doesn't block authentication -- falls back to password
- This is the key enabler for non-native terminals

**Line 2 (`pam_tid.so`, sufficient):**
- The native macOS Touch ID module
- `sufficient` means if Touch ID succeeds, authentication is complete (no password needed)
- If Touch ID fails (e.g., on a Mac without Touch ID), PAM continues to the next module

### The cleanup step

The installer also cleans `/etc/pam.d/sudo` of any `pam_tid.so` lines that were added by previous manual setups. This prevents conflicts where Touch ID would be checked twice or in the wrong order.

---

## 4. Security Properties

| Property | Implementation |
|---|---|
| Source integrity | SHA256 verification against official release tarball + file-by-file comparison |
| No network dependencies at runtime | All downloads happen during installation; the compiled library is self-contained |
| Idempotent deployment | Running the script multiple times produces the same result |
| Autocleanup | Removes duplicate PAM entries from previous configurations |
| Privilege boundary | `pam_setup.py` only runs with `sudo`; password is handled via `sudo -v` (biometric) or `sudo -S` (cached), never stored |
| No password persistence | The osascript password prompt is a one-time fallback; the password is piped directly to `sudo -S` and immediately discarded |
| Universal binary | Compiled for both `arm64` (Apple Silicon) and `x86_64` (Intel) |

### Threat model

This tool modifies PAM configuration, which is a sensitive system component. The threat model assumes:
- The user running the script is the machine owner
- The official `pam-reattach` release v1.3 is trustworthy (verified via hash)
- The Homebrew Registry hash is authoritative (pinned in the script)

---

## 5. Homebrew Distribution

The `pam-mac-setup` tool is distributed via a private Homebrew tap at `aleffita/homebrew-private`.

### The Formula

```ruby
class PamMacSetup < Formula
  desc "macOS Sudo Touch ID Configuration Script"
  homepage "https://github.com/aleffita/pam-mac-setup"
  url "https://github.com/aleffita/pam-mac-setup.git", using: :git, tag: "v1.0.0"
  version "1.0.0"
  license "MIT"

  def install
    libexec.install "setup.sh", "pam_setup.py"
    (bin/"pam-mac-setup").write <<~EOS
      #!/bin/bash
      exec "#{libexec}/setup.sh" "$@"
    EOS
  end
end
```

### Installation on new machines

**Option 1 -- PAT token (HTTPS):**
```bash
brew tap aleffita/private https://github_pat_...@github.com/aleffita/homebrew-private.git
brew install pam-mac-setup
pam-mac-setup
```

**Option 2 -- SSH (cleaner):**
```bash
brew tap aleffita/private git@github.com:aleffita/homebrew-private.git
brew install pam-mac-setup
pam-mac-setup
```

The formula is minimal: it installs `setup.sh` and `pam_setup.py` into `libexec/` and creates a `pam-mac-setup` wrapper in the Homebrew `bin/`. The wrapper delegates to `setup.sh`, which handles the interactive flow.

---

## 6. Requirements

- macOS Sonoma or later
- Python 3.11+ (managed via `uv`)
- `uv` package manager (auto-installed if missing)
- Apple Command Line Tools (for Clang compilation)
- Touch ID hardware (for biometric auth; password fallback available)

---

## 7. Repository Structure

```
pam-mac-setup/
├── README.md         # Documentation (bilingual PT/EN)
├── setup.sh          # Interactive entry point (AppleScript UI)
├── pam_setup.py      # Autonomous installer (PEP 723)
└── repos/
    └── pam_reattach/ # Source code (v1.3, verified, no .git metadata)
```

The `repos/` directory contains the `pam-reattach` source without git metadata. This is intentional: the integrity verification compares the local source against the official tarball, and `.git` directories would cause false mismatches.

---

## 8. Connection to ClickFix

The ClickFix attack used a **fake PAM dialog** (Stage 2) to steal the user's password. The dialog used `osascript display dialog ... with hidden answer` to impersonate a "System Preferences" prompt. This is a social engineering attack that bypasses the real PAM entirely.

The PAM setup tool addresses a different problem (enabling Touch ID for sudo in non-native terminals), but the connection is meaningful: the more commonly Touch ID is used for sudo, the less frequently users type passwords into terminal prompts, and the fewer opportunities attackers have to capture passwords via fake dialogs.

A user who exclusively uses Touch ID for sudo never types their password into a terminal -- making the ClickFix Stage 2 password theft impossible even if the malware reaches that stage.

---

## 9. Design Decisions

1. **PEP 723 for the Python script** -- `uv run --script` means zero setup; the user just needs `uv` installed. Dependencies are declared inline in the script header.

2. **AppleScript for UI** -- native macOS dialogs feel trustworthy and integrate with the system. The password prompt uses `with hidden answer` (the same primitive the malware abused, but for a legitimate purpose here).

3. **Integrity verification before compilation** -- the script downloads the official tarball and compares it against the local source before compiling. This catches supply chain attacks at the source level.

4. **Universal binary** -- compiling for both `arm64` and `x86_64` means the same deployment works on both Apple Silicon and Intel Macs.

5. **`sudo_local` over `sudo`** -- Apple designed `sudo_local` as the extension point. Modifying `sudo` directly would be overwritten by macOS updates and could break SIP.

6. **Autocleanup of old configurations** -- users who previously added `pam_tid.so` to `/etc/pam.d/sudo` manually would have conflicts. The installer detects and removes these automatically.
