feat(mercury): egress_profile indirection for static-IP relay routing#249
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
chittyconnect | 83ccb47 | Jun 16 2026, 05:12 PM |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Review limit reached
More reviews will be available in 24 minutes and 41 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Mercury per-token IP allowlisting requires all calls leave from one static IP. This adds a configurable egress profile so Mercury proxy calls can route through a relay (Access-locked, reserved-IP app-connector) without further code changes once the egress node exists. - `direct` (default): unchanged — hit api.mercury.com directly with Bearer auth, byte-for-byte the legacy request shape (preserves the working aribia path). - `relay`: POST to MERCURY_EGRESS_URL with the Mercury token as X-Mercury-Token plus CF-Access-Client-Id/Secret service-token headers. The relay receives a relative `path` only (host allowlist preserved structurally). Fails closed if relay is selected but MERCURY_EGRESS_URL is unset (no silent fallback). Profile is global (MERCURY_EGRESS_PROFILE) with optional per-slug override (MERCURY_EGRESS_PROFILE_<SLUG>), mirroring getMercuryToken's convention. Keys stay in Secrets (Option A) and transit as headers; never stored on the relay. Pure buildEgressRequest/resolveEgressProfile extracted for mock-free unit tests (14 tests). Relay HTTP round-trip deferred to integration test once the egress node is provisioned (noted in test). wrangler.jsonc: MERCURY_EGRESS_PROFILE/URL as non-secret vars (default direct/empty); MERCURY_EGRESS_ACCESS_CLIENT_ID/SECRET as Secrets Store bindings mirroring the MERCURY_OIDC_* block — across dev/staging/prod. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
a401e1b to
036d89f
Compare
There was a problem hiding this comment.
Pull request overview
Adds an egress-profile indirection to the Mercury proxy so Mercury API calls can be routed either directly (current behavior) or via a future static-IP relay without further code changes, plus configuration scaffolding and unit tests.
Changes:
- Adds
resolveEgressProfile+buildEgressRequestand threads the resolved egress profile through Mercury route handlers. - Introduces a relay request envelope (
{ method, path, body }) and Cloudflare Access service-token headers for relay authentication. - Adds unit tests for profile selection and request construction; updates
wrangler.jsoncvars/secrets bindings for the new egress config.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/api/routes/thirdparty.js |
Adds egress profile resolution + request construction and wires Mercury handlers to use it. |
tests/api/thirdparty-mercury-egress.test.js |
Adds unit tests covering egress profile selection and direct/relay request shapes. |
wrangler.jsonc |
Adds egress vars defaults and Secrets Store bindings for relay Access credentials across envs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
….jsonc
The initial egress wiring inserted MERCURY_EGRESS_ACCESS_CLIENT_ID/SECRET and
MERCURY_EGRESS_PROFILE/URL twice per env block, which wrangler rejects
("assigned to multiple Secrets Store Secret bindings"). Each var/binding now
appears exactly once per dev/staging/production. Verified with
`CHITTYCONNECT_SAFE_DEPLOY=1 wrangler deploy --dry-run --env production` —
config parses and all four egress bindings resolve.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…review) Address two code-review findings on the egress_profile indirection: 1. Path-injection guard (relay mode): the relay re-hosts the forwarded `path` onto api.mercury.com, so an absolute URL, protocol-relative `//host`, embedded scheme, or `..` traversal could redirect the relay off Mercury and break the host-allowlist guarantee. buildEgressRequest now fails closed unless `path` is a relative API path beginning with a single `/`. Query strings remain legal. 2. Case-insensitive profile matching: resolveEgressProfile normalizes MERCURY_EGRESS_PROFILE / per-slug overrides with trim+lowercase, so `RELAY`/`Relay` map to `relay` instead of silently falling back to direct. Unknown values now throw early (fail closed) rather than masking misconfig. The requireMercuryToken middleware catches the throw and returns a diagnosable 503. Adds real unit tests for both: absolute/protocol-relative/traversal/ non-string path rejection (and that legit query-string paths still pass), case-insensitive profile resolution, and unknown-profile rejection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Note on |
What
Introduces an
egress_profileindirection for the Mercury banking proxy so calls can route through a static-IP relay without further code changes once the egress node exists. Mercury per-token IP allowlisting is whychicago/it-can-beget 401 from Cloudflare's shared egress; the fix is to send Mercury calls out through one static IP. This makes ChittyConnect ready for that relay — it does not build the egress node (blocked on key-management authority).How
mercuryFetchnow honors an active egress profile resolved per-request:direct(default) — unchanged. Hitsapi.mercury.comdirectly withAuthorization: Bearer, byte-for-byte the legacy request shape. Preserves today's workingaribiapath.relay— POSTs toMERCURY_EGRESS_URLwith the Mercury token asX-Mercury-TokenplusCF-Access-Client-Id/CF-Access-Client-Secretservice-token headers so the relay is Access-locked. The relay envelope carries{ method, path, body }with a relative path only —api.mercury.comhost-allowlisting is preserved structurally (caller can never redirect to another host). Token stays in Secrets (Option A), transits as a header, never stored on the relay.relayis selected butMERCURY_EGRESS_URLis unset,buildEgressRequestthrows. No silent fallback to direct (that would mask misconfig and leak the shared egress IP).Profile selection: global
MERCURY_EGRESS_PROFILE(defaultdirect) with optional per-slug overrideMERCURY_EGRESS_PROFILE_<SLUG>, mirroringgetMercuryToken's existing env convention. No DB table — env suffices for 7 entities, structured so amercury_connection { entity_id, egress_profile_id, allowed_hosts[], account_ids[] }record maps cleanly later.Files
src/api/routes/thirdparty.js—resolveEgressProfile+ purebuildEgressRequestextracted;mercuryFetchtakes anegressarg;requireMercuryTokenstashesmercuryEgress; 4 call sites updated (accounts, account, transactions, refresh).tests/api/thirdparty-mercury-egress.test.js— 14 mock-free unit tests (novi.mock): profile selection, per-slug override, header construction, fail-closed, direct-shape preservation, token-not-in-body. Relay HTTP round-trip deferred to an integration test once the egress node is provisioned (noted in-file).wrangler.jsonc—MERCURY_EGRESS_PROFILE/MERCURY_EGRESS_URLas non-secretvars(defaultdirect/empty) across dev/staging/prod;MERCURY_EGRESS_ACCESS_CLIENT_ID/_SECRETas Secrets Store bindings mirroring theMERCURY_OIDC_*block.Validation
npx vitest run tests/api/thirdparty-mercury-egress.test.js→ 14 passedthirdparty-neon+thirdparty-notion-aliases→ 6 passed (no regression)eslint src/api/routes/thirdparty.js→ cleanwrangler.jsonc→ valid JSONCThe ONE config change to activate (once the egress node exists)
Set, per the credential hierarchy:
vars:MERCURY_EGRESS_PROFILE=relay,MERCURY_EGRESS_URL=https://<egress-node>/mercuryMERCURY_EGRESS_ACCESS_CLIENT_ID,MERCURY_EGRESS_ACCESS_CLIENT_SECRET(Cloudflare Access service token for the relay)No code change.
directremains the default until then.Not in scope (blocked / out of lane)
Egress node provisioning and credential sweep/rotation — both require authority this workstream does not hold.
🤖 Generated with Claude Code