# Threat model

`kwt` is a local developer tool, not a sandbox for Git repositories. Security
reviews should distinguish data controlled by a branch from execution policy
already controlled by the machine's owner.

## Trusted local state

The following are trusted because changing any of them already gives the local
user or an attacker equivalent code-execution capability outside `kwt`:

- the operating-system account and its environment;
- executables on `PATH`, shell and tmux configuration, and agent commands;
- global `kwt` configuration;
- an existing repository's Git configuration, hooks, remotes, URL rewrites,
  credential helpers, filters, and fsmonitor configuration; and
- authenticated fleet peers belonging to the same user.

Consequently, ordinary Git inspection after a successful checkout—including
status, log, diff, ref discovery, and fleet metadata collection—is within the
accepted execution boundary. If trusted Git configuration deliberately invokes
a helper while performing those operations, `kwt` does not try to provide a
stronger sandbox than Git. Registry staleness that changes when inspection
occurs is a correctness concern, not a security boundary bypass.

## Untrusted inputs

Branch and ref names, pull-request metadata, remote repository contents, and
repository file contents are untrusted data. They must not be interpolated into
shell commands, environment-variable expansion, credentials, remote
destinations, or paths outside the user-selected or configured destination.

Repository-local `.kwt.toml` is separately trust-gated. Even a trusted local
file cannot set machine-level fleet credentials or endpoints. Trusting that
file authorizes its repository-scoped policy; it does not authorize branch
names or remote metadata to become shell syntax.

## Worktree creation and repository automation

Checking out a local or remote branch is an explicit user action and establishes
the same boundary as checking it out with Git directly. `kwt` still makes
creation unsurprising:

- ref and branch values are passed as arguments, never shell source;
- automated checkout disables configured hooks, filters, and recursive
  submodule updates;
- imported existing branches do not automatically run `copy_files`,
  `setup_commands`, layouts, or pane commands; and
- `kwt`-managed tokens are removed from checkout and workspace processes.

After checkout, the worktree participates in normal status, discovery, and
fleet observation. Opening it is the explicit opt-in to create its configured
workspace and run any trusted layout or pane commands.

## Secrets and machine-level policy

`kwt` bearer tokens, token-file locations, and credential-bearing remote URLs
must not be exposed to repository-controlled processes, printed, persisted in
shared state, or published to fleet. Fleet configuration is global-only, and
non-loopback fleet transport requires HTTPS.

Ordinary developer credentials and environment variables outside `kwt`'s own
credential surfaces remain the user's responsibility.

## Local daemon authority

The operating-system account is the local daemon trust boundary. The daemon
runtime directory and bearer record are owner-only because the credential can
authorize future worktree mutations and authenticated SSH use. Clients prove
the recorded endpoint before sending the bearer. The server accepts only its
exact loopback Host, rejects browser Origin requests, bounds bodies and
diagnostics, and never logs credentials or request bodies.

Inventory repository paths are carried only in authenticated request bodies,
not URL paths. The owner-only inventory cache contains derived discovery data;
it is disposable and never authorizes a mutation. Repository-local config is
never implicitly trusted: interactive approval is bound to a content digest
that the daemon revalidates, while rejection and noninteractive skipping apply
only to that request. Authenticated inventory requests also carry the client's
path-expansion context, including its environment. This transient context is
used only to interpret trusted global paths and is neither logged nor persisted.
A remote client reaches inventory by invoking the remote machine's kwt CLI over
its existing shell boundary; the loopback daemon itself is not remotely
reachable.

An operation ID is a routing handle, not an authorization credential. Every
event, response, and cancellation request still requires the owner-only bearer
credential and the daemon's loopback host checks. Prompt responses are accepted
only on the authenticated response endpoint and only for the operation's exact
current prompt ID. They are held only for the active prompt round and are not
rendered, logged, or persisted. Event and response payloads, replay and live
subscriber queues, every live subscription including terminal replay, active
operations, and retained completed operations are bounded so an authenticated
or same-user local client cannot create unbounded daemon memory growth. The
live queue reserves critical-event capacity that noncritical progress cannot
consume. The same operating-system user remains the trust boundary.

The operation stream is reconnectable only while the same verified daemon
retains it in memory. Neither an operation ID nor a request digest authorizes a
new daemon to adopt or replay work. Loss of that authority is reported as an
unknown outcome rather than causing an automatic retry of a mutation.

An SSH lease ID is likewise only a same-daemon routing handle. The daemon keeps
the generation-bound lease and private projection state; clients receive only
the arguments needed to reach that verified master. Lease touch, hold, and
release require the owner-only bearer. A disconnected hold returns the lease to
the thirty-second expiry policy, so a suspended foreground CLI keeps authority
without letting a crashed CLI pin authentication state indefinitely. During
replacement, new leases are refused, existing leases may finish within the
advertised grace period, and remaining lease handles are invalidated before the
SSH owner is closed. Live masters are never transferred to the successor.
Masterless direct arguments are never published through the daemon API. They
remain available only to an in-process Go owner that assumes responsibility for
the direct authentication, prompt, and trust boundary.

Interactive SSH preparation uses the running kwt executable as a forced
OpenSSH askpass helper only when its process environment contains a fresh,
one-time channel handle. Helper dispatch occurs before configuration or Cobra
startup. On POSIX the channel is an owner-private Unix socket; on Windows it is
a named pipe with a protected DACL granting the current user. The opaque handle
also carries a random 256-bit secret, and every request must prove it before a
prompt is forwarded. Prompt and response frames, concurrent connections,
rounds, and deadlines are bounded. Responses cross only this local channel and
stdout back to the waiting OpenSSH process; they are not placed in arguments,
environment values, daemon events, logs, or persistent state. Deliberate empty
responses remain distinct from rejection. Kwt creates no channel state unless
the selected system OpenSSH satisfies the 8.4 forced-askpass floor.
OpenSSH's own askpass confirmation hint distinguishes host-key confirmation
from credential input before kwt parses the confirmation text into a reviewed
host, algorithm, and fingerprint for native clients. Every unhinted prompt
remains sensitive authentication input, even when its text matches OpenSSH's
standard host-key question; server-controlled prose is not trusted provenance.
Confirmation requests outside the complete unknown-host shape also remain
sensitive authentication input, so they reach the client without claiming a
host-key identity or enabling echoed input.
Published prompt details contain only that host-key review identity, the
already reviewed route targets, and hop position; they contain no credentials
or environment values.

SSH route resolution validates user, hostname, and port before invoking
OpenSSH. POSIX resolution quotes the validated argv into the account login
shell and accepts stdout only between unpredictable, exact nonce markers;
startup output and marker-like stderr are not configuration. Windows avoids a
shell boundary and invokes OpenSSH directly. Kwt's built-in credential
environment variables and the configured fleet-token variable are removed
before either process boundary. The daemon reloads the fleet-token variable
name and sanitizes a fresh process environment for every resolution, so a
compatible daemon cannot retain stale credential policy after configuration
changes. Cancellation terminates the complete resolver process tree through a
POSIX process group or a Windows Job Object, so an OpenSSH configuration
command cannot survive daemon drain while retaining its output handles. Stdout
and stderr are each capped at 1 MiB; exceeding either cap cancels that same
owned process tree.

The daemon retains the complete normalized `ssh -G` stream only as private
route-identity input. Authenticated responses expose semantic targets and the
reviewed execution projection, never arbitrary directives. Opaque
ProxyCommand and nested proxy routes are rejected because kwt cannot bind
independent trust policy to every hop. Projections are target-local; route
consumers must use the ordered preceding target's prepared master as proxy
transport rather than executing a downstream projection directly. Stage 1
resolution does not authorize a host key, authenticate, create a master, or
make the returned route identity an authorization credential.

Guarded project unregistration requires the exact persisted path, its
credential-free repository identity, and an opaque fingerprint of the complete
decoded registry entry the client observed. The daemon derives and compares
that token from its own fresh snapshot before identity resolution or endpoint
inspection; it never treats the client token as a description of trusted state.
The raw registration comparison and final compare-and-swap remain mutation
authority. An identity-keyed, owner-private project fence serializes removal
with operations that can create worktrees, provenance, or protected sessions.
Pull-request provenance is owner-private durable authority: kwt writes it
before establishing a protected session and rolls an import back if that write
fails. Manual deletion of this owner-private state is outside the supported
integrity model.

The daemon derives protected socket names from durable session and worktree
identity and probes each endpoint without collapsing operational failures into
absence. New protected named-socket commands discard ambient `TMUX_TMPDIR`, so
a foreground client and the daemon resolve the same owner-specific endpoint.
Released kwt versions inherited that variable; migration-aware operations
check the canonical endpoint first, then the invoking client's explicit legacy
directory without inheriting any other environment state. Probes strip the
configured fleet-token environment name in addition to kwt's canonical
credential variables. A live endpoint blocks metadata removal and
provenance-backed pruning; permission, connection, or
other indeterminate failures fail closed. Public errors expose only the exact
protected session and socket identity needed to resolve the block. Ordinary
tmux-server sessions are outside this authority and are neither probed as
project endpoints nor killed.

Public service descriptors never contain an unexpected error's private cause.
HTTP detail keys are selected by stable code and type-checked before
serialization; arbitrary cause metadata is not forwarded. The owner-private
daemon log records the route, stable code, and a diagnostic capped at 1024
bytes. The bearer, values from credential-named environment variables,
authorization headers, and URL user information are redacted before that
diagnostic is written. Request bodies are never logged.

PID liveness alone does not authorize cleanup or replacement. Kwt also checks
the recorded process creation identity. A dead PID or exact creation mismatch
makes a runtime record stale; an identity that cannot be read, a timed-out
probe, or failed proof preserves the record and blocks a second writer.

## Out of scope

Do not report an issue as a `kwt` vulnerability when exploitation requires:

- a malicious binary already on the user's `PATH`;
- hostile shell, tmux, Git, hook, filter, fsmonitor, or credential-helper
  configuration already installed by the local user;
- another malicious process running as the same operating-system user;
- a compromised authenticated fleet machine; or
- treating a normal Git command after checkout as crossing an untrusted-code
  execution boundary.

These assumptions may still expose usability or correctness bugs. Severity
should follow the concrete consequence within the boundaries above: credential
disclosure, command injection by branch-controlled data, trust bypass, writes
outside configured roots, or pushes to the wrong repository are security
issues; stale UI state, compatibility, and ordinary Git behavior are not.
