Skip to content

test: pin /api/gate fail-closed, the beacon allowlist, and verify mismatch - #115

Draft
cursor[bot] wants to merge 7 commits into
productionfrom
cursor/missing-test-coverage-e5d1
Draft

test: pin /api/gate fail-closed, the beacon allowlist, and verify mismatch#115
cursor[bot] wants to merge 7 commits into
productionfrom
cursor/missing-test-coverage-e5d1

Conversation

@cursor

@cursor cursor Bot commented Sep 5, 2026

Copy link
Copy Markdown

Coverage for production paths that shipped without tests, and that #112/#114 do not already pin.

Risky behavior now covered

  • Gate store (POST /api/gate): the /gate page still posts answers here one question at a time. Unparseable or out-of-range bodies never reach the Rust service. An unreachable or rejecting gate is 503 before the INSERT, and the client error never carries answer text or the gate's body. A well-formed answer is posted to /api/store without X-Gate-Key. The fresh_gate_responses INSERT still binds NULL for both plaintext answer columns.
  • Client metrics beacon (POST /api/metrics/increment): only the allowlisted names move a counter. A server-only funnel name (funnel.gate.q1_answered) is a silent no-op. Allowed and disallowed responses are the same 200 { ok: true } with Cache-Control: no-store, so the list cannot be walked by status code. Membership is exact, not a prefix.
  • Magic-link verify (GET /auth/verify): missing tokens, a garbage JWT, and a resume token that does not hash to the JWT session_id all fail before the database and set no cookie. Failure pages do not echo the email, the JWT, or the resume token.
  • Wearable invitation (GET /w/:token): traversal-shaped, short, long, and punctuation tokens 404 before any query and set no cookie, so the page is not an existence oracle.
  • Database URL parse: localhost disables TLS even when sslmode=require is in the URL; a remote sslmode=require enables it; URL-encoded password characters are decoded; an unparseable URL is not a config.
  • Gate startup log (redact_db_url): userinfo is stripped, including a password that itself contains @. A schemeless string is <redacted>, not echoed.

Test files added/updated

  • routes/api/gate_test.ts (new)
  • routes/api/metrics/increment_test.ts (new)
  • routes/auth/verify_test.ts (new)
  • routes/w/token_test.ts (new)
  • lib/db_config_test.ts (new)
  • lib/db.ts_resolveConfigForTest hook only
  • rust-server/src/main.rs — unit tests for redact_db_url
  • .github/workflows/ci.yml — these Deno suites, plus cargo test for the gate

Why these tests materially reduce regression risk

/api/gate is the other door answers enter through. #113 collapsed the questionnaire onto lib/gate_encrypt.ts and tested that client plus /api/questions/answer; this endpoint was still untested, so a plaintext INSERT or a 200-on-gate-down path would ship again. The beacon allowlist is public: a prefix match or a leaked 4xx would let anyone invent conversions the daily report treats as truth. Verify mismatch before the database is what stops a stolen JWT fragment from minting cookies. Wearable format 404 is what keeps a printed token from becoming an oracle or a cookie injection. redact_db_url is the line that logs DATABASE_URL on every gate start — the password used to go to journald.

Hermetic: fetch is stubbed, DATABASE_URL is unparseable, no SMTP, no Postgres. No production behavior changes.

Open in Web View Automation 

cursoragent and others added 3 commits September 3, 2026 20:34
…WT that must not carry an address

The delivery confirmation route, the key-box callback, and questionnaire
JWTs shipped without tests. Pin fail-closed auth, write-once pdf_delivered_at,
GPC/DNT opt-out, and session tokens that verify as hashes not mailboxes.

Read KEYBOX_RENDER_* and RENDER_CALLBACK_* on each call (same boot-order
trap jwt.ts already documents) so the tests can set them, and so a missing
.env at import cannot lock the consent path into "unavailable" forever.

Co-authored-by: Ralphie B. <nyagrodha@users.noreply.github.com>
… nowhere

These suites already existed or just landed; the check job never invoked
them. Add --allow-write for keystore temp dirs. Leave tests/ out: it still
carries the pre-existing type errors that fail before any test executes.

Co-authored-by: Ralphie B. <nyagrodha@users.noreply.github.com>
…dentity

These contracts lived under tests/ (which CI cannot type-check) or skipped
whenever typst/qpdf were absent. Colocate them next to the production code
and run them on the check job so a hostile session id, a forged
X-Forwarded-For, or a guessed "yes" cannot regress silently.

Co-authored-by: Ralphie B. <nyagrodha@users.noreply.github.com>
@gitguardian

gitguardian Bot commented Sep 5, 2026

Copy link
Copy Markdown

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

…match

The /gate page still posts answers through /api/gate, which had no tests.
These pin fail-closed encryption, the public metrics allowlist, magic-link
token mismatch before the database, wearable token format, DATABASE_URL
parsing, and rust-server connection-string redaction.

Co-authored-by: Ralphie B. <nyagrodha@users.noreply.github.com>
cursoragent and others added 3 commits September 7, 2026 12:31
Keep production's lib/gate_encrypt_test.ts on the check job alongside the
delivery, auth, and audience suites this branch already added. The two
edits collided on the same deno test path list after #113 landed.

Co-authored-by: rbauer <rbauer@colorado.edu>
Union the CI test path lists so this branch (consent, session-key
injection, contact rate-limit) no longer conflicts with the delivery/JWT
suites from #112 or with production's gate_encrypt_test.ts from #113.

Co-authored-by: rbauer <rbauer@colorado.edu>
Union CI path lists so /api/gate fail-closed, the beacon allowlist, and
verify-mismatch suites sit next to the stacked delivery/consent tests.
Keep this branch's rust-server cargo test job.

Co-authored-by: rbauer <rbauer@colorado.edu>
cursor Bot pushed a commit that referenced this pull request Sep 7, 2026
Keep both rust-server test suites: store bounds / recipient fail-closed
from this branch, and DATABASE_URL redaction from #115. Union the CI
Deno path list so profile, metrics, and gate page tests sit next to the
stacked coverage.

Co-authored-by: rbauer <rbauer@colorado.edu>
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