-
-
EVGuard's dashboard on load: the pipeline (Auth → Authz → State → Limits → Rate → ALLOW/BLOCK) and step-by-step usage, no context needed.
-
Eight one-click scenarios, grouped as normal ops vs. attacks. Each card names the attack and expected outcome before you even run it.
-
Same 20 kW command, two paths: the unprotected baseline jumps to 20 kW; EVGuard blocks it (policy.power_limit) and holds steady at 5 kW.
-
A blocked rate-limit attack: plain-English reason as the headline, full decision JSON (rule, state before/after) in Technical details.
-
Live session state — plugged-in/charging status, current power vs. configured limits — with the raw API response inspectable below.
-
Every ALLOW/BLOCK decision logged with rule and reason, filterable by decision/command/rule, with one-click CSV export for review.
-
EVGuard’s complete architecture: FastAPI backend, Security engine, EV simulator, Dashboard, Audit logging, policy configuration, tests
Inspiration
EV chargers are cyber-physical devices: a digital command directly causes a physical action. Most security thinking around them stops at "is the sender authenticated and authorized?" But a fully authenticated, fully authorized command can still be unsafe or invalid right now — a controller can be who it says it is, and still ask for 20 kW on a session capped at 7 kW, or try to start charging on a charger that was never plugged in. We wanted to explore that gap: what does it take to check a command against the current state and safety limits of the session, not just against who sent it?
What it does
EVGuard is a software-only, simulated security gateway that sits between a charging controller and the charging execution layer. Every command runs through one pipeline before it can execute:
Authentication → Authorization → Session state validation → Safety policy → Rate limiting → ALLOW / BLOCK
The first failing check decides the outcome. Every decision — ALLOW or BLOCK — comes with a plain-English reason, a rule ID, and is written to an audit log. Nothing is applied to the simulated charger unless the decision is an explicit ALLOW, and any unexpected internal error fails closed rather than open.
The project ships with:
- A FastAPI backend implementing the full decision pipeline, with a SQLite audit log.
- A simulated EV charger and 8 attack/scenario cases (excess power, excess current, invalid state transitions, invalid tokens, unauthorized roles, rate-limit floods, a full valid lifecycle, and a normal session).
- A Streamlit dashboard where you can run each scenario, watch the ALLOW/BLOCK decision and reason live, compare an unprotected "baseline" controller against EVGuard on the same attack, and browse a filterable audit trail.
- A one-command launcher (
run.ps1/run.sh) and a no-browserpython demo.pythat runs every scenario end-to-end and prints the results, for anyone who wants to verify the project without touching the UI.
How we built it
We split the work across three people around a single shared contract file that defined every command name, state, rule ID and API shape up front, so the three parts — the security engine, the simulator, and the dashboard — could be built independently and integrated without guesswork:
- Core engine & backend (Gopesh): the state machine, authentication/authorization checks, the safety-policy and rate-limiting logic, the SQLite audit log, and the FastAPI layer, plus final integration.
- EV simulator & attack scenarios (Videsh): a deliberately "dumb" simulated charger that just does what it's told, plus the 8 scenario definitions that double as both the demo script and the end-to-end test suite.
- Dashboard & input validation (Vishwajit): the Streamlit UI and the request-schema validation that rejects malformed commands before they reach the engine.
We used Claude for implementation, debugging, and reviewing our own security assumptions, and Antigravity/Gemini for early scaffolding. AI helped us move fast, but every rule, every test, and every claim in the README was checked against the actual running code before we trusted it — including catching and fixing a case where our own documentation overclaimed what the system actually did.
Challenges we ran into
- Keeping "authenticated" and "safe" clearly separate. It was easy to accidentally conflate identity checks with state/safety checks; we ended up with a strict, ordered pipeline specifically to keep these concerns distinct and testable.
- Integration drift. Three people building independently against a shared spec still produced real mismatches — like a rate-limit constant that got out of sync between the contract and one scenario file. We caught it with an end-to-end test that runs every scenario through the real engine, not just against mocks.
- Honest positioning. State-aware, specification-based misbehavior detection for EV charging already exists in the research literature (e.g. specification-based EVSE intrusion detection, ISO 15118 remote attestation). We deliberately avoided "first ever" or "novel" language and instead positioned EVGuard as a focused, lightweight enforcement layer that's complementary to that work — not a replacement for it.
- Not letting the demo lie. Early on, a UI panel described an outcome from hardcoded text instead of the actual result. We caught it and rebuilt every on-screen conclusion to be computed live from the real API response.
Accomplishments that we're proud of
- A fully fail-closed pipeline: unknown sessions, malformed input, and unexpected internal errors all default to BLOCK, never ALLOW.
- 230+ automated tests, including an end-to-end suite that runs all 8 attack scenarios through the real engine (not mocks) and checks the outcome.
- A one-command setup (
run.ps1/run.sh) and a no-browserdemo.py, so anyone reviewing the repo can verify our claims themselves in minutes. - No trace of any authentication token anywhere in logs, the database, or API error responses — verified with dedicated tests.
What we learned
That a security control is only as trustworthy as its weakest untested assumption — several of our real bugs weren't in the "clever" logic but in the boring integration seams between components. And that writing the contract and the tests before trusting a claim (including our own README's claims) catches more problems than writing them after.
What's next for EVGuard
Ingesting real OCPP 2.0.1/2.1 messages, ISO 15118-20 awareness, trusted charger telemetry instead of a simulated state source, hardware-backed authentication, and formal verification of the state machine and rule set.