Skip to content

Create spec and security audit#1149

Merged
ebma merged 94 commits into
stagingfrom
create-spec-and-security-audit
May 20, 2026
Merged

Create spec and security audit#1149
ebma merged 94 commits into
stagingfrom
create-spec-and-security-audit

Conversation

@ebma
Copy link
Copy Markdown
Member

@ebma ebma commented May 20, 2026

In this PR, a security spec for Vortex is created that is used to perform an AI audit. The relevant audit findings where fixed. It also includes misc other improvements for security and transaction validation.

gianfra-t and others added 17 commits May 18, 2026 17:56
User-wallet phases (moneriumOnrampMint, SELL squidRouterApprove/Swap,
squidRouterNoPermit{Approve,Swap,Transfer}) previously fell through
validatePresignedTxs via 'continue', which allowed a malicious client to
attach an unrelated presigned tx labeled with one of these phase names
without any content validation. Flip the skip to a BAD_REQUEST reject and
direct integrators to submit only the on-chain tx hash via additionalData.

Add verifyUserSubmittedTxByHash helper that resolves the receipt and
transaction by hash, then binds receipt.from, tx.to, tx.input, tx.value to
the server-issued unsigned payload (blueprint.signer + blueprint.txData).
Refactor squidrouter-permit-execution-handler.waitForUserHash to delegate
to the helper, and add verifyUserSubmittedSquidHashes at the top of
FundEphemeralPhaseHandler.executePhase so SELL standard EVM offramps
verify squidRouterApprove + squidRouterSwap on-chain before any ephemeral
funding occurs. This closes the F-041 gap where SELL squid hashes were
neither validated as presigned txs nor verified at runtime.

Update validation.test.ts: replace 3 skip-tests with 5 reject-tests
covering each user-wallet phase, plus a positive test confirming BUY
squidRouterSwap still validates as ephemeral-signed. All 50 validation
tests pass.

Update docs/security-spec/03-ramp-engine/transaction-validation.md to
document the two-layer model (reject + by-hash verification), mark F-041
as MITIGATED, and add a threat row for user-wallet phase presigned-tx
smuggling.
The literal-string override widened verifyingContract from EvmAddress
(`0x${string}`) back to plain string, breaking TypedDataDomain
assignability. Narrow the literal to the branded hex type, which is the
canonical pattern for hex-string types in viem/ethers (already used a few
lines below for sig.r / sig.s).
Server-issued unsigned txs with maxPriorityFeePerGas:'0' (or other zero
minimums) were rejected when the signer produced a legacy/type-0 tx with
only gasPrice, blocking BRL->USDT onramp updateRamp. A zero minimum means
'no constraint', so a missing field is acceptable; only reject if a
concrete value is strictly below the minimum. Non-zero minimums still
require the field to be present and meet the bound.
@netlify
Copy link
Copy Markdown

netlify Bot commented May 20, 2026

Deploy Preview for vortex-sandbox ready!

Name Link
🔨 Latest commit 41caaf1
🔍 Latest deploy log https://app.netlify.com/projects/vortex-sandbox/deploys/6a0d7d61a3f0980008658063
😎 Deploy Preview https://deploy-preview-1149--vortex-sandbox.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 20, 2026

Deploy Preview for vortexfi ready!

Name Link
🔨 Latest commit 41caaf1
🔍 Latest deploy log https://app.netlify.com/projects/vortexfi/deploys/6a0d7d61f202b60008d2f069
😎 Deploy Preview https://deploy-preview-1149--vortexfi.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment thread apps/api/src/api/services/ramp/ramp.service.ts Fixed
Comment thread apps/api/src/api/services/ramp/ramp.service.ts Fixed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a security specification + audit artifacts for Vortex and applies multiple security/reliability hardening changes across the API, SDK, shared package, and frontend (auth enforcement, timeouts, cleanup phases, and stronger transaction validation).

Changes:

  • Add security spec documentation (auth + integrations) and OpenAPI export/type-generation tooling.
  • Harden API behavior: enforce auth/ownership on ramp endpoints, introduce fetch timeouts, tighten error handling, and add additional cleanup/validation paths.
  • Update transaction/ramp mechanics across chains (new cleanup phases, retry behavior, validation for user-submitted tx hashes, and phase naming normalization).

Reviewed changes

Copilot reviewed 193 out of 211 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
relayer-contract/SECURITY_AUDIT.md Add relayer security audit report
packages/shared/src/services/squidrouter/route.ts Reinstate slippage guardrail
packages/shared/src/services/pendulum/apiManager.ts Cache API instances per network/index
packages/shared/src/services/brla/mappings.ts Fix BRLA type import path
packages/shared/src/endpoints/ramp.endpoints.ts Normalize phases; expand cleanup phases
packages/shared/src/endpoints/brla.endpoints.ts Fix BRLA endpoints type imports
packages/shared/src/constants.ts Centralize sandbox flag via helper
packages/sdk/src/services/ApiService.ts Remove debug logging
packages/sdk/README.md Update SDK usage docs + APIs
package.json Add docs OpenAPI scripts
docs/security-spec/05-integrations/monerium.md Add Monerium security spec
docs/security-spec/05-integrations/alfredpay.md Add Alfredpay security spec
docs/security-spec/05-integrations/_template.md Add integration spec template
docs/security-spec/01-auth/supabase-otp.md Add Supabase OTP security spec
docs/security-spec/01-auth/admin-auth.md Add admin auth security spec
docs/api/scripts/generate-openapi-types.ts Generate TS types from OpenAPI
docs/api/scripts/export-openapi.ts Export OpenAPI from Apidog
docs/api/pages/11-production-checklist.md Add production checklist
docs/api/pages/10-sandbox.md Add sandbox guide + mock accounts
docs/api/pages/09-brl-kyc-notes.md Add BRL KYC integration notes
docs/api/pages/06-quotes-and-pricing.md Add quotes/pricing guide
docs/api/pages/05-ephemeral-key-custody.md Add ephemeral key custody guide
docs/api/pages/04-ramp-lifecycle.md Add ramp lifecycle guide
docs/api/pages/03-authentication-and-partner-keys.md Add auth + partner key guide
docs/api/pages/01-overview.md Add API docs overview
docs/api/apidog/page-manifest.json Add docs page manifest for Apidog
contracts/relayer/x-ray/entry-points.md Add relayer entry-point map
contracts/relayer/typechain-types/index.ts Typechain formatting update
contracts/relayer/typechain-types/factories/contracts/TokenRelayer__factory.ts Typechain formatting update
contracts/relayer/typechain-types/@openzeppelin/contracts/utils/index.ts Typechain formatting update
contracts/relayer/typechain-types/@openzeppelin/contracts/token/ERC20/index.ts Typechain formatting update
contracts/relayer/typechain-types/@openzeppelin/contracts/index.ts Typechain formatting update
apps/frontend/tsconfig.json Switch TS moduleResolution to bundler
apps/frontend/src/services/transactions/userSigning.ts Include optional gas override when provided
apps/frontend/src/pages/progress/phaseMessages.ts Remove deprecated *Evm phase labels
apps/frontend/src/pages/progress/phaseFlows.test.ts Add tests for phase flow lists
apps/frontend/src/main.tsx Make Sentry DSN env-configurable
apps/frontend/src/machines/ramp.context.ts Introduce initial ramp context helpers
apps/frontend/src/machines/ramp.actors.ts Add ramp machine actors (quote refresh, auth refresh)
apps/frontend/src/hooks/ramp/useRampSubmission.ts Minor error message formatting
apps/frontend/.env.example Add optional VITE_SENTRY_DSN
apps/api/src/models/rampState.model.ts Add unique constraint index name + uniqueness
apps/api/src/models/quoteTicket.model.ts Remove persisted fee column from model
apps/api/src/index.ts Refactor env loading + required secret validation
apps/api/src/database/migrations/026-add-unique-constraint-ramp-quote-id.ts Add unique constraint on ramp_states.quote_id
apps/api/src/database/migrations/025-remove-quote-ticket-fee-column.ts Drop quote_tickets.fee column
apps/api/src/constants/constants.ts Remove secrets/env from constants; add subsidy fraction constant
apps/api/src/config/express.ts Reduce request body limits; tighten CORS env gating
apps/api/src/config/database.ts Add production SSL dialectOptions
apps/api/src/config/crypto.ts Read webhook private key from config
apps/api/src/api/workers/cleanup.worker.ts Expand cleanup eligibility + null-safe cleanupCompleted
apps/api/src/api/services/webhook/webhook-delivery.service.ts Use fetchWithTimeout for webhooks
apps/api/src/api/services/transak/transak.service.ts Use fetchWithTimeout for provider calls
apps/api/src/api/services/transactions/stellar/offrampTransaction.ts Switch secrets + sandbox flag to config; adjust constants
apps/api/src/api/services/transactions/polygon/cleanup.ts Add Polygon cleanup approval builder
apps/api/src/api/services/transactions/onramp/routes/monerium-to-evm.ts Add Polygon cleanup tx; use shared funding account
apps/api/src/api/services/transactions/onramp/routes/monerium-to-assethub.ts Add Polygon + Hydration cleanup txs; use config sandbox flag
apps/api/src/api/services/transactions/onramp/routes/avenia-to-evm.ts Use shared funding account helper
apps/api/src/api/services/transactions/onramp/routes/avenia-to-evm-base.ts Add Base cleanup approvals + bounded backup approval
apps/api/src/api/services/transactions/onramp/routes/avenia-to-assethub.ts Add Hydration cleanup tx
apps/api/src/api/services/transactions/onramp/routes/alfredpay-to-evm.ts Add Polygon cleanup tx; use shared funding account helper
apps/api/src/api/services/transactions/onramp/common/transactions.ts Normalize Nabla phase names
apps/api/src/api/services/transactions/onramp/common/monerium.ts Use config sandbox flag
apps/api/src/api/services/transactions/offramp/routes/evm-to-brl-base.ts Add Base cleanup approvals; store evmEphemeralAddress
apps/api/src/api/services/transactions/offramp/routes/evm-to-alfredpay.ts Use config secrets; add Polygon AXLUSDC cleanup approval
apps/api/src/api/services/transactions/offramp/routes/assethub-to-brl.ts Split BRL offramp metadata validation
apps/api/src/api/services/transactions/offramp/common/validation.ts Add quote-metadata validation helper
apps/api/src/api/services/transactions/offramp/common/transactions.ts Use config sandbox flag for source network selection
apps/api/src/api/services/transactions/moonbeam/cleanup.ts Use shared funding account helper
apps/api/src/api/services/transactions/moonbeam/balance.ts Use shared funding account helper
apps/api/src/api/services/transactions/hydration/cleanup.ts Add Hydration cleanup extrinsic builder
apps/api/src/api/services/transactions/common/feeDistribution.ts Add fallback payout address + phase name normalization
apps/api/src/api/services/transactions/base/cleanup.ts Add Base cleanup approval builder
apps/api/src/api/services/stellar/loadAccount.ts Use shared HORIZON_URL constant
apps/api/src/api/services/stellar/checkBalance.ts Use shared HORIZON_URL constant
apps/api/src/api/services/stellar.service.ts Use config sandbox flag; import HORIZON_URL from shared
apps/api/src/api/services/slack.service.ts Move Slack config to vars + add fetch timeout
apps/api/src/api/services/sep10/sep10.service.ts Use config secrets + sandbox flag
apps/api/src/api/services/ramp/ramp-transaction-preparation.ts Add preparation-kind selector helper
apps/api/src/api/services/ramp/ramp-transaction-preparation.test.ts Add tests for preparation-kind selection
apps/api/src/api/services/ramp/helpers.ts Use fetchWithTimeout; use config sandbox flag
apps/api/src/api/services/ramp/base.service.ts Allow createRampState inside DB transaction
apps/api/src/api/services/quote/routes/strategies/onramp-monerium-to-evm.strategy.ts Refactor strategy definition helper
apps/api/src/api/services/quote/routes/strategies/onramp-monerium-to-assethub.strategy.ts Refactor + hydration stage helper
apps/api/src/api/services/quote/routes/strategies/onramp-avenia-to-evm.strategy.ts Refactor strategy definition helper
apps/api/src/api/services/quote/routes/strategies/onramp-avenia-to-evm.strategy-base.ts Refactor strategy definition helper
apps/api/src/api/services/quote/routes/strategies/onramp-avenia-to-assethub.strategy.ts Refactor + hydration stage helper
apps/api/src/api/services/quote/routes/strategies/onramp-alfredpay-to-evm.strategy.ts Refactor strategy definition helper
apps/api/src/api/services/quote/routes/strategies/offramp-to-stellar.strategy.ts Refactor strategy definition helper
apps/api/src/api/services/quote/routes/strategies/offramp-to-pix.strategy.ts Refactor strategy definition helper
apps/api/src/api/services/quote/routes/strategies/offramp-to-pix-base.strategy.ts Refactor strategy definition helper
apps/api/src/api/services/quote/routes/strategies/offramp-evm-to-alfredpay.strategy.ts Refactor strategy definition helper
apps/api/src/api/services/quote/routes/route-resolver.ts Switch resolver to singleton strategy instances
apps/api/src/api/services/quote/routes/route-definition.ts Add defineRouteStrategy + hydration stage helper
apps/api/src/api/services/quote/index.ts Reject misconfigured partners for EVM payout routes
apps/api/src/api/services/quote/engines/finalize/onramp.ts Enforce max amount limits
apps/api/src/api/services/quote/engines/finalize/offramp.ts Enforce max amount limits
apps/api/src/api/services/quote/engines/finalize/index.ts Stop persisting fee column
apps/api/src/api/services/quote/engines/fee/index.ts Document fee summary as single source of truth
apps/api/src/api/services/quote/core/quote-fees.ts Clamp negative fee components to zero
apps/api/src/api/services/priceFeed.service.ts Move provider config to vars + add fetch timeout
apps/api/src/api/services/phases/register-handlers.ts Remove deprecated EVM subsidy handlers
apps/api/src/api/services/phases/post-process/stellar-post-process-handler.ts Use config sandbox flag
apps/api/src/api/services/phases/post-process/index.ts Register additional post-process handlers
apps/api/src/api/services/phases/post-process/hydration-post-process-handler.ts Add Hydration cleanup post-process
apps/api/src/api/services/phases/post-process/base-chain-post-process-handler.ts Add Base ERC-20 sweep post-process
apps/api/src/api/services/phases/post-process/assethub-post-process-handler.ts Add AssetHub handler placeholder
apps/api/src/api/services/phases/phase-processor.ts Allow per-phase max retries
apps/api/src/api/services/phases/meta-state-types.ts Add tx-hash tracking fields
apps/api/src/api/services/phases/helpers/user-tx-verifier.ts Verify user-submitted EVM tx hashes vs blueprint
apps/api/src/api/services/phases/helpers/stellar-payment-verifier.ts Use shared HORIZON_URL constant
apps/api/src/api/services/phases/handlers/stellar-payment-handler.ts Persist stellarPaymentTxHash + idempotency guard
apps/api/src/api/services/phases/handlers/squid-router-pay-phase-handler.ts Use shared funding account helper
apps/api/src/api/services/phases/handlers/spacewalk-redeem-handler.ts Remove ts-ignore; adjust nonce reading
apps/api/src/api/services/phases/handlers/pendulum-to-hydration-xcm-phase-handler.ts Add idempotency guard + arrival polling
apps/api/src/api/services/phases/handlers/pendulum-to-assethub-phase-handler.ts Add idempotency guard
apps/api/src/api/services/phases/handlers/nabla-swap-handler.ts Add tx-hash idempotency guard; normalize phase names
apps/api/src/api/services/phases/handlers/nabla-approve-handler.ts Normalize phase names
apps/api/src/api/services/phases/handlers/moonbeam-to-pendulum-handler.ts Use config secret for executor key
apps/api/src/api/services/phases/handlers/monerium-onramp-self-transfer-handler.ts Use config secret for executor key
apps/api/src/api/services/phases/handlers/initial-phase-handler.ts Use config sandbox flag
apps/api/src/api/services/phases/handlers/hydration-to-assethub-xcm-phase-handler.ts Treat nonce mismatch as recoverable
apps/api/src/api/services/phases/handlers/hydration-swap-handler.ts Persist hydrationSwapHash + idempotency guard
apps/api/src/api/services/phases/handlers/helpers.ts Use config sandbox flag for Stellar passphrase
apps/api/src/api/services/phases/handlers/fund-ephemeral-handler.ts Verify user squid hashes before funding; normalize phase names
apps/api/src/api/services/phases/handlers/final-settlement-subsidy.ts Use shared funding account; add output sanity check
apps/api/src/api/services/phases/handlers/distribute-fees-handler.ts Normalize phases; add fetch timeout; move Subscan key to config
apps/api/src/api/services/phases/handlers/destination-transfer-handler.ts Validate destination recipient against presigned tx
apps/api/src/api/services/phases/evm-funding.ts Centralize EVM funding account derivation
apps/api/src/api/services/phases/base-phase-handler.ts Add optional per-handler max retries
apps/api/src/api/services/pendulum/pendulum.service.ts Move funding seed to config
apps/api/src/api/services/moonpay/moonpay.service.ts Use fetchWithTimeout for provider calls
apps/api/src/api/services/monerium/index.ts Move Monerium creds to config + add fetch timeout
apps/api/src/api/services/auth/supabase.service.ts Use admin client for token verification
apps/api/src/api/services/alchemypay/alchemypay.service.ts Use fetchWithTimeout for provider calls
apps/api/src/api/routes/v1/webhook.route.ts Require API key auth for webhook mgmt
apps/api/src/api/routes/v1/stellar.route.ts Require auth for Stellar create
apps/api/src/api/routes/v1/ramp.route.ts Require partner-or-user auth for ramp endpoints
apps/api/src/api/routes/v1/quote.route.ts Enforce partner auth when partnerId present
apps/api/src/api/routes/v1/maintenance.route.ts Gate schedules endpoints with admin auth
apps/api/src/api/routes/v1/index.ts Remove deprecated routes from v1 index
apps/api/src/api/routes/v1/brla.route.ts Require auth for user-scoped BRLA endpoints
apps/api/src/api/middlewares/error.ts Hide 5xx messages in non-dev environments
apps/api/src/api/middlewares/dualAuth.test.ts Add quote ownership tests
apps/api/src/api/middlewares/adminAuth.ts Use timingSafeEqual; add audit logging
apps/api/src/api/helpers/fetchWithTimeout.ts Add timeout wrapper for fetch
apps/api/src/api/helpers/anchors.ts Use fetchWithTimeout for TOML fetch
apps/api/src/api/controllers/subsidize.controller.ts Validate subsidy amount + use config seed
apps/api/src/api/controllers/stellar.controller.ts Use config secrets for Stellar funding
apps/api/src/api/controllers/session.controller.ts Use configurable widget base URL
apps/api/src/api/controllers/ramp.controller.ts Enforce quote/ramp ownership checks
apps/api/src/api/controllers/moonbeam.controller.ts Use config secret for executor key
apps/api/src/api/controllers/brla.controller.ts Normalize/consistently persist taxId
apps/api/src/api/controllers/admin/partnerApiKeys.controller.ts Use config sandbox flag for key env
apps/api/README.md Update endpoint/auth docs
apps/api/.env.example Expand + document new env vars
.gitignore Ignore env files repo-wide (keep .env.example)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 78 to +82
const network = this.getNetworkConfig(networkName);
const index = wsUrlIndex ?? 0;
const wsUrl = network.wsUrls[index];
logger.current.info(`Connecting to node ${wsUrl}...`);
const newApi = await this.connectApi(networkName, index);
const instanceKey = this.generateInstanceKey(networkName, index);
const existingInstance = this.apiInstances.get(instanceKey);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Removed the unused wsUrl variable from populateApi in 69904d1.

Comment on lines 38 to +40
const baseFee = STELLAR_BASE_FEE;
if (!FUNDING_SECRET) {
const NUMBER_OF_PRESIGNED_TXS = 5;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Replaced the local NUMBER_OF_PRESIGNED_TXS = 5 with the shared constant imported from @vortexfi/shared in 69904d1.

Comment on lines +53 to +59
if (pendulumToHydrationXcmHash) {
logger.info(
`PendulumToHydrationXCMPhaseHandler: Transaction already submitted (${pendulumToHydrationXcmHash}), waiting for arrival`
);
logger.info("Waiting for assets to arrive on Hydration");
await waitUntilTrue(didInputTokenArriveOnHydration, 60000);
return this.transitionToNextPhase(state, "hydrationSwap");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Replaced waitUntilTrue(didInputTokenArriveOnHydration, 60000) with waitUntilTrueWithTimeout(didInputTokenArriveOnHydration, 5000, 120000) on the already-submitted path (matching the fresh-submission path) in 69904d1. Also removed the now-unused waitUntilTrue import.

Comment thread apps/api/src/config/database.ts Outdated
Comment on lines +22 to +31
dialect: config.database.dialect,
dialectOptions:
config.env === "production"
? {
ssl: {
rejectUnauthorized: false,
require: true
}
}
: undefined,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changed rejectUnauthorized: false to process.env.DB_SSL_REJECT_UNAUTHORIZED !== "false" in 69904d1. This defaults to true (validates certificates) and provides an explicit opt-out via the DB_SSL_REJECT_UNAUTHORIZED=false env var for environments where certificate validation can't be used.

Comment on lines +34 to +39
### Euro Onramps
- **Login Method**: Sign in using an EVM wallet.
- **Test Wallet**:
- Public Address: `0x6f64A6a3eBB0Fa2F265bB173407cb2A90AE0D32f`
- Recovery Phrase: `sword joke bomb old couch junior dumb need story grace spirit casual`
- **Note**: This wallet is pre-loaded with testnet funds.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Removed the recovery phrase from the sandbox docs and replaced it with a note to use a freshly generated test wallet in 69904d1.

Comment on lines +11 to +14
export class BaseChainPostProcessHandler extends BasePostProcessHandler {
public getCleanupName(): CleanupPhase {
return "baseCleanupBrla";
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overrode createErrorObject in BaseChainPostProcessHandler to accept an optional CleanupPhase parameter, and updated all sweepToken error sites to pass the actual phase. Errors now correctly report the phase that failed (e.g. baseCleanupUsdc) rather than always showing baseCleanupBrla — fixed in 69904d1.

…UntilTrue timeout, SSL cert validation, seed phrase, cleanup phase label

Agent-Logs-Url: https://github.com/pendulum-chain/vortex/sessions/57d9c30a-ecbd-4c12-b598-95ed6c21e347

Co-authored-by: ebma <6690623+ebma@users.noreply.github.com>
Copilot finished work on behalf of ebma May 20, 2026 09:12
@ebma ebma merged commit 0ffee08 into staging May 20, 2026
6 of 7 checks passed
@ebma ebma deleted the create-spec-and-security-audit branch May 20, 2026 09:22
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.

4 participants