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.
This commit is contained in:
2026-08-11 10:37:26 -04:00
parent 70a12cad22
commit 420fc41571
+16 -2
View File
@@ -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