Threat Modeling a Citizen Ballot API
When a municipal government exposes a ballot tracking API, the threat model changes. You aren't protecting corporate data anymore. You're defending civic trust. While auditing a regional voting status service last quarter, we found a classic vulnerability dressed up in JSON. The endpoint took raw string parameters for ballot IDs and returned precinct statuses without schema validation or rate…
When a municipal government exposes an API for tracking ballots, the threat model changes dramatically. No longer are you safeguarding corporate secrets; you are defending the public's trust in the electoral process. During a recent audit of a regional voting status service, we uncovered a typical vulnerability, obscured by JSON formatting.
This endpoint accepted raw string parameters for identifying ballots and returned information about precincts, all without any schema validation or rate limits in place. An attacker with a large number of automated scrapers could easily disrupt service for citizens who are trying to verify their ballot status on election day.
Typically, engineers rely on web application firewalls and API keys as defenses against such attacks. However, when it comes to civic infrastructure, a different approach is required. Public APIs cannot use secret credentials because anyone, not just authorized users, needs to query their ballot status. This fact invalidates authentication as a primary line of defense against scraping. Instead, the focus must shift to behavioral analysis and input sanitization.
One vulnerability that was discovered was an SQL injection vector. The backend was using dynamic string concatenation to construct query filters. A separate reporting route had bypassed the Object-Relational Mapping (ORM) for performance reasons, leaving a significant gap that could be exploited with tautology payloads. To mitigate this risk, we replaced the raw query builder with parameterized statements enforced at the database driver level.
Additionally, we implemented strict TypeScript interfaces for all incoming query parameters, rejecting any requests that contained unexpected keys or special characters before they reached the database connection pool.
Stopping scrapers required a more nuanced strategy. We couldn't block automated tools entirely because legitimate news organizations, researchers, and civic technology groups often scrape public data using custom scripts. Rather than blocking user agents that didn't originate from web browsers, we developed an algorithmic rate limiter.
This system used sliding windows based on subnet blocks and query costs to limit the number of requests. For instance, requesting a single ballot ID caused minimal delay, but trying to paginate through millions of sequential IDs triggered escalating delays and eventually exhausted the rate limit. We also introduced cryptographic nonces into the frontend payload, requiring automated clients to perform a lightweight proof-of-work challenge.
This made large-scale scraping too costly and impractical to perform. In summary, when building systems for public administration, it's crucial not only to adhere to clean architectural principles and optimize for low latency but also to create defenses that keep legitimate users transparent while effectively resisting malicious actors.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.