Hi, I noticed a possible paywall route matching issue in the gateway.
In src/gateway/server.ts, the x402 server is configured with full endpoint keys:
386: x402 = createX402Server(
387: {
388: payToAddress: config.x402.server.payToAddress,
389: network: config.x402.server.network || 'solana',
390: facilitatorUrl: config.x402.facilitatorUrl,
391: },
392: {
393: 'POST /api/compute': { priceUsd: 0.01, description: 'Compute request' },
394: 'POST /api/backtest': { priceUsd: 0.05, description: 'Strategy backtest' },
395: 'GET /api/features': { priceUsd: 0.002, description: 'Feature snapshot' },
396: 'POST /api/launch/token': { priceUsd: 1.00, description: 'Token launch' },
397: 'POST /api/launch/swap': { priceUsd: 0.10, description: 'Bonding curve swap' },
398: 'POST /api/launch/claim-fees': { priceUsd: 0.10, description: 'Claim creator fees' },
399: }
400: );
403: // Apply x402 middleware to premium routes
404: app.use(['/api/compute', '/api/backtest', '/api/features', '/api/launch/token', '/api/launch/swap', '/api/launch/claim-fees'], x402.middleware);
In src/payments/x402/index.ts, the middleware derives the endpoint key from req.path:
569: async middleware(req: any, res: any, next: () => void) {
570: // Get endpoint path
571: const path = req.path || req.url?.split('?')[0] || '';
572: const method = req.method || 'GET';
573: const endpointKey = `${method} ${path}`;
576: const endpointConfig = endpoints[endpointKey] || endpoints[path];
577: if (!endpointConfig) {
578: return next();
579: }
Because this middleware is mounted at paths such as /api/launch/token, Express may expose the mounted remainder as req.path rather than the full original URL. If path is / or otherwise stripped, it will not match keys like POST /api/launch/token, and the code goes directly to next().
When verification does run, the local accounting uses the configured endpoint price, but I did not see a local comparison between the submitted payment payload and the current endpoint's amount, recipient, network, or resource:
623: // Verify with facilitator
624: const verified = await verifyPayment(payload);
626: if (!verified) {
627: res.status?.(402) || (res.statusCode = 402);
...
633: stats.totalPayments++;
634: stats.totalRevenue += endpointConfig.priceUsd;
647: { path, amount: endpointConfig.priceUsd, payer: payload.payer },
651: // Proceed to handler
652: next();
The protected launch routes perform high-impact actions. For example, src/gateway/launch-routes.ts launches a token:
492: router.post('/token', async (req: Request, res: Response) => {
519: const validation = validateLaunchRequest(req.body);
527: try {
528: // Step 1: Resolve metadata URI
532: uri = await uploadMetadata({
574: // Step 3: Launch token
581: const r = await createDbcPoolWithFirstBuy(connection, keypair, {
590: const { createDbcPool } = await import('../solana/meteora-dbc.js');
644: res.json({ ok: true, data: { ...response, launchId: record.id } });
And the swap route uses the server keypair to execute a swap:
735: router.post('/swap', async (req: Request, res: Response) => {
768: // Get quote first to calculate minimumAmountOut with slippage
769: const { getDbcSwapQuote, swapOnDbcPool } = await import('../solana/meteora-dbc.js');
780: const result = await swapOnDbcPool(connection, keypair, {
787: res.json({
790: signature: result.signature,
The effective risk is:
full endpoint keys configured -> mounted middleware computes stripped path -> no endpointConfig -> next() -> premium handler
A safer design would use req.originalUrl or req.baseUrl + req.path for endpoint matching, add tests for mounted middleware paths, and locally bind verified payments to the current endpoint price, recipient, network, and resource before calling next(). I am reporting this as a potential issue rather than a confirmed exploit, since exact Express path behavior should be confirmed in the deployed version, but the current source has a clear route-key mismatch risk.
Hi, I noticed a possible paywall route matching issue in the gateway.
In
src/gateway/server.ts, the x402 server is configured with full endpoint keys:In
src/payments/x402/index.ts, the middleware derives the endpoint key fromreq.path:Because this middleware is mounted at paths such as
/api/launch/token, Express may expose the mounted remainder asreq.pathrather than the full original URL. Ifpathis/or otherwise stripped, it will not match keys likePOST /api/launch/token, and the code goes directly tonext().When verification does run, the local accounting uses the configured endpoint price, but I did not see a local comparison between the submitted payment payload and the current endpoint's amount, recipient, network, or resource:
The protected launch routes perform high-impact actions. For example,
src/gateway/launch-routes.tslaunches a token:And the swap route uses the server keypair to execute a swap:
The effective risk is:
A safer design would use
req.originalUrlorreq.baseUrl + req.pathfor endpoint matching, add tests for mounted middleware paths, and locally bind verified payments to the current endpoint price, recipient, network, and resource before callingnext(). I am reporting this as a potential issue rather than a confirmed exploit, since exact Express path behavior should be confirmed in the deployed version, but the current source has a clear route-key mismatch risk.