Skip to content

Security

Priyesh Patel edited this page Sep 27, 2026 · 4 revisions

Security risks relevant to TransitSync and how the team will address them.

Data handled by the system

Most of what we handle is public transit data. We archive GTFS feeds that the STM and REM publish openly. The passengers in our simulations are generated by the model, so they aren't real people.

The archive is stored as compressed files in Firebase cloud object storage, partitioned by date and hour, with Firestore holding only the metadata about each file. Past simulation runs and their event logs are stored the same way, since they're far too large to sit in a database.

We do store some user data though. Saved scenarios need to persist across devices, so each one is tied to an account. What we keep per account is an email address, the scenarios they saved, and the parameters and results attached to them. No names, no payment info, no location data.

Access control

The repo is public but only team members have write access, and main is protected so everything goes through a reviewed pull request.

For the app, we're using Firebase Auth with Google and GitHub sign-in rather than our own passwords, so we never store password hashes and 2FA is handled by the provider. Our backend verifies the token on every request that needs an account.

Scenario data lives in Firestore and only our backend talks to it using a service account. The browser never connects to the database directly, so every read and write goes through our API where we check that the scenario actually belongs to the account requesting it. Browsing simulations and adjusting parameters doesn't need an account at all, only saving does.

The archive bucket isn't publicly readable. The archiver writes to it and our backend reads from it, both through the service account, and nothing touches it from the browser. Since the underlying data is public anyway, the reason for locking it down is integrity rather than confidentiality: we don't want anything modifying or deleting what we've collected.

Everything is deployed on a hosting platform so we don't manage a server ourselves. Access to the deployment dashboard is limited to team members with 2FA enabled, since anyone who gets into it can see all of our environment variables.

Secrets and credentials

We have the STM developer portal API key and our Firebase service account key. The service account key is the most sensitive thing we would hold, since it has full access to both the database and the archive bucket and bypasses any rules we set. Both are environment variables in the hosting dashboard and GitHub Actions secrets for CI, and neither is ever committed.

We gitignored .env from the first commit and turned on secret scanning and push protection so a committed key gets caught before it hits main. If one leaks we revoke it and generate a new one rather than just deleting it from history.

Dependency and code checks

Dependabot will be on for both Python and npm and we review security updates weekly. pip-audit and npm audit will run in CI and fail the build on anything high severity.

Dependencies are pinned with lockfiles so builds stay reproducible, and every pull request needs a review plus passing lint and type checks before it can merge.

Security testing

All client-supplied values are validated server-side before use. Since anyone can bypass the interface and send requests directly, the limits enforced by our controls provide no real protection on their own. Values outside the accepted range are rejected, as silently adjusting them would return results for a scenario the user did not request.

Firebase projects default to test mode, which leaves the database openly readable and writable. We configure Firestore and Storage to deny all client access from the outset, since all data access goes through our backend and the browser has no reason to connect to the database directly.

We test that scenario access verifies ownership rather than only authentication. Without this check, any authenticated user could retrieve another user's scenario by modifying the ID in the request, which is among the most common API vulnerabilities. We also test that protected endpoints reject missing or invalid tokens, ensuring they fail closed.

Our primary availability risk is the simulation endpoint. Running a simulation takes a few seconds of CPU on our end, but asking for one is just a single request, so it's cheap to ask and expensive to answer. That means it doesn't take many requests to use up our capacity.

We mitigate this at several levels. Simulation requests require authentication, allowing us to rate limit per account rather than per IP address, which is easy to change. Requests go into a queue instead of running right away, and once the queue is full we start rejecting new ones instead of letting work pile up. Each run has a timeout that kills it so nothing can hang forever.

We also cap simulation length and replication count together instead of separately. We cap simulation length and replication count as a combined limit rather than limiting each one separately, since the cost is the product of the two. If we capped them individually at 24 hours and 30 replications, a request for 24 hours repeated 30 times would pass both checks while costing 720 times more than a single one hour run. Instead we limit the product directly, something like hours × replications ≤ 100, so a run can be long or repeated but not both.

The most significant control is that adjusting a parameter never triggers a simulation. The number updates in real time, but it's coming from a grid of results we computed ahead of time, so dragging sliders around costs us nothing. Executing a real simulation for your exact values is a separate action you have to explicitly request.

Incident response

If a credential leaks we revoke it first, generate a new one, then figure out how it happened. Dependency vulnerabilities get triaged within a week, or right away if it's actually exploitable in our setup.

If we find a bug that exposed one account's scenarios to another, we take the app offline, fix it, and tell affected users what happened.

We validate each file on ingest and run a daily job that reports gaps and parse failures, so we'd notice if something was missing or malformed rather than finding out when a simulation produces odd results.

Users can delete their account and everything saved under it, and we'll honour that within a few days rather than keeping data around.

Security issues can be reported through GitHub issues, or directly to us if someone thinks it's sensitive.

Clone this wiki locally