Security

Compliance documents index

The procedures and policies behind this overview live under docs/compliance/ — quick jump-off:

Reporting a vulnerability

Email contact@meterbox.ai with details and reproduction steps. Please do not open public issues for security reports. We aim to acknowledge within 2 business days. Our disclosure program — scope, safe-harbor terms, and our commitments — is published at /.well-known/security.txt. Good-faith research within scope is authorized and protected by safe harbor. Paid researcher channel + reward tiers live in docs/compliance/bug-bounty-program.md; the annual external pentest program is documented at docs/compliance/penetration-testing.md.


Vulnerability management

Dependencies are monitored continuously: Dependabot opens update PRs (.github/dependabot.yml) and the Security scan CI workflow (.github/workflows/security-scan.yml) runs on every PR, every push to main, and weekly on a schedule. It runs npm audit (backend + frontend), a Trivy filesystem scan (vulns + secrets + misconfig) whose results are uploaded to the GitHub Security tab, and emits a CycloneDX SBOM artifact.

CI hard-fails on a Critical production-dependency advisory (scripts/ci/npm-audit-gate.js, which honors the time-boxed allowlist below, plus a Trivy critical gate with ignore-unfixed), so a known, patchable critical cannot be merged. Lower severities are reported non-blocking and remediated on the SLA below.

Remediation SLA (from disclosure / Dependabot alert; severity taken from the advisory's CVSS rating as a starting point):

Severity Target Notes
Critical 24 hours Patch, hotfix, or compensating control + ticket. CI blocks merges meanwhile.
High 1 week
Medium 1 month
Low 3 months
Informational best-effort Tracked, no committed deadline.

When a fix requires a breaking change we can't land immediately (or no fix exists), the advisory gets a time-boxed, justified exception in .github/npm-audit-allowlist.json — the gate ignores it until the until date, then fails again. Each entry records the compensating control and the remediation plan. CI logs every suppression on each run, so exceptions are never silent.

Active exceptions: none. (The prior request/form-data criticals were resolved by upgrading @kubernetes/client-node to 1.x.)


Data retention

Per-data-class retention. The live policy is queryable at GET /cp/retention (owner/admin) and surfaced in the SPA's Settings → Security tab — both read from src/control-plane/services/retention-policy.js, the single source of truth that the audit sweeper + session layer also enforce.

Data class Store Retention Configurable via
Audit log (cp_audit_events) control-plane Postgres 365 days, then pruned AUDIT_RETENTION_DAYS
Outbound webhook delivery log data-plane (per env) 30 days (terminal rows), then purged
Session — idle timeout Redis 30 min sliding SESSION_IDLE_TTL_SECONDS
Session — absolute cap Redis 12 h from login SESSION_ABSOLUTE_TTL_SECONDS
Usage ledger data-plane PVC lifetime of the account (statements need full history)
Credit grants data-plane (per env) lifetime; expired flipped to expired, never deleted
Reclaim backups (tarballs) control-plane PV operator-managed — no auto-prune; snapshot off-host BACKUP_PATH

A background sweeper prunes cp_audit_events older than the window (append-only otherwise — prune is the only deletion path, and only by age). For off-host SIEM shipping, GET /cp/audit/export?since=&until= streams the org's events as NDJSON. Audit-log tamper-evidence (WORM) + signed off-host archival remain on the compliance roadmap.


Detection & response


NIST 800-53 control mapping (technical controls)

This maps the technical controls implemented in this codebase to NIST SP 800-53 Rev. 5 control IDs, as a starting point for a System Security Plan (SSP). It is a self-assessment of what the software enforces — not an ATO or a 3PAO assessment. Organizational and operational controls (policies, training, incident response, contingency planning, personnel, physical) and several deployment-time technical controls (see Operator responsibilities) are required for an actual authorization and are out of scope of the code.

Defaults below are configurable; the FedRAMP-Moderate-aligned values are noted where they differ from the shipped default.

Access Control (AC)

Control Implementation Where
AC-2 Account Management Org+owner created at signup; invite flow (email + role); role update/remove; org deletion purges users/memberships + revokes sessions; SSO authenticates existing users only (no self-provision) routes/auth.js, services/{users,memberships,organizations,invites}
AC-3 Access Enforcement Session/JWT/admin-key auth populates req.auth; owner/admin/member gates on /cp/*; admin gate on /v1/* middleware/{authenticate,require-admin}.js
AC-6 Least Privilege Three-tier roles; invite/member-management/org-delete restricted to owner/admin routes/auth.js
AC-7 Unsuccessful Logon Attempts Throttled login (5/min per IP, 10/hr per email) and MFA verify (30/5min per IP, 10/5min per user) services/rate-limiter.js, routes/auth.js
AC-11 Session Lock (idle) Sliding inactivity timeout, renewed on each authenticated request — default 30 min (SESSION_IDLE_TTL_SECONDS; FedRAMP Moderate: 900) services/sessions.js
AC-12 Session Termination Absolute max-lifetime cap from login regardless of activity — default 12 h (SESSION_ABSOLUTE_TTL_SECONDS); explicit logout + Clear-Site-Data; revoke-all-sessions on password reset services/sessions.js, routes/auth.js

Audit & Accountability (AU)

Control Implementation Where
AU-2 / AU-3 / AU-12 Event logging, content, generation Append-only cp_audit_events records every mutating /cp/* action and security-relevant failures (login failure, bad-password/invalid-code MFA, rate-limited); each row carries actor id, denormalized actor email (survives user deletion), IP, user-agent, outcome, timestamp, and structured meta services/audit.js
AU-9 Protection of audit information Append-only (no update/delete API); exposed read-only to owner/admin via GET /cp/audit services/audit.js, routes/auth.js

Identification & Authentication (IA)

Control Implementation Where
IA-2(1)(2) MFA TOTP (RFC 6238, authenticator-app compatible) + one-time bcrypt-hashed backup codes; login returns a challenge instead of a session; disable requires both the account password and a fresh second factor services/mfa.js, routes/auth.js
IA-5 Authenticator management Password policy 12–72 chars + letter + number (NIST 800-63B; no forced composition); always-on local weak-password screen; bcrypt password hashing (cost configurable); one-time email/phone verification codes, invite tokens, and reset tokens all bcrypt-hashed schemas/index.js, services/{weak-passwords,users,verification,invites,password-reset}
IA-5(1) Compromised-password check Optional HIBP breach screening via k-anonymity (only a 5-char SHA-1 prefix leaves the process; fail-open); off by default for air-gapped installs, or point at a self-hosted mirror services/pwned-passwords.js
IA-8 Federated identity SSO via OIDC (Google + enterprise IdP); SAML intentionally out of scope services/oidc.js

System & Communications Protection (SC)

Control Implementation Where
SC-5 Denial-of-service protection Per-IP / per-identifier fixed-window rate limiting on every unauthenticated and sensitive endpoint (signup, login, MFA, password-reset, verification, SSO start, CSP report) services/rate-limiter.js
SC-7 Boundary protection Single-tenant isolation — one pod per (org, env) with its own PVC; COOP/CORP same-origin; X-Frame-Options: DENY. Egress SSRF guard on every tenant-configured outbound URL (webhooks, reverse-ETL sinks, security-alert webhooks): host allow-list rejects loopback/private/link-local/metadata addresses, and redirects are refused so a 3xx can't relay past the check (NetworkPolicy is the complementary perimeter) middleware/security-headers.js, lib/ssrf-guard.js, deploy chart
SC-8 Transmission confidentiality HSTS (1 y + includeSubDomains) enforces HTTPS in browsers; Secure cookie flag in production middleware/security-headers.js, routes/auth.js
SC-18 Mobile code CSP — default-src 'none' enforced on the JSON API, restrictive policy on /docs; violations reported to /cp/csp-report middleware/security-headers.js, routes/csp-report.js
SC-23 Session authenticity 256-bit server-side session tokens in httpOnly + SameSite=Lax cookies; CSRF double-submit on state-changing /cp/* and on session-cookie-authenticated /v1/* mutations (Bearer/admin-key callers exempt; one-time-token endpoints exempted) services/sessions.js, control-plane/csrf.js, middleware/require-csrf-for-session.js
SC-28 Protection of information at rest Passwords, MFA secrets/backup codes, verification + reset tokens hashed (bcrypt) or held only in-cluster; customer billing/metering data never leaves the cluster (no managed datastores) services/*, deploy chart

System & Information Integrity (SI)

Control Implementation Where
SI-4 System monitoring CSP violation collector (csp.violation log), structured per-request access log, append-only audit log routes/csp-report.js, middleware/access-log.js, services/audit.js
SI-10 Information input validation Zod schema validation on every request body/query/params; structured 400s with field paths middleware/validate.js, schemas/

Configuration Management (CM)

Control Implementation Where
CM-6 Configuration settings Hardened HTTP header baseline applied to every response across all roles; X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, X-Permitted-Cross-Domain-Policies: none, Cache-Control: no-store on sensitive surfaces; X-Powered-By removed middleware/security-headers.js
CM-7 Least functionality Permissions-Policy disables unused browser features (camera, mic, geolocation, payment, sensors, Topics) middleware/security-headers.js
SR-3 / CM-7 Supply chain Container images published to a private GHCR registry; pods pull via imagePullSecrets deploy chart, CI

Data access & least privilege

MeterBox staff never access tenant data in the course of running the platform. The architecture makes this structural, not just policy:

HIPAA scope (when a BAA is in place)

A Business Associate Agreement is available on the on-prem / enterprise tier. Until then the platform is not covered for PHI. Once a BAA is signed, the following data-store scope applies:

Data store Contents PHI-eligible Notes
Control-plane Postgres orgs, users, memberships, audit log ✅ in scope encrypt the PVC (SC-28); RBAC on by default
Data-plane PVC (per org × env) customer registry, ledger, coupons, credit grants ✅ in scope per-tenant isolation; encrypt the PVC
Redis sessions, rate-limit counters, verification flags 🟡 conditional no PHI by design; treat as in scope if metadata could carry it
Reclaim backups (BACKUP_PATH PV) gzipped /data archive per reclaim ✅ in scope encrypt at rest (roadmap: AEAD + KMS)
Metering request_id + metadata tenant-supplied per-event fields ⚠️ avoid enable zero-retention metering (/v1/settings/privacy) so these are never persisted
Stripe (external, opt-in) payment data ❌ out of scope here covered by the subprocessor's own PCI posture

To keep PHI out of MeterBox's persisted surface entirely, tenants on PHI workloads should enable zero-retention metering so only token aggregates + cost are stored — no request_id, no metadata.

Operator responsibilities (deployment-time controls)

The codebase cannot enforce these; they must be provided by the deployment:

This document covers the technical control surface only; a full FedRAMP/SOC 2 authorization additionally requires the organizational controls and a formal assessment. See the compliance & trust overview for framework status.