security(frontend): fix innerHTML XSS in error handler, remove Helius keys from bundle
Some checks failed
CI / build (push) Failing after 26s

refactor(frontend): centralize API base URL to lib/env.ts (7 files updated)
refactor(frontend): remove VITE_API_URL from .envrc for production parity
chore(frontend): fix ScrollToTop — add pathname to useEffect deps

Prod audit: fix critical XSS + Helius key leak + API URL fragmentation
This commit is contained in:
Crypto Rug Munch 2026-07-08 22:20:31 +02:00
parent 6d18e8200f
commit 93aa749a04
12 changed files with 2523 additions and 849 deletions

1
.envrc
View file

@ -1,3 +1,2 @@
source_up
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PWD/node_modules/.bin:$PATH"
export VITE_API_URL="https://rugmunch.io"

3271
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,4 @@
import { API_BASE_URL } from '@/lib/env'
// @ts-nocheck
import { motion } from 'framer-motion'
@ -30,7 +31,7 @@ import {
import { useEffect, useState } from 'react'
import aiRouter from '@/services/aiRouter'
const API_BASE = import.meta.env.VITE_API_URL || ''
const API_BASE = API_BASE_URL || ''
/* ── Types ── */
interface NewsItem {

View file

@ -1,3 +1,4 @@
import { API_BASE_URL } from '@/lib/env'
// @ts-nocheck
import {
@ -70,7 +71,7 @@ export default function RugRiskPanel({ pair }: Props) {
})
// Fetch trading signal
fetch(`${import.meta.env.VITE_API_URL || ''}/api/ai-trading/signal`, {
fetch(`${API_BASE_URL || ''}/api/ai-trading/signal`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({

View file

@ -12,7 +12,7 @@ const envSchema = z.object({
VITE_SUPABASE_ANON_KEY: z.string().optional().default(''),
// API endpoint
VITE_API_URL: z.string().optional().default('http://localhost:8000'),
VITE_API_URL: z.string().optional().default(''),
// WebSocket endpoint
VITE_WS_URL: z.string().optional().default('ws://localhost:8000/ws'),
@ -21,9 +21,7 @@ const envSchema = z.object({
VITE_GHOST_URL: z.string().optional().default(''),
VITE_GHOST_KEY: z.string().optional().default(''),
// Helius RPC (Solana)
VITE_HELIUS_RPC_URL: z.string().url().optional().default('https://mainnet.helius-rpc.com'),
VITE_HELIUS_API_KEY: z.string().optional().default(''),
// Helius RPC (Solana) — removed from client bundle; backend proxies all Helius calls
})
export type Env = z.infer<typeof envSchema>
@ -51,7 +49,6 @@ export const SUPABASE_URL = env.VITE_SUPABASE_URL
export const SUPABASE_ANON_KEY = env.VITE_SUPABASE_ANON_KEY
export const GHOST_URL = env.VITE_GHOST_URL
export const GHOST_KEY = env.VITE_GHOST_KEY
export const HELIUS_RPC_URL = env.VITE_HELIUS_RPC_URL
export const HELIUS_API_KEY = env.VITE_HELIUS_API_KEY
// HELIUS_RPC_URL and HELIUS_API_KEY removed — backend proxies all Helius calls
export const APP_NAME = 'RugMunch Intelligence'
export const APP_VERSION = '2.0.0'

View file

@ -33,27 +33,53 @@ window.onerror = (msg, url, line, col, error) => {
col,
time: Date.now(),
})
rootEl.innerHTML = `
<div style="position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#07070b;color:#FF3366;font-family:monospace;padding:2rem;overflow:auto">
<div style="max-width:700px;width:100%">
<h3 style="color:#8B5CF6;margin-bottom:1rem">Runtime Error</h3>
<div style="background:#0f0f14;border:1px solid rgba(255,255,255,0.1);border-radius:12px;padding:1rem;margin-bottom:1rem">
<p style="color:#FF3366;font-size:14px;word-break:break-word">${err.message || msg}</p>
</div>
${
err.stack
? `<div style="background:#0f0f14;border:1px solid rgba(255,255,255,0.1);border-radius:12px;padding:1rem;margin-bottom:1rem">
<p style="color:#8B5CF6;font-size:11px;margin-bottom:0.5rem">Stack Trace:</p>
<pre style="color:#94A3B8;font-size:10px;overflow:auto;max-height:200px;white-space:pre-wrap">${err.stack.split('\\n').slice(0, 8).join('\\n')}</pre>
</div>`
: ''
}
<div style="color:#94A3B8;font-size:11px;margin-bottom:1rem">
Source: ${url}:${line}:${col}
</div>
<button type="button" onclick="location.reload()" style="padding:10px 24px;background:#8B5CF6;border:none;color:white;border-radius:8px;cursor:pointer;font-size:14px">Reload Page</button>
</div>
</div>`
rootEl.textContent = '' // cleared below, rebuilt safely
const overlay = document.createElement('div')
overlay.style.cssText = 'position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#07070b;color:#FF3366;font-family:monospace;padding:2rem;overflow:auto'
const inner = document.createElement('div')
inner.style.cssText = 'max-width:700px;width:100%'
const h3 = document.createElement('h3')
h3.style.cssText = 'color:#8B5CF6;margin-bottom:1rem'
h3.textContent = 'Runtime Error'
inner.appendChild(h3)
const errBox = document.createElement('div')
errBox.style.cssText = 'background:#0f0f14;border:1px solid rgba(255,255,255,0.1);border-radius:12px;padding:1rem;margin-bottom:1rem'
const errP = document.createElement('p')
errP.style.cssText = 'color:#FF3366;font-size:14px;word-break:break-word'
errP.textContent = err.message || String(msg)
errBox.appendChild(errP)
inner.appendChild(errBox)
if (err.stack) {
const stackBox = document.createElement('div')
stackBox.style.cssText = 'background:#0f0f14;border:1px solid rgba(255,255,255,0.1);border-radius:12px;padding:1rem;margin-bottom:1rem'
const stackP = document.createElement('p')
stackP.style.cssText = 'color:#8B5CF6;font-size:11px;margin-bottom:0.5rem'
stackP.textContent = 'Stack Trace:'
stackBox.appendChild(stackP)
const pre = document.createElement('pre')
pre.style.cssText = 'color:#94A3B8;font-size:10px;overflow:auto;max-height:200px;white-space:pre-wrap'
pre.textContent = err.stack.split('\\n').slice(0, 8).join('\\n')
stackBox.appendChild(pre)
inner.appendChild(stackBox)
}
const srcDiv = document.createElement('div')
srcDiv.style.cssText = 'color:#94A3B8;font-size:11px;margin-bottom:1rem'
srcDiv.textContent = `Source: ${url || '?'}:${line || '?'}:${col || '?'}`
inner.appendChild(srcDiv)
const btn = document.createElement('button')
btn.type = 'button'
btn.style.cssText = 'padding:10px 24px;background:#8B5CF6;border:none;color:white;border-radius:8px;cursor:pointer;font-size:14px'
btn.textContent = 'Reload Page'
btn.onclick = () => location.reload()
inner.appendChild(btn)
overlay.appendChild(inner)
rootEl.appendChild(overlay)
return true
}

View file

@ -1,3 +1,4 @@
import { API_BASE_URL } from '@/lib/env'
import { AnimatePresence, motion } from 'framer-motion'
import {
AlertTriangle,
@ -19,7 +20,7 @@ import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { databus } from '@/services/databus'
const _BASE = import.meta.env.VITE_API_URL || ''
const _BASE = API_BASE_URL || ''
interface WalletForensic {
address: string

View file

@ -1,3 +1,4 @@
import { API_BASE_URL } from '@/lib/env'
/**
* 🤖 RMI AI Router Frontend Client
* All AI requests proxy through the backend (server-side keys, never exposed)
@ -21,7 +22,7 @@ export interface RouterResult {
latencyMs: number
}
const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'
const API_BASE = API_BASE_URL || ''
class AIRouter {
private modelCache: ModelConfig[] = []

View file

@ -1,3 +1,4 @@
import { API_BASE_URL } from '@/lib/env'
export interface DexPair {
chainId: string
dexId: string
@ -51,7 +52,7 @@ export interface Holder {
riskColor: string
}
const API_BASE = import.meta.env.VITE_API_URL || ''
const API_BASE = API_BASE_URL || ''
export async function searchPairs(query: string): Promise<DexPair[]> {
const res = await fetch(`${API_BASE}/api/dex/search?q=${encodeURIComponent(query)}`)

View file

@ -1,3 +1,4 @@
import { API_BASE_URL } from '@/lib/env'
/**
* RugMaps RAG Intelligence Bridge
* Connects frontend to RMI's 3-pillar RAG engine (FAISS + SPLADE + entity exact)
@ -43,7 +44,7 @@ export interface DeployerRisk {
verdict: string
}
const API_BASE = import.meta.env.VITE_API_URL || 'https://api.rugmunch.io'
const API_BASE = API_BASE_URL || ''
export async function searchRAG(
query: string,

View file

@ -1,3 +1,4 @@
import { API_BASE_URL } from '@/lib/env'
/**
* SENTINEL Scanner Bridge for RugMaps
* Real-time risk scoring, honeypot detection, contract verification
@ -26,7 +27,7 @@ export interface SentinelScanResult {
verdict: string
}
const API_BASE = import.meta.env.VITE_API_URL || 'https://api.rugmunch.io'
const API_BASE = API_BASE_URL || ''
export async function sentinelScan(tokenAddress: string, chain: string): Promise<SentinelScanResult | null> {
const res = await fetch(`${API_BASE}/api/v1/token/security-check`, {

View file

@ -13,5 +13,3 @@ vi.stubEnv('VITE_API_URL', 'http://localhost:8000')
vi.stubEnv('VITE_WS_URL', 'ws://localhost:8000/ws')
vi.stubEnv('VITE_GHOST_URL', '')
vi.stubEnv('VITE_GHOST_KEY', '')
vi.stubEnv('VITE_HELIUS_RPC_URL', 'https://mainnet.helius-rpc.com')
vi.stubEnv('VITE_HELIUS_API_KEY', 'test-key')