Skip to content

Epic 4: stop claiming things that aren't true - #369

Merged
bordumb merged 4 commits into
mainfrom
epic4/stop-claiming-untrue-things
Jul 17, 2026
Merged

bordumb merged 4 commits into
mainfrom
epic4/stop-claiming-untrue-things

Conversation

@bordumb

@bordumb bordumb commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Four fixes where the product asserted something the code didn't do. Ordered by how much they cost a new user.

4.1 — The default registry was a 404

DEFAULT_REGISTRY_URL = "https://registry.auths.dev" returns HTTP 404 (Vercel: DEPLOYMENT_NOT_FOUND). Verified again today. It was the default for init --registry, publish, account, org join, id claim|register, artifact publish and namespace — and auths doctor probed it on every run, so every user saw a failing "Registry connectivity" check for a service they neither had nor needed.

That's the worst failure shape available: it looks configured and dies at the network.

Nothing needs it. Identity, signing and verification are local and git-native, and since Epic 1 a signer's KEL reaches a verifier over the same git remote as the code. "No central server" is the README's own headline; a default contradicting it is worse than no default.

Registry use is now explicit, refused through one shared require_registry so the message is stated once, in terms of what the user can do:

no registry configured. Auths needs no registry: identity, signing and verification are local and git-native… Pass --registry <url> or set AUTHS_REGISTRY_URL only if you are running one.

New RegistrationError::NoRegistryConfigured (AUTHS-E5400). Doctor reports "not configured (optional)" and passes.

Per your decision on the roadmap's Open Question 3. Checked first that no sibling repo depends on the constant — auths-github-app carries its own copy of the string, so it's unaffected.

4.2 — The green badge was gated behind an unrelated dead flag

Linking GitHub uploads the SSH signing key, and that is the only thing that makes a user's commits render Verified to everyone else. It sat behind interactive && cmd.register — a flag whose documented job is publishing to the (404) registry. So the default first run left commits Unverified while the code to fix it was one boolean away.

The research puts a number on the cost: 73.73% of commit-signature verification failures in the wild are unknown_key — developers sign and never register the key. The authors' verdict: "a usability problem, not a cryptographic one" (arXiv:2604.14014). Auths mints the key, signs with it, and emits a standard SSHSIG GitHub can verify — the thing that is structurally impossible for gitsign, whose CA isn't in GitHub's trust root (gitsign#40, open since 2022).

New --github / --no-github, defaulting on when the repo has a GitHub remote and there's a TTY. Remote detection matches on the parsed host, never a substring — github.com.evil.example is a different host, and there's a test that says so.

4.3 — README: wrong curve, and a claim that wouldn't survive contact

  • "A did:keri derived from your Ed25519 key" — the default is P-256 (the iOS Secure Enclave holds P-256, not Ed25519, so hardware-backed mobile pairing requires it). auths demo emits did:key:zDna….
  • Replaced the implied offline-verification differentiator with the claim that survives: cosign has shipped --offline since 2023-02-23, v3 made offline bundles the default, v4 deprecates --rekor-url. What's actually true is narrower — their offline path checks a trust-root snapshot, so a revocation you haven't downloaded is one you can't see (GitHub's own docs say exactly that). A KEL carries its own rotation history. That's a trust-freshness claim about long-lived device keys — Auths's model, and precisely not Sigstore's.

Not changed: the README lists 10 commands where --help-all shows 45. My roadmap called that drift; it isn't — it's a curated quick-reference that points at --help-all on the next line. My report was unfair there.

4.4 — A scripted init rewrote your global git config

prompt_for_git_scope returned Global unconditionally when non-interactive, with no flag to override. So auths init --non-interactive — the CI and agent path — silently rewrote ~/.gitconfig and repointed core.hooksPath machine-wide, disabling every repo's own .git/hooks (husky, pre-commit, prek) as a side effect.

This repo is living with that right now: its prek pre-commit and pre-commit pre-push hooks are being bypassed by exactly this.

New --git-scope local|global|skip, defaulting to local when non-interactive. Verified:

invocation global gitconfig repo-local hooksPath
--non-interactive (default) untouched set
--git-scope local untouched set
--git-scope global written —
--git-scope skip untouched —

The follow-on the tests caught (bbc5b889)

Scoping init to the repo exposed that the doctor read git config --global in two places, so a correctly configured repo reported as broken. Both now read the effective value — which is what git itself resolves, and was already wrong for anyone who'd scoped signing by hand.

Two integration tests encoded the old behaviour and were corrected, not silenced: test_init_happy_path now asserts a scripted init leaves ~/.gitconfig alone; test_doctor_detects_missing_gpg_format unset --global, a scope init no longer writes, so it would have no-op'd and passed for the wrong reason.

Verification

  • cargo test -p auths-cli --test integration → 67 passed
  • cargo test -p auths-cli --lib → 157 passed
  • cargo test -p auths-sdk --lib → 176 passed
  • cargo clippy -p auths-sdk -p auths-cli --all-targets → clean · cargo fmt --all → applied

On commit granularity

You asked for one commit per task. 4.1, 4.2 and 4.4 are interleaved in the same struct and the same five test fixtures in init/mod.rs — splitting them produces commits that don't compile. I combined those three and kept 4.3 and the diagnostics follow-on separate, rather than fake atomicity.

bordumb added 3 commits July 17, 2026 12:52
Two fixes to claims the code does not make.

- "A `did:keri` derived from your **Ed25519** key" — the default is P-256, and
  has been since the Secure Enclave work: iOS's enclave holds P-256 and not
  Ed25519, so hardware-backed mobile pairing requires it. `auths demo` emits
  `did:key:zDna…` (P-256 multicodec); `z6Mk…` would be Ed25519.

- Replaces the implied offline-verification differentiator with the claim that
  survives scrutiny. Cosign has shipped `--offline` since 2023-02-23 and v3 made
  offline-capable bundles the default; v4 deprecates `--rekor-url` outright.
  GitHub documents offline attestation verification first-party. "We verify
  offline" invites a correction in the first technical conversation.

  What is actually true and narrower: their offline path checks against a
  trust-root *snapshot*, so a rotation or revocation you have not downloaded is
  one you cannot see — GitHub's own docs say "You will not know if key material
  has been revoked since you last generated the trusted root file", and
  sigstore-python#1175 proposes erroring after ~a week offline. A KEL carries its
  own rotation history. That is a trust-freshness claim about long-lived device
  keys, which is precisely Auths's model and precisely not Sigstore's.

Also documents the trust/KEL distribution that Epic 1 made real.

Not changed: the README lists 10 commands where `--help-all` shows 45. That is a
curated quick-reference that already points at `--help-all` on the next line —
deliberate, not drift.
Three related fixes to `auths init` and every registry-touching verb. They are
one commit because they are interleaved in the same struct and the same test
fixtures — splitting them yields commits that do not compile, which is worse than
an honest combined one.

1. No default registry (4.1).
   `DEFAULT_REGISTRY_URL` was `https://registry.auths.dev`, which returns HTTP 404
   (Vercel: DEPLOYMENT_NOT_FOUND) — the worst failure shape there is: it looks
   configured and dies at the network. It was the default for `init --registry`,
   `publish`, `account`, `org join`, `id claim|register`, `artifact publish` and
   `namespace`, and `auths doctor` probed it on every run, so every user saw a
   failing "Registry connectivity" check for a service they neither had nor needed.

   Nothing needs it: identity, signing and verification are local and git-native,
   and since Epic 1 a signer's KEL reaches a verifier over the same git remote as
   the code. "No central server" is the README's own headline; a default that
   contradicts it is worse than no default. Registry use is now explicit
   (`--registry` / `AUTHS_REGISTRY_URL`), refused through one shared
   `require_registry` so the message is stated once, in terms of what the user can
   do: "Auths needs no registry … pass --registry only if you are running one."
   New `RegistrationError::NoRegistryConfigured` (AUTHS-E5400). Doctor now reports
   "not configured (optional)" and passes.

2. GitHub linking is no longer gated on `--register` (4.2).
   Linking uploads the SSH signing key, and that is the only thing that makes a
   user's commits render Verified to everyone else. It sat behind
   `interactive && cmd.register` — a flag whose documented job is publishing to
   the (dead) registry. So the default first run left commits Unverified while the
   code to fix it was one boolean away.

   Research puts a number on the cost: 73.73% of commit-signature verification
   failures in the wild are `unknown_key` — developers sign and never register the
   key; "a usability problem, not a cryptographic one" (arXiv:2604.14014). Auths
   mints the key, signs with it, and emits a standard SSHSIG that GitHub *can*
   verify — the thing that is structurally impossible for gitsign, whose CA is not
   in GitHub's trust root. New `--github` / `--no-github`, defaulting on when the
   repo has a GitHub remote and there is a TTY. Remote detection matches on the
   parsed host, never a substring: `github.com.evil.example` is a different host.

3. A scripted init no longer rewrites your global git config (4.4).
   `prompt_for_git_scope` returned `Global` unconditionally when non-interactive,
   with no flag to override — so `auths init --non-interactive`, the CI and agent
   path, silently rewrote `~/.gitconfig` and repointed `core.hooksPath`
   machine-wide, disabling every repo's own `.git/hooks` (husky, pre-commit, prek)
   as a side effect. New `--git-scope local|global|skip`, defaulting to `local`
   when non-interactive.

Verified: `--non-interactive` leaves ~/.gitconfig byte-identical (md5), while
`--git-scope global` still writes it and `skip` touches nothing.
Follow-on from scoping `auths init` to the repo by default: the doctor read
`git config --global`, so a correctly configured repo reported as broken. Two
reads were wrong in the same way — `system_diagnostic::get_git_config` (the "Git
signing config" check) and `check_commit_hook_installed`'s `core.hooksPath`.

Both now read the effective value. That is not merely "what makes the tests pass"
— it is the value git itself resolves, which is the only thing a diagnostic about
git's behaviour should assert on. A global-only read was already wrong for anyone
who had scoped signing to one repo by hand.

The integration tests caught this, and two of them encoded the old behaviour
rather than the intent:

- `test_init_happy_path` asserted the *global* gitconfig gains ssh signing. It now
  asserts the opposite — a scripted init must leave ~/.gitconfig alone — and that
  the repo-local config gains it instead.
- `test_doctor_detects_missing_gpg_format` unset `gpg.format` from --global, a
  scope init no longer writes. It would have silently no-op'd and passed for the
  wrong reason; it now unsets --local.

400 tests green across auths-cli (67 integration + 157 lib) and auths-sdk (176).
@vercel

vercel Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
auths Ready Ready Preview, Comment Jul 17, 2026 12:43pm

@github-actions

Copy link
Copy Markdown

Auths Commit Verification

Commit Status Details
bbc5b889 ❌ Failed Commit carries no Auths-Id/Auths-Device trailer. The prepare-commit-msg hook installed by auths init adds these on every commit — if this repo sets its own core.hooksPath (e.g. husky), the hook is bypassed; run auths doctor to check. Backfill existing commits with auths sign <ref> (rewrites the commit).
3c21e0c1 ❌ Failed Commit carries no Auths-Id/Auths-Device trailer. The prepare-commit-msg hook installed by auths init adds these on every commit — if this repo sets its own core.hooksPath (e.g. husky), the hook is bypassed; run auths doctor to check. Backfill existing commits with auths sign <ref> (rewrites the commit).
b64dd507 ❌ Failed Commit carries no Auths-Id/Auths-Device trailer. The prepare-commit-msg hook installed by auths init adds these on every commit — if this repo sets its own core.hooksPath (e.g. husky), the hook is bypassed; run auths doctor to check. Backfill existing commits with auths sign <ref> (rewrites the commit).

Result: ❌ 0/3 commits verified


How to fix

Commit bbc5b889 has no Auths signature (no Auths-Id/Auths-Device trailer).

1. Install auths

macOS: brew install auths
Linux: Download from releases

2. One-time setup (creates your identity and configures Git)

auths init

3. Sign this branch and push

auths sign origin/main..HEAD
git push --force-with-lease

For CI to verify the signer, commit an identity bundle:

auths id export-bundle --alias main --output .auths/ci-bundle.json --max-age-secs 31536000

Quickstart →

The golden-path smoke test caught a real design flaw in the previous commit, not
a test artifact: defaulting `--non-interactive` to `local` unconditionally breaks
`auths init` run outside a git repository, because `git config --local` there is
an error. `docs/smoketests/end_to_end.py:241` runs init from the home directory
and creates the repo afterwards — which is not a contrived case but an ordinary
first run. It failed rc=1 and took nine downstream assertions with it.

The default is now local **when standing in a repository**, global otherwise.
That is what makes the conservative default safe rather than merely strict: the
surprise being fixed is "I ran a scripted init inside my repo and it silently
reconfigured my whole machine". A user who is not in a repository has nothing to
scope to, and is not being surprised by machine-wide config — it is the only
scope that means anything there. `--git-scope` still decides outright.

Also regenerates the error docs for AUTHS-E5400 (the `gen-error-docs --check`
gate was correctly red) and reflows its message to a single line: the generator
extracts the raw source string, so `\` line-continuations rendered literally in
docs/errors/AUTHS-E5400.md.

The unit test for the default was dropped in favour of asserting explicit
`--git-scope` in all three directions — the default is now CWD-dependent, so
testing it in a unit test would assert on wherever cargo happened to run. The
integration suite controls the working directory and covers it properly.

Verified: golden-path smoke 60/60 (was 50/60); inside a repo the global gitconfig
is byte-identical while the repo-local config gains signing; outside a repo init
succeeds and configures global.
@github-actions

Copy link
Copy Markdown

Auths Commit Verification

Commit Status Details
0e068583 ❌ Failed Commit carries no Auths-Id/Auths-Device trailer. The prepare-commit-msg hook installed by auths init adds these on every commit — if this repo sets its own core.hooksPath (e.g. husky), the hook is bypassed; run auths doctor to check. Backfill existing commits with auths sign <ref> (rewrites the commit).
bbc5b889 ❌ Failed Commit carries no Auths-Id/Auths-Device trailer. The prepare-commit-msg hook installed by auths init adds these on every commit — if this repo sets its own core.hooksPath (e.g. husky), the hook is bypassed; run auths doctor to check. Backfill existing commits with auths sign <ref> (rewrites the commit).
3c21e0c1 ❌ Failed Commit carries no Auths-Id/Auths-Device trailer. The prepare-commit-msg hook installed by auths init adds these on every commit — if this repo sets its own core.hooksPath (e.g. husky), the hook is bypassed; run auths doctor to check. Backfill existing commits with auths sign <ref> (rewrites the commit).
b64dd507 ❌ Failed Commit carries no Auths-Id/Auths-Device trailer. The prepare-commit-msg hook installed by auths init adds these on every commit — if this repo sets its own core.hooksPath (e.g. husky), the hook is bypassed; run auths doctor to check. Backfill existing commits with auths sign <ref> (rewrites the commit).

Result: ❌ 0/4 commits verified


How to fix

Commit 0e068583 has no Auths signature (no Auths-Id/Auths-Device trailer).

1. Install auths

macOS: brew install auths
Linux: Download from releases

2. One-time setup (creates your identity and configures Git)

auths init

3. Sign this branch and push

auths sign origin/main..HEAD
git push --force-with-lease

For CI to verify the signer, commit an identity bundle:

auths id export-bundle --alias main --output .auths/ci-bundle.json --max-age-secs 31536000

Quickstart →

@bordumb
bordumb merged commit 8644e7f into main Jul 17, 2026
22 of 25 checks passed
@bordumb
bordumb deleted the epic4/stop-claiming-untrue-things branch July 17, 2026 12:53
bordumb added a commit that referenced this pull request Jul 17, 2026
)

* fix(cli): keep global the default scope; guard the hooksPath instead

I over-corrected in 3c21e0c, and two suites said so before I listened. Reverting
the default and fixing the thing that was actually harmful.

`auths init`'s own description is "Create your signing identity and configure
Git", and its *interactive* default is already "All repositories". Making
--non-interactive default to something else made the scripted path disagree with
the prompted one, and broke two real flows: the golden-path smoke test inits from
a home directory (where `git config --local` is an error), and the E2E suite inits
in one repo and commits in another (so repo-scoped signing left commits unsigned —
"No signature found", 3 tests, which I merged in #369 before noticing).

Setting up signing for one repo and silently not the next is its own surprise. The
default is global again.

What was genuinely wrong in W4 was never the default — it was that the scope was
**unchooseable**: `--non-interactive` hard-returned Global with no override, so a
scripted, CI or agent init had no way to keep its hands off ~/.gitconfig.
`--git-scope local|global|skip` closes that, and stays.

And the actual harm — clobbering `core.hooksPath` — is now guarded. It replaces
.git/hooks entirely, so pointing it at ours when husky/pre-commit/prek already own
it silently disables every hook they installed; the user experiences that as "my
pre-commit checks stopped running" with nothing tying it to `auths init`.
`enable_commit_trailers` refuses, naming the occupying path and the two ways out.
Needed a `get` on GitConfigProvider — a port that could only write was how this
went unnoticed.

Tests: the two I "fixed" in bbc5b88 to match the over-correction are restored,
plus new coverage for the capability that is actually new (`--git-scope local`
leaves ~/.gitconfig byte-identical; `skip` touches nothing but still pins the
root), and two SDK tests for the clobber guard including its message being
actionable rather than merely a refusal.

Verified: golden-path smoke 60/60 · e2e git-signing + key-rotation 9/9 · cli
integration 69 · cli lib 157 · sdk lib 180 · clippy 0 errors.

* fix(cli): drop the helper the reverted default left dead

`in_git_repository` and its import became unused when the git-scope default went
back to global. CI builds with `-D warnings`, so dead code is an error there and
merely a warning locally — which is why this passed my `cargo build` and failed
Clippy, MSRV, smoke and KERI conformance all at once.

This branch was successfully deployed

1 active deployment
Preview — 0e068583 Deployed Jul 17, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant