From 420fc41571edcfc71cf4f5a2927d6da08528dd66 Mon Sep 17 00:00:00 2001 From: Jared Knowles Date: Tue, 11 Aug 2026 10:37:26 -0400 Subject: [PATCH] fix: validate the deep-link state param before propagating it ?state= was used verbatim and ends up interpolated into a fetch URL and into the Hugging Face parquet shard path duckdb-wasm reads. Impact is low -- it is a client-only fetch to a public HTTPS URL, and the wasm sandbox loads no httpfs -- but validating at the boundary is cheap and correct. Deep links now require /^[A-Z]{2}$/ (after trim + uppercase, so ?state=az still works) and fall back to the state selector with a warning when they do not match. --- src/App.jsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index cd27b8b..8fd2b39 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -6,9 +6,14 @@ import ChartPanel from './components/ChartPanel.jsx' import Footer from './components/Footer.jsx' import { fetchDistrictEstimates } from './hooks/useApi.js' +// Two-letter USPS state code. The deep-link value flows into API request URLs +// and into the Hugging Face parquet shard path duckdb-wasm reads, so it gets +// validated here at the boundary rather than propagated verbatim. +const STATE_CODE_PATTERN = /^[A-Z]{2}$/ + /** * CRDC Arrests API Demo App — main router. - * Flow: state → district search (with interesting suggestions) → loading animation → 6 charts + * Flow: state → district search (with interesting suggestions) → loading animation → charts */ export default function App() { const [step, setStep] = useState('state') // 'state' | 'search' | 'loading' | 'results' @@ -19,7 +24,16 @@ export default function App() { useEffect(() => { const params = new URLSearchParams(window.location.search) const leaid = params.get('leaid') - const stateParam = params.get('state') + const rawState = params.get('state') + const stateParam = rawState ? rawState.trim().toUpperCase() : null + + if (rawState && !STATE_CODE_PATTERN.test(stateParam)) { + // Malformed deep link — drop it and start at the state selector rather + // than passing an arbitrary string into fetch URLs and shard paths. + console.warn('Ignoring deep link: `state` is not a two-letter state code.') + return + } + if (leaid && stateParam) { // Deep link (including browser back/forward landing on this URL): resolve // the district name via a real estimates row, keyed by LEAID. The previous