Open ADMS keeps evidence that public money is paid on: every ticket, photograph and audited change may be read in a federal audit or a fraud case years later. Security comes first, at every layer.
The standards it is checked against
Section titled “The standards it is checked against”- OWASP ASVS (current release), Level 2, for the API and the web apps.
- OWASP MASVS for the field app.
- OWASP API Security Top 10.
- NIST SP 800-63B for passwords and authenticators.
- CIS PostgreSQL Benchmark for the database’s configuration.
In transit
Section titled “In transit”- HTTPS everywhere, locally too. Every hop inside the local stack is TLS: Caddy reaches every service over TLS, the API serves HTTPS alone, and Postgres takes TLS alone. TLS 1.2 or later, HSTS on. Plain HTTP is redirected for the web apps’ pages and refused at the API’s address.
- CORS by an exact allow-list of origins from configuration: no wildcards, no patterns.
- The web apps send a strict Content Security Policy,
frame-ancestors 'none',nosniffand a referrer policy.
People: sign-in and sessions
Section titled “People: sign-in and sessions”- Two-step sign-in is required for admins and managers/analysts (an authenticator app), optional for read-only viewers unless the firm requires it.
- Passwords are hashed with Argon2id and follow NIST SP 800-63B: length over complexity, known breached passwords refused, no forced rotation.
- Sessions are a short-lived access token and a rotating refresh token, in
HttpOnly,Secure,SameSite__Host-cookies, never in browser storage; refresh tokens are kept only as hashes and can be revoked; cookie-authenticated writes carry a CSRF value. A quiet session ends, and every session ends a set time after sign-in. - Permissions are checked on the server on every request, from the database, so a revoked permission refuses the very next request. Nobody can grant a permission they don’t hold, and the last admin can’t be removed or locked out.
- Wrong tries make a login wait, then lock it; limits by address stop a stranger first. Every sign-in, failure, lockout, phone setup and use of the master passcode is a security event.
Phones
Section titled “Phones”- A phone is enrolled once with the master passcode and proves itself before every sync by signing a server challenge with its own device key (ECDSA P-256), which never leaves it.
- Monitor credentials work only on enrolled phones, never on the web. Offline sign-in checks a login list of slow hashes, salted per phone, inside the encrypted store; it waits after 5 wrong tries and locks after 10 until the master passcode.
- The phone’s store is encrypted at rest and never backed up. A phone taken out of service wipes itself only after the server confirms it holds everything; a lost phone’s key is revoked.
The database enforces its own rules
Section titled “The database enforces its own rules”The database is the last line of defence; it doesn’t trust the code above it.
- A login role per service, each with only the grants its work needs: the API can’t write a completed load or a transaction, the worker can’t change setup, nobody can change the audit log.
- No hard deletes of operational data.
- The audit log is written by the database itself, by trigger, for every change, and a write with no actor is refused.
- Row-level security limits what each read-only viewer can read.
- Constraints carry the rules that keep money right: a pay item’s code unique on its project, a load’s links to its field events unique so it bills once.
Requests
Section titled “Requests”- Every request body is checked against the shared schema at the boundary; unknown fields are refused; size limits apply.
- Rate limits per address, per account and per phone.
- Errors are plain codes: no stack traces or internal errors in responses.
- Logs are structured with a request id, and never hold a secret, a token, a password or full personal details.
- Files come in only through their own routes (photographs, a project’s map file), checked for type and size, a photograph’s hash verified, stored outside the web root and never executed.
The supply chain
Section titled “The supply chain”- Every dependency is pinned to an exact version, at least seven days old, with a licence compatible
with Apache-2.0 for what ships (
pnpm licences). - No dependency runs an install script unless the workspace allows it by name.
pnpm securityruns the dependency audit (every advisory fails it unless accepted by name, with why it can’t be reached), a secrets scan and the database’s grant tests.
Secrets
Section titled “Secrets”Secrets are generated at setup, stored outside the repository (git-ignored env files locally, the provider’s secret store in a cloud), and never logged, printed or committed. The installer never rotates its secrets on a resume and never prints one.