Bump hono from 4.11.3 to 4.11.7 in /pinning-webui#14
Closed
dependabot[bot] wants to merge 1 commit into
Closed
Conversation
Bumps [hono](https://github.com/honojs/hono) from 4.11.3 to 4.11.7. - [Release notes](https://github.com/honojs/hono/releases) - [Commits](honojs/hono@v4.11.3...v4.11.7) --- updated-dependencies: - dependency-name: hono dependency-version: 4.11.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Author
|
Superseded by #19. |
ehsan6sha
added a commit
that referenced
this pull request
May 19, 2026
Implements the issuer side of the "seed IS the user" identity model agreed with the maintainer (audit F-A1 / F-A3 redesign, 2026-05-18). Matches the cryptographic primitives shipped in fula-api (functionland/fula-api@7fa2f32) and the specification on functionland/fula-api#14. Mode B / Mode C clients prove possession of the seed by signing a challenge nonce with an Ed25519 keypair the seed deterministically produces. The seed never leaves the client. The webui mints a JWT with `sub = effective_user_id_hex` (the same 16-byte BLAKE3 derivation the client computes locally). The fula-cli gateway treats `sub` opaquely; no gateway change is required. Because Mode B/C `effective_user_id` values are seed-derived 128-bit hashes of high-entropy input, the gateway's published users-index CBOR is non-enumerable for these users — closing F-A3 naturally without a separate dual-publish design. New file `server/services/seedAuth.ts`: - Strict input validators (hex32 regex; exact-length base64 decoders). - Ed25519 verification via Node's built-in `crypto.verify`, with raw 32-byte public keys wrapped in the SPKI prefix Node expects. - Domain-separated `buildSignedTranscript(purpose, uid, challenge)` so a signature issued for `register-mode-b` can't be replayed at `sign-in`, and a signature for one uid can't be replayed for another (Codex advisor 2026-05-18). - In-memory challenge store with 60-second TTL, single-use semantics, and a periodic sweep that runs on a 60-second interval (unref'd so it doesn't keep the process alive). Single-process server today; if ever scaled horizontally, swap for Redis. - `insertOrAssertSeedUser` handles the squatting case: same effective_user_id with a different public_key throws `PublicKeyMismatchError` (mapped to HTTP 409 PUBLIC_KEY_MISMATCH). Same key is idempotent (returns the existing row). New endpoints in `server/app.ts`: - `POST /auth/register-mode-b` — first sign-up (Google/Apple OAuth + seed). Verifies OAuth via existing `google-auth-library` / `apple-signin-auth`; verifies Ed25519 signature; transactional INSERT into `seed_users` + `webui_users`; mints JWT. Response includes `has_mode_a` so the client can warn the user that their new vault is separate from any existing Mode A account on the same OAuth identity (Gemini advisor 2026-05-18). - `POST /auth/register-mode-c` — same shape minus OAuth. The effective_user_id derivation is `BLAKE3(domain || NFKC(seed))[..16]` client-side; two users with identical seeds coincidentally collide — by design (the seed IS the identity; use a high-entropy passphrase). - `POST /auth/challenge` — `{effective_user_id_hex}` → returns a fresh 32-byte random nonce (base64). Single-use, 60-second TTL. Returns 404 USER_NOT_FOUND on unknown id (the id is already public via the gateway's CBOR, so hiding it adds no security). - `POST /auth/sign-in` — verifies the signed challenge using the stored public_key, mints a fresh JWT, touches `last_used_at`. The minted JWT reuses the existing `generateJwtApiKey` helper so the shape is byte-identical to today's API keys (`{sub, iat, scope, jti}`), just with `sub = effective_user_id_hex` (32 hex chars) instead of `SHA-256(email)` (64 hex chars). Both encodings fit in `webui_users.user_id VARCHAR(64)`. New DB table `seed_users`: CREATE TABLE seed_users ( effective_user_id VARCHAR(32) PRIMARY KEY, mode CHAR(1) NOT NULL CHECK (mode IN ('B','C')), public_key BYTEA NOT NULL, oauth_sub VARCHAR(255), provider VARCHAR(16), registered_at TIMESTAMPTZ DEFAULT NOW(), last_used_at TIMESTAMPTZ ) Plus a partial index on `oauth_sub WHERE NOT NULL`. Migration runs inside the existing `initializeDatabase` block alongside the other table migrations. Mode B users get a fresh `webui_users` row keyed by their effective_user_id — explicitly NOT shared with any Mode A account for the same Google/Apple identity. This matches the maintainer's "treat different modes as different users" decision; the consequence is that credit balances / wallets / pins are per-vault, not per-OAuth-identity. The `webui_users` row stores no email or profile information (the OAuth binding lives only in `seed_users.oauth_sub`). Tighter rate limit on the seed-auth endpoints (30 requests per hour per IP) than the generic API limit, to bound registration spam + challenge-store growth. Skipped under `options.skipRateLimit = true` for tests. Tests in `tests/seedAuth.test.ts` (15 cases — vitest + supertest): - happy-path registration → 200, JWT sub matches uid, mode=C. - Idempotent re-registration (same key) → 200. - Squatting (different key, same uid) → 409 PUBLIC_KEY_MISMATCH. - Bad signature → 401 SIGNATURE_INVALID. - Cross-purpose signature replay (register sig at sign-in) → 401. - Malformed effective_user_id_hex (5 variants) → 400 VALIDATION_ERROR. - Bad public_key length → 400 VALIDATION_ERROR. - `/auth/challenge` unknown user → 404 USER_NOT_FOUND. - `/auth/challenge` known user → 200 + 32-byte nonce. - `/auth/sign-in` happy path → 200 + JWT. - `/auth/sign-in` challenge replay → 401 CHALLENGE_INVALID. - `/auth/sign-in` wrong signature → 401 SIGNATURE_INVALID. - `/auth/sign-in` challenge tampering → 401 CHALLENGE_INVALID. - `/auth/sign-in` no prior challenge → 401 CHALLENGE_INVALID. - `/auth/sign-in` cross-user signature replay → 401 SIGNATURE_INVALID. Tests are skipped on machines without PostgreSQL reachable (same pattern as the existing `api.test.ts`). Out of scope for this change (separate follow-ups): - Free-tier abuse mitigation: an attacker with a leaked Google ID token could call `register-mode-b` once per attacker seed and get a fresh Mode B vault per call. Each vault would get its own default credits. Mitigation discussed but deferred — bind free tier to `oauth_sub` rather than `effective_user_id` in creditService. Tracked on functionland/fula-api#14. - Mode-C recovery mnemonic UX (Flutter side). - FxFiles `auth_service.dart` integration calls to these endpoints. Refs: - fula-api commit 7fa2f32 (crypto primitives + FFI) - fula-api issue #14 (full design spec) - Gemini advisor 2026-05-18 (error UX, vault labeling) - Codex advisor 2026-05-18 (transactional insert, SPKI wrapping, domain-separated transcript, strict validation) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ehsan6sha
added a commit
that referenced
this pull request
May 19, 2026
Three security/correctness fixes for the issuer endpoints, closing the open audit items on functionland/fula-api#14. ## Finding #1 (CRITICAL) — Registration replay `POST /auth/register-mode-b` and `/auth/register-mode-c` previously accepted a CLIENT-generated challenge and verified only the Ed25519 signature over it. The challenge was never tracked as single-use, so anyone who captured a valid registration request body could replay it to mint fresh JWTs forever. Severity is amplified by the design choice that JWTs have no `exp` claim (DT-1): each replay produced a perpetually-valid token, killable only by rotating `JWT_SECRET`. Fix: extended the existing `POST /auth/challenge` endpoint to accept a `purpose` parameter (`'sign-in'` | `'register-mode-b'` | `'register-mode-c'`) defaulting to `'sign-in'` for back-compat. The two register endpoints now consume the challenge from the in-memory store the same way `/auth/sign-in` already does: `challengeStore.takeIfValid(uid, 'register-mode-{b,c}')` then `crypto.timingSafeEqual` against the submitted bytes. Replay of a captured body fails at the first step (challenge already consumed) and returns HTTP 401 `CHALLENGE_INVALID`. Sign-in callers still trigger USER_NOT_FOUND when the target uid is unknown; register callers skip that check because they're creating the user. ## Finding #4 (IMPORTANT) — `has_mode_a` was wrong The `register-mode-b` response had a `has_mode_a: boolean` field intended to surface "this OAuth identity already has a Mode A vault that the new Mode B sign-up is SEPARATE from". The previous logic counted `seed_users` rows with the same `oauth_sub` — which detects "another seed-based vault", NOT "a Mode A account". Mode A users live in `webui_users` keyed by `SHA-256(lowercase(email))`, never in `seed_users`. Fix: replaced `checkModeAExistsForOauthSub(oauthSub)` with `checkModeAExistsForEmail(email)` that does a direct PK lookup on `webui_users` using `emailToUserId(verifiedOauthEmail)`. Captures the OAuth-verified email alongside the sub in the existing Google / Apple verification block. Apple's relay-email behavior on subsequent sign-ins (no email returned after first sign-in) is an acceptable false-negative — `has_mode_a = false` is the safe default. The FxFiles client wires the result into a "Existing vault detected" warning dialog on the Mode B sign-up success path so the user knows their new Maximum-security vault is independent of their existing Standard-security one (Gemini advisor 2026-05-18). ## Finding #5 (IMPORTANT) — `encrypted_email = NULL` audit Mode B / Mode C `webui_users` rows used to be inserted with `encrypted_email = NULL`. Codex flagged this as a downstream risk: any future code that reads the column without null-guards could misattribute or crash for these users. A grep across the codebase found only INSERT sites for the column today, but the surface area is non-trivial (admin tools, future SELECT joins, possible backup / audit scripts). Fix per maintainer's direction: store the AES-256-GCM-encrypted `effective_user_id_hex` in the column instead of NULL. "Email is essentially user-id all over the system" — treating the canonical seed-user-id as the email gives downstream code a non-null opaque value to work with. The actual OAuth email of a Mode B user is NOT persisted (the OAuth binding still lives only in `seed_users.oauth_sub`), so this change does NOT introduce new PII exposure. ## Tests `tests/seedAuth.test.ts`: - AUDIT-1 replay: register a uid via the new server-issued-challenge flow, replay the captured body, expect 401 CHALLENGE_INVALID. - AUDIT-5 encrypted_email non-null: register Mode C, query `webui_users.encrypted_email`, assert non-null and non-empty. Existing 15 tests updated to use the new `obtainRegisterChallenge` helper (call `/auth/challenge` with the appropriate purpose before registering) — they previously passed client-generated nonces and would now fail with `CHALLENGE_INVALID`. Total: 17 vitest cases. Tests skip on machines without PostgreSQL — same pattern as the existing `api.test.ts`. Run via `npm test` against a populated DB. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps hono from 4.11.3 to 4.11.7.
Release notes
Sourced from hono's releases.
... (truncated)
Commits
f7d272a4.11.72cf6004Merge commit from forkcf9a78dMerge commit from forkedbf6eeMerge commit from fork12c5117Merge commit from fork73434874.11.6b6e5a97feat(bun): export getBunServer (#4626)2a9cd95fix(sse): handle\rand\r\nline endings in writeSSE (#4644)8078bbfdocs: align CODE_OF_CONDUCT.md wording with Contributor Covenant (#4630)a5be555refactor: useunique symbolfor more accurate typing. (#4651)Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot mergewill merge this PR after your CI passes on it@dependabot squash and mergewill squash and merge this PR after your CI passes on it@dependabot cancel mergewill cancel a previously requested merge and block automerging@dependabot reopenwill reopen this PR if it is closed@dependabot closewill close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)You can disable automated security fix PRs for this repo from the Security Alerts page.