Security model
Empirical treats requests, repository content, specs, decisions, evidence, receipts, Git metadata, policies, CLI/MCP inputs, and remote observations as untrusted.
- Strict schemas reject unknown fields at protocol boundaries. Feature, capability, command, branch, and artifact identifiers use portable allowlists; repository paths cannot be absolute or traverse upward.
- Atomic writers preserve modes and refuse managed symbolic-link paths. Ownership-aware locks cannot remove a newer caller's lock.
- Policy commands run as exact argument arrays without a shell. Output, timeout, and working directory are bounded. Shell launchers and control syntax are rejected.
- Emergency release authorization is disabled by default, explicit rather than inferred, bounded to one administrator/incident/PR/merge/version/integrity and 30-minute receipt, gated by the protected npm environment, and denied for the historical v0.29.0 failure. It grants no force, deletion, replacement, token, merge, or manual publication authority.
- Evidence consists of immutable, canonical-digest receipts. Executed receipts retain command/result/source provenance; collected receipts fingerprint repository-contained artifacts. QA receipts additionally retain matrix, workflow/Git revision, platform, duration, cleanliness, artifacts, every attempt, and anomalies. Failed retries, skips, unsupported or missing environments, cancellation, and timeouts cannot be rewritten as green. Caller assertions are not evidence.
- Capability ownership is shared through the Git common directory. Integration verifies base digests, detects claim conflicts, overlays committed feature differences from the merge base plus bounded working changes, rejects target divergence, validates in an independent worktree, rolls back candidate projections, and never force-writes Git.
- Worktree and agent-handoff proposals are read-only and integrity-bound. Creation or host execution requires literal approval of an unchanged exact path/branch/argv proposal.
- Manifest v2 inventory is bounded and excludes ignored, build, dependency, secret-like, binary, and oversized paths. It stores fingerprints, not a remote semantic index. Stale generated pages are not silently retrieved.
- Tracker Policy v1/v2 stores only provider target IDs, normalized status IDs,
behavior/visibility choices, and credential environment-variable names
matching
^(?=.{2,64}$)[A-Z][A-Z0-9]*_[A-Z0-9_]+$. Runtime authentication is selected outside policy in strict order: a trusted host OAuth resolver, a complete injected environment set, then a permission-checked host secrets file. OAuth registration, callbacks, refresh, revocation, and encrypted token custody remain host responsibilities. Resolver failures are replaced with stable diagnostics, returned credential shapes are strictly validated, and ephemeral values are added to transport redaction without serialization. - Tracker Policy v2 enforcement is local, secret-free authority. Best-effort is compatible; opt-in strict gates only deterministically required work. A blocked gate is derived from validated local binding, revision, policy digest, and effect acknowledgements—not provider prose—and core mutation APIs enforce it independently of agent prompts. Missing authentication exposes only safe variable names/host-file recovery. Exact terminal feature sync and bounded new-start preflight prevent a crash from hiding an unresolved final state.
- An OAuth handoff is a secret-free HTTPS URL, bounded opaque elicitation ID,
provider, and short message. MCP sends it only when the connected client
explicitly declares
elicitation.url. Form-only, legacy-empty, absent, or failed elicitation support receives no request and falls back out of band. Form schemas, tool arguments/results, assistant text, and chat are never credential channels. - Standalone Linear OAuth pins
https://mcp.linear.app/mcp, uses SDK DCR/PKCE, binds an ephemeral callback to127.0.0.1, validates exact path and random state, bounds the returned code and wait, and closes pending clients/listeners on success, decline, cancellation, error, or timeout. Registration, verifier, code, and tokens remain in memory and never reuse another client's token store. - The fallback file is
${XDG_CONFIG_HOME:-$HOME/.config}/empirical/secrets.envon POSIX or%APPDATA%\Empirical\secrets.envon Windows. Empirical never creates it or mutatesprocess.env. The reader rejects final symbolic links, non-regular files, repository-contained paths (including resolved aliases), files over 64 KiB, malformed or duplicate assignments, partial provider identities, and group/world POSIX permission bits. Explicit test environments do not trigger implicit reads of a developer's home file. - GitHub and Linear OAuth tokens use Bearer authorization at their fixed API
endpoints; Linear personal API-key fallback retains its required raw
Authorizationvalue. Jira OAuth requires a validated Cloud ID and Bearer authorization athttps://api.atlassian.com/ex/jira/{cloudId}; Jira email/API-token fallback retains Basic authorization against the configured tenant origin. Provider requests use fixed HTTPS boundaries, bounded timeouts and responses, complete bounded pagination, checksummed target-bound feature state, stable create markers, and deterministic per-effect keys. Discovery catalogs are ephemeral and preview validates target access before persistence. Off/disabled branches occur before authentication resolution. A rule-backed optional ticket with no explicit reference also returns local-only before OAuth, environment/file lookup, or provider transport; this is covered with throwing doubles. - Tracker evidence is selected only through receipt IDs already committed in local workflow state. Receipt and file digests, repository containment, regular-file/non-symlink identity, secret-like names, media allowlists, count, and byte ceilings are revalidated before provider access. Uploads use deterministic reconciliation names; durable repository links are commit pinned and emitted only when committed bytes match. No artifact bytes enter pending state. Diagnostics are bounded and credential-redacted before return or persistence, including failures raised by an injected transport. Remote input is never allowed to mutate local workflow state or acceptance criteria.
- Reserved migration stage/marker/backup paths are transaction state rather than source. Pre-marker failure removes only its owned stage; evidence, knowledge, and integration overlays exclude scratch, while Doctor diagnoses orphans without deleting them.
- Global uninstall is confirmation-gated and derives every candidate from the pinned catalog under the validated user home. It removes only regular files carrying Empirical's managed marker and valid owner-stamped metadata, never follows symlinks, never searches repositories, and invokes exact npm package removal only after managed integration cleanup succeeds.
- Global installation exposes only the narrowly scoped
empirical-initskill. Project activation validates completed Schema 5 configuration, ignores read-only prompts, never initializes implicitly, and changes instruction or skill files only through contained marker-owned writes that preserve unmatched markers and unmanaged collisions. Managed instruction blocks are kept first without changing user-owned bytes, and inspection is bounded to the selected checkout, invocation ancestry, known settings, and injected or host user-skill roots. It never follows symlinks or claims that a running host reloaded repaired content. - Doctor never repairs, deletes, prunes, launches, or writes.
- Review configuration persists only an uppercase reviewer-token environment
name. The value is resolved at the exact call boundary, translated to
GH_TOKENonly inside the reviewer child process, removed from recorded environment keys, and redacted exactly from captured output. One readiness check requires an authenticated repository-readable non-author login before bot review mutation. Fresh-context mode uses no second credential and makes no claim of forge-level independence. - Delivery opens owned source and evidence PRs as drafts, reviews their exact
remote base/head diffs, and refuses draft or stale-head merge. It accepts
GitHub aggregate approval or effective latest non-author approval, preserving
valid bot approval when aggregate
reviewDecisionis empty. It uses ordinary merges and declared checks, with no admin, protection-bypass, force-push, credential-discovery, or hidden cleanup path. - Generic Empirical publication requires an exact explicit version, commit,
tag, dist-tag, literal approval, and authorization bound to the complete
request. The repository release pipeline recognizes only an exact,
required-check-passing, same-repository
developto protectedmainmerge with no effectiveCHANGES_REQUESTEDreview as its equivalent narrow authorization. It paginates the complete review history and fails closed on incomplete or malformed review evidence; ordinary pushes, forks, changed SHAs, other branch pairs, YOLO/delivery authority, and unbound dispatches fail before mutation. - The release PR gate has read-only contents and no secrets/OIDC. The merged-PR
orchestrator repeats exact PR, two-parent commit, product/package/pack,
changelog/migration, clean CI, and remote checks. Its GitHub job has
contents: writewithout OIDC. npm candidate/CI preflight and post-publish provenance verification are separate read-only jobs without OIDC. The intervening GitHub-hosted publish job has only read-only contents plusid-token: write, the protected environmentnpm, an ignore-scripts install, and one tarball whose version/SRI must match the authorization output before the literal npm publish lifecycle. Every checkout disables persisted credentials, release dependency caching is off, and no npm write token is accepted. npm trusted publishing binds the exact workflow filename/environment and supplies automatic public-package provenance. - Git tag, GitHub Release, npm version/integrity/provenance, and
latestare queried before and after effects, including after a failed/lost response. Only ordered identical prefixes can resume. Existing conflicts, gaps, incomplete observations, missing provenance, or a divergent dist-tag block without deletion, edit, force, replacement, rollback, or a token-based repair path. Reports contain bounded identities/status only, never provider tokens or unbounded bodies. - YOLO changes question frequency, not authority. It never bypasses host permissions or branch protection, extracts credentials, infers publication, replaces immutable artifacts, or deletes real worktrees/branches.
- Decision files reject hidden-reasoning, prompt-transcript, credential, and secret sections. Explain exposes deterministic state-machine rationale only.
Do not place secrets in requests, chat, Socratic answers, specifications,
decisions, evidence summaries, screenshots, tracker configuration, tool input,
tool output, commands, shell history, process arguments, or delivery inputs.
Never paste credentials into chat. Tracker credential fields contain
environment-variable names, never values. New defaults are
LINEAR_SECRET_KEY, GITHUB_TOKEN, and the Jira pair JIRA_EMAIL plus
JIRA_API_TOKEN; review setup names EMPIRICAL_REVIEWER_TOKEN. Historical
or custom valid names remain supported.
.empirical/ is committed project data; Git-common-dir claim records are local
coordination metadata.