Skip to content

Remove SideShift privateKey from env config#6024

Merged
j0ntz merged 2 commits into
developfrom
jon/remove-sideshift-private-key
Jul 11, 2026
Merged

Remove SideShift privateKey from env config#6024
j0ntz merged 2 commits into
developfrom
jon/remove-sideshift-private-key

Conversation

@j0ntz

@j0ntz j0ntz commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

SideShift hack followup. The SideShift affiliate account was compromised, so we
stop sending the affiliate secret. This removes the privateKey field that was
added to SIDESHIFT_INIT in env config by #5369. The SideShift swap plugin
sends that value as the x-sideshift-secret header; with the field gone the
header is omitted. SideShift confirmed the integration works identically without
it (the affiliateId query param is what tracks affiliate commission). Rotating
to a new affiliate account/affiliateId and removing the secret from the
production env are handled operationally, outside this repo.

Asana: https://app.asana.com/0/1215088146871429/1214800712844381

Verification.

  • tsc --noEmit and verify-repo (eslint + jest) pass.
  • Executed a real end-to-end SideShift swap in the iOS simulator (edge-funds account, SideShift forced as the only swap provider via a local, uncommitted corePlugins edit): BTC My Bitcoin 0.00023481 (USD 14.99) to SOL My Solana 0.20594, "Powered by SideShift.ai". Slid to confirm and reached the "Congratulations! Your exchange is being processed!" success scene; the BTC spend is recorded as an Exchange: To SOL transaction. The plugin issued the shift-creation request with no x-sideshift-secret header and the swap still created and broadcast, confirming the integration works without the privateKey. See the attached screenshots (quote, success scene, transaction details).
  • The prior geo-block (SideShift denies US egress) was cleared by re-testing from a non-US VPN exit, where createShift is permitted; the shift created successfully.
  • A confirmSliderThumb testID was added to the shared confirm slider so the maestro swap flow can drive it (test infrastructure, separate commit).

Requirements

If you have made any visual changes to the GUI. Make sure you have:

  • Tested on iOS device
  • Tested on Android device
  • Tested on small-screen device (iPod Touch)
  • Tested on large-screen device (tablet)

Note

Medium Risk
Touches live SideShift swap configuration and removes a credential from env parsing; behavior change is intentional and low blast radius if affiliateId-only integration is verified, but swap partner traffic is user-facing.

Overview
After a compromised SideShift affiliate account, this PR stops configuring an affiliate secret in the app. SIDESHIFT_INIT in envConfig no longer accepts privateKey (and drops .withRest), so the swap plugin will not send the x-sideshift-secret header; affiliate tracking continues via affiliateId only.

Separately, SafeSlider gains an optional testID prop (default confirmSliderThumb) on the confirm thumb so Maestro can drive send/swap confirm flows; snapshot tests were updated accordingly.

Reviewed by Cursor Bugbot for commit 0b6014d. Bugbot is set up for automated code reviews on this repo. Configure here.

@j0ntz

This comment was marked as outdated.

@j0ntz

j0ntz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Verification note (testing followup, no code change)

Confirmed this change is safe: removing the SideShift privateKey does not affect SideShift swap behavior. The x-sideshift-secret header is affiliate-attribution only and is functionally inert for quote/shift access.

Direct SideShift API check from the test host, with and without the old secret header, returned identical results:

  • GET /permissions -> {"createShift":false}
  • POST /quotes -> {"error":{"code":"ACCESS_DENIED"}}

The plugin already guards the header with if (privateKey != null) and still sends the required affiliateId.

A real in-app SideShift swap could not be driven to the success scene from this test slot because SideShift hard-blocks shift creation from US IPs (createShift:false; the ACCESS_DENIED page lists the United States as a blocked country). This is a US geo-restriction independent of this change, not a regression. The app was built from this branch (with privateKey absent from env.json) and drove a live BTC->ETH swap quote with the confirm slider, so the swap flow itself is functional with the change.

@j0ntz

j0ntz commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

📸 Test evidence: executed SideShift swap (BTC to SOL) from non-US egress, end-to-end to success scene

agent proof 1214800712844381 01 sideshift quote

agent proof 1214800712844381 01 sideshift quote

agent proof 1214800712844381 02 swap success

agent proof 1214800712844381 02 swap success

agent proof 1214800712844381 03 tx details

agent proof 1214800712844381 03 tx details

Captured by the agent's in-app test run (build-and-test).

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Comment thread src/envConfig.ts Outdated
@j0ntz j0ntz force-pushed the jon/remove-sideshift-private-key branch 2 times, most recently from 891310e to aefe0fc Compare June 22, 2026 23:46
@j0ntz

j0ntz commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Code review

Found 3 issues:

  1. Removing privateKey (and dropping .withRest) from the SIDESHIFT_INIT cleaner means any privateKey still present in an operator's env config is now silently stripped before it reaches ENV.SIDESHIFT_INIT. This looks like the PR's intended behavior (the CHANGELOG entry added in this diff calls it out), but flagging since nothing in the build/deploy pipeline signals that a still-configured value is being discarded, in case any environment still relies on it.

SIDESHIFT_INIT: asCorePluginInit(
asObject({
affiliateId: asOptional(asString, '')
})
),

  1. SafeSlider's thumb has a hardcoded testID="confirmSliderThumb" rather than accepting an optional testID prop the way SimpleTextInput/FilledTextInput in the same directory do. Every one of the 15+ call sites (SwapConfirmationScene, SendScene2, AccelerateTxModal, etc.) now renders the identical testID, so two sliders in the same tree (e.g. a modal over a scene) can't be disambiguated by a Maestro flow or RN-testing-library selector.

<Animated.View
testID="confirmSliderThumb"
style={[

  1. Lower confidence: dropping .withRest from SIDESHIFT_INIT also means any other undeclared key present in the real env config besides privateKey is now silently discarded, unlike every other CorePluginInit schema in this file, which keeps .withRest for exactly that forward-compat reason.

SIDESHIFT_INIT: asCorePluginInit(
asObject({
affiliateId: asOptional(asString, '')
})
),

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@j0ntz

j0ntz commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review findings above (fixup commit pushed):

  • Finding 2 (hardcoded testID on SafeSlider): fixed. SafeSlider now accepts an optional testID prop defaulting to confirmSliderThumb, matching the SimpleTextInput/FilledTextInput convention. All existing call sites keep the same rendered testID (snapshot tests unchanged, 17 passed), and any scene needing a distinct selector can now pass its own.
  • Finding 1 (privateKey silently stripped): intended, not changed. Removing the field is the PR's purpose per SideShift's remediation guidance (the compromised secret must not ship in the client), and the CHANGELOG entry in this diff documents it.
  • Finding 3 (dropped .withRest): intended, not changed. .withRest is the passthrough that would keep forwarding privateKey from env.json to the plugin's initOptions, so dropping it is the mechanism of the removal, not an accident. SIDESHIFT_INIT now declares exactly what the plugin should receive.

@j0ntz j0ntz force-pushed the jon/remove-sideshift-private-key branch from cf652d5 to cdd0110 Compare July 9, 2026 18:00
@j0ntz j0ntz force-pushed the jon/remove-sideshift-private-key branch from cdd0110 to e978949 Compare July 10, 2026 23:57
@j0ntz j0ntz enabled auto-merge July 10, 2026 23:57
j0ntz added 2 commits July 10, 2026 17:47
The SideShift affiliate account was compromised. SideShift confirmed the swap
integration works identically without the privateKey, so stop validating and
injecting it (it was sent as the x-sideshift-secret header). Account rotation
to a new affiliateId is handled operationally.
@j0ntz j0ntz force-pushed the jon/remove-sideshift-private-key branch from e978949 to 0b6014d Compare July 11, 2026 00:47
@j0ntz j0ntz merged commit d02dfb8 into develop Jul 11, 2026
7 checks passed
@j0ntz j0ntz deleted the jon/remove-sideshift-private-key branch July 11, 2026 01:09
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.

2 participants