# Defense Patterns: IPC Hardening
> Concrete defenses for inter-process communication: authentication, anti-replay, validation, socket security, and least privilege.

---

## Overview

IPC channels between application processes (UI layer to backend, backend to sidecar, parent to child) must be treated as untrusted by default. A caller identity claim inside a message is meaningless without cryptographic proof. Every defense below addresses a specific attack class documented in `atk-ipc-process.md`.

---

## DEF-IPC-01 — HMAC-Based Message Authentication

**Pattern:** Every IPC message carries a cryptographic MAC computed over `nonce || timestamp || payload` using a shared secret. The receiver recomputes the MAC and compares using a constant-time function before processing any content.

**Key properties:**
- Shared secret generated by CSPRNG at process startup, never stored on disk, never logged
- Secret transmitted to child process via stdin pipe (not CLI arguments or environment variables — both are readable from `/proc/PID/cmdline` and `/proc/PID/environ` on Linux)
- MAC verification uses constant-time comparison (`hmac.verify_slice` in Rust, `hmac.compare_digest` in Python) — never `==`
- Recommended algorithm: HMAC-SHA256 (32-byte output, 256-bit security)

**Applies to:** Any IPC channel where the receiver cannot independently verify caller identity via OS primitives.

**Code review checklist:**
- [ ] MAC covers all fields (nonce, timestamp, AND payload) — partial coverage leaves fields malleable
- [ ] Verification fails CLOSED: an error blocks the operation, never silently passes
- [ ] Secret is zeroized from memory after the IPC session ends

---

## DEF-IPC-02 — Anti-Replay: Nonces and Timestamps

**Pattern:** Each message includes a 256-bit random nonce (CSPRNG-generated) and a millisecond timestamp. The receiver maintains a bounded cache of seen nonces and rejects any message whose nonce was already seen OR whose timestamp is older than the TTL window (recommended: 30 seconds).

**Key properties:**
- Nonce cache is bounded with a hard maximum (e.g., 10,000 entries) to prevent OOM via replay flooding
- When the cache fills, eviction uses LRU order — the oldest nonce leaves first
- Timestamp check uses `saturating_sub` or equivalent to avoid underflow on clock skew
- TTL must be shorter than the expected attack window but longer than legitimate clock drift

**Applies to:** Any authenticated IPC channel; essential when the same channel carries repeated similar commands (e.g., "execute", "read", "write").

**Code review checklist:**
- [ ] Nonce cache capacity is explicitly bounded — no unbounded `HashSet` or `Map`
- [ ] Both nonce uniqueness AND timestamp freshness are checked (either alone is insufficient)
- [ ] A replay returns an error that blocks — never falls through to command execution
- [ ] Cache eviction removes the LRU entry, not the entry being inserted (prevents silent replay acceptance)

---

## DEF-IPC-03 — Serialized Message Validation (Schema Enforcement)

**Pattern:** IPC message types are defined as a closed enumeration, not free-form strings. The deserializer rejects any message with an unknown type tag. Each variant enforces strict field-level validation (type, length, format, character set) before any business logic executes.

**Key properties:**
- No "catch-all" or `Other(String)` variant — a new action requires an explicit code change and review
- Maximum payload size enforced before deserialization (recommended: 64 KiB)
- Each field validated independently: length bounds, regex format, null-byte rejection, path-traversal rejection
- Validation failure returns a typed error that blocks execution

**Applies to:** All IPC message parsing, both from frontend-to-backend and backend-to-sidecar directions.

**Code review checklist:**
- [ ] No free-form string dispatch: `if action == "exec"` patterns indicate untyped dispatch
- [ ] Payload size checked before deserialization to avoid parser DoS
- [ ] All string fields have explicit maximum length
- [ ] Path fields reject `..`, `\0`, and OS-reserved names before any filesystem operation

---

## DEF-IPC-04 — Socket Security (Permissions and Identity Verification)

**Pattern:** Local IPC sockets are created with the most restrictive permissions the OS supports, and the server verifies the connecting process identity before accepting messages.

**Unix Domain Sockets:**
- Created with permission mode `0600` (owner read/write only)
- Socket placed in a user-scoped runtime directory (e.g., `/run/user/<uid>/`) rather than `/tmp`
- After accepting a connection, verify caller UID via `SO_PEERCRED` (Linux) or `LOCAL_PEERCRED` (macOS) — these are kernel-enforced and cannot be spoofed

**Windows Named Pipes:**
- Created with an explicit DACL granting access only to the current user's SID
- `FILE_FLAG_FIRST_PIPE_INSTANCE` flag prevents pipe name squatting by another process
- Impersonation level set to `SecurityIdentification` unless the caller truly needs impersonation

**Abstract namespace sockets (Linux):** Avoid — abstract sockets are not protected by filesystem permissions and are accessible to any process in the same network namespace.

**Code review checklist:**
- [ ] Socket permissions explicitly set — do not rely on process umask
- [ ] Caller identity verified via kernel API, not a user-supplied claim inside the message
- [ ] No use of abstract namespace sockets for sensitive IPC
- [ ] Named Pipe creation includes both explicit DACL and `FILE_FLAG_FIRST_PIPE_INSTANCE`

---

## DEF-IPC-05 — Principle of Least Privilege for IPC Endpoints

**Pattern:** Each IPC endpoint declares exactly the operations it needs. Operations not declared are unavailable by default. Privileges are scoped to the minimum required by each window, role, or caller context.

**Key properties:**
- Deny-by-default: no operation is available unless explicitly granted
- Per-window or per-caller scoping: a UI component that displays data does not need write permissions
- Sensitive permissions (filesystem write, subprocess execution) are separate from general permissions
- Test configurations never reach the production build

**Applies to:** All permission / capability configuration for the IPC bridge.

**Code review checklist:**
- [ ] Every granted permission has a documented business justification
- [ ] Wildcard scopes (`**`, `*`) are absent from production configurations
- [ ] Sensitive capabilities are not granted to wildcard windows or callers
- [ ] CI pipeline flags dangerous permission patterns (wildcards, broad filesystem, shell execution)
- [ ] Test capability files are excluded from production builds via `.gitignore` or build tooling

---

## DEF-IPC-06 — Input Validation at IPC Boundary

**Pattern:** Every field received over IPC is validated at the entry point, before any further processing. Validation is fail-closed: an invalid field blocks the entire message.

**Validation rules by field type:**

| Field type | Required checks |
|---|---|
| File path | Length, null bytes, `..` traversal, OS reserved names, canonical resolution within allowed base |
| URL | Length, scheme allowlist (HTTPS only), hostname not private/loopback/link-local, no embedded credentials |
| Free text / keyword | Length, character set allowlist regex, no leading `-` (option injection) |
| Integer / ID | Range bounds, positive-only where applicable |
| Enum / action type | Closed set — reject unknown variants |
| List / array | Maximum element count |

**Code review checklist:**
- [ ] Every IPC handler starts with validation before any side-effect
- [ ] Validation is centralized — not duplicated inline in each handler
- [ ] Regex patterns are compiled once at startup (not per-request)
- [ ] URL validation blocks numeric-encoded IPs (hex, decimal-integer, octal)

---

## DEF-IPC-07 — Rate Limiting per Caller

**Pattern:** The IPC endpoint tracks the number of requests per caller (window, process, IP, or session ID) within a sliding time window. Callers exceeding the limit are rejected until the window resets.

**Key properties:**
- The rate-limit table is bounded by a maximum number of tracked callers to prevent OOM
- When the table is full, expired entries are evicted first; if still full, new callers are rejected
- Rate limit is applied per authenticated caller, not globally (global limits are too easy to DoS via one heavy caller)
- Rejection returns a typed error — not a silent pass

**Applies to:** IPC endpoints that trigger expensive operations (subprocess launch, filesystem scan, network request).

**Code review checklist:**
- [ ] Rate-limit counter map has an explicit `max_callers` bound
- [ ] Eviction removes expired entries before rejecting new callers
- [ ] Rate limit is enforced before the expensive operation, not after
- [ ] Counter reset logic handles clock monotonicity correctly (use monotonic clock, not wall clock)

---

## DEF-IPC-08 — Secure Secret Bootstrap (Startup Handshake)

**Pattern:** A shared secret needed for IPC authentication is generated by the parent process, transmitted to the child exactly once via a stdin pipe at startup, and then zeroized from memory in both processes after the handshake completes.

**Why stdin pipe instead of alternatives:**
- CLI arguments: readable by all same-UID processes via `/proc/PID/cmdline` on Linux, `ps` on macOS/Linux
- Environment variables: persist for the full process lifetime in `/proc/PID/environ`
- Stdin pipe: content is consumed once and not visible to other processes

**Key properties:**
- The child process reads exactly one bootstrap line and zeroizes the buffer immediately after parsing
- Core dumps are disabled before secret parsing (`RLIMIT_CORE = 0`)
- If bootstrap fails (missing, malformed, wrong format), the child exits immediately — never continues with a missing secret

**Code review checklist:**
- [ ] Secret is never passed via CLI arguments or environment variables
- [ ] Bootstrap buffer is explicitly zeroized after parsing — not left to garbage collection
- [ ] Child exits on any bootstrap error — no fallback to an unauthenticated mode
- [ ] Parent zeroizes its copy of the secret immediately after writing to the pipe

