Security Self-Audit
This is a maintainer self-audit for people deciding whether to point Codex
Quota Monitor at sensitive CLIProxyAPI and Codex OAuth state. It is not a
third-party audit or a claim that deployment is risk-free. It is a map of what
the monitor reads, where that data can go, what the project promises not to
persist or expose, and how to verify those claims locally.
For a more structured asset, boundary, and abuse-path view, see the threat model.
Threat Model
The monitor is designed for a trusted operator running a self-hosted dashboard near an existing CPA deployment. This is an operational security model, not a hosted SaaS security model: the project relies on local binding, least-privilege runtime configuration, secret redaction, and an external authenticated access layer when the dashboard is shared. It does not implement built-in user login, RBAC, multi-tenant isolation, or user access audit logs. The main assets are:
- CPA management key.
- Local Codex OAuth auth files and their
access_tokenvalues. - Account labels, plan types, quota windows, request/model activity, and gateway health metadata.
- Optional SQLite history and benchmark summaries.
In scope:
- Accidental exposure through the browser UI, read-only HTTP API, Prometheus metrics, logs, SQLite history, and benchmark output.
- Over-broad Docker, NixOS, or reverse-proxy deployment choices.
- Regressions that accidentally serialize key or token values.
Out of scope:
- A malicious root user or compromised host.
- A compromised Python runtime, container runtime, CPA process, browser, or reverse proxy.
- Public exposure of the dashboard without an authenticated access layer.
- Bugs or policy behavior in upstream CPA or provider APIs.
Data Flow
| Input | Why it is used | Destination | Persistence |
|---|---|---|---|
managementBaseUrl |
Read CPA management endpoints for auth inventory, usage, routing, logs, and health. | Local CPA endpoint configured by the operator. | Derived status and counters may appear in API/UI/history. |
| CPA management key | Adds Authorization: Bearer <key> to CPA management requests. Legacy manual_quota_refresh_auth = "management-key" deployments also use it for the protected manual direct-quota refresh action. |
The configured CPA management URL, and legacy inbound /api/v1/quota/refresh verification. |
The key value should not be serialized to API/UI/history/metrics/logs. Browser storage is off by default and only available in legacy management-key refresh mode when allow_browser_key_storage is enabled. |
| Manual refresh UI token | Authorizes the default browser POST /api/v1/quota/refresh action without exposing the CPA management key. |
Rendered page bootstrap and inbound /api/v1/quota/refresh verification. |
Generated in memory at process start and embedded only in the served HTML page; not printed by print-config, API JSON, history, metrics, or logs. |
| Codex auth files | Optional direct quota sampling from auth-file access_token and account id. |
https://chatgpt.com/backend-api/wham/usage. |
Token values should not be serialized to API/UI/history/metrics/logs. |
| SQLite state DB | Stores Trends, ETA, Audit, and Stats time-series inputs from dashboard snapshots. | Local path configured by --state-db or the NixOS module. |
Account labels, status, quota, and usage metadata are persisted. |
| Benchmark summary | Displays calibration results in Trends. | Local summary.json path. |
Summary fields are shown in the dashboard. |
The HTTP API is mostly read-only, but it is not an authentication boundary. The
Host and Origin checks reduce accidental or cross-origin exposure; they do
not identify users or replace a login/access layer. The default
POST /api/v1/quota/refresh action verifies an in-page refresh token before
queuing a provider-facing refresh, so browser users do not need the CPA
management key. Setting manual_quota_refresh_auth = "none" removes that action
check and should be limited to trusted local deployments.
Treat /api/v1/status, /api/v1/stats, /api/v1/recommendations,
/api/v1/diagnostics, /api/v1/alerts, and /metrics as sensitive
operational metadata even when secret values are redacted. LAN or reverse-proxy
deployments must explicitly list the browser hostname/IP through
--allowed-host or the NixOS module allowedHosts option.
Security Claims
These claims are intended to be testable in the repository:
- Default service exposure is loopback-first. Python and Nix default to
127.0.0.1:4515; the NixOS module keepsopenFirewall = false; HTTP requests with unexpectedHostorOriginvalues are rejected, but that rejection is not user authentication. - The Docker example publishes
127.0.0.1:4515:4515, drops Linux capabilities, uses--read-only,--security-opt=no-new-privileges, and mounts only the management key file, auth-files directory, and/data. - The CPA management key is read from a file when configured and is used as a
CPA request header. The default protected manual quota refresh endpoint uses
an in-page UI token instead; only legacy
management-keymode reuses the CPA key for that action. - Direct Codex quota sampling reads auth JSON locally and sends only the bearer token needed for the provider usage request. The token is not part of the public API schema.
print-configprints resolved non-secret configuration;doctor --jsonreports access and risk checks without printing secret contents.- SQLite history stores operational metadata, not management key or auth token values.
- Release images are built from tagged source, include SBOM and provenance
attestations, and are signed with keyless
cosignthrough GitHub OIDC.
Local Verification
Run the normal test suite before trusting a change:
nix develop -c python3 -m unittest discover -s tests -v
nix develop -c python3 -m build
Check that resolved config and doctor output do not print secret values:
codex-quota-monitor print-config \
--management-base-url http://127.0.0.1:8317 \
--management-key-file /path/to/management-key \
--gateway-health-url http://127.0.0.1:8317/healthz \
--state-db off
codex-quota-monitor doctor --json \
--management-base-url http://127.0.0.1:8317 \
--management-key-file /path/to/management-key \
--gateway-health-url http://127.0.0.1:8317/healthz \
--auth-dir /path/to/auth-files \
--state-db off
After starting the service, inspect the read-only outputs for an exact secret string that only exists in your key or auth file:
secret='replace-with-a-test-only-secret-string'
curl -fsS http://127.0.0.1:4515/api/v1/status > /tmp/cqm-status.json
curl -fsS http://127.0.0.1:4515/api/v1/recommendations > /tmp/cqm-recommendations.json
curl -fsS http://127.0.0.1:4515/api/v1/diagnostics > /tmp/cqm-diagnostics.json
curl -fsS http://127.0.0.1:4515/api/v1/alerts > /tmp/cqm-alerts.json
curl -fsS http://127.0.0.1:4515/metrics > /tmp/cqm-metrics.txt
grep -R --fixed-strings "$secret" /tmp/cqm-status.json /tmp/cqm-recommendations.json /tmp/cqm-diagnostics.json /tmp/cqm-alerts.json /tmp/cqm-metrics.txt
The grep command should print no matches. Use a throwaway test secret for
this check; do not paste real auth tokens or management keys into issue reports.
Before sharing support artifacts, create redacted copies instead of editing the original files:
codex-quota-monitor redact /tmp/cqm-status.json --secret "$secret" >/tmp/cqm-status-redacted.json
codex-quota-monitor redact /path/to/history.sqlite3 --output /tmp/history-redacted.sqlite3 --secret "$secret"
codex-quota-monitor redact /path/to/benchmark/summary.json --output /tmp/summary-redacted.json --secret "$secret"
For a staged support directory, run a dry-run summary first. Directory and
multi-path redaction only modifies files when --apply is present:
codex-quota-monitor redact /tmp/cqm-support --secret "$secret"
codex-quota-monitor redact /tmp/cqm-support --secret "$secret" --apply
The dry-run JSON reports how many supported JSON, JSONL, CSV, text/Markdown,
and SQLite files would change. Use --apply only on copied support artifacts,
not on live auth directories, live history databases, or benchmark directories
that the service is still writing.
Deployment Rules For New Users
- Keep the monitor bound to
127.0.0.1while evaluating it. - When binding beyond loopback, explicitly allow only the browser-facing
hostname/IP with
--allowed-hostorservices.codexQuotaMonitor.allowedHosts. - Use
--management-key-file; avoid inline keys in shell history or process listings. - Mount only the exact key file and auth-files directory the monitor needs. Keep auth files read-only unless you explicitly enable auth live plan sync; then make only that sync runner writable.
- Keep
/dataor the configured SQLite path as the only writable persistent path in container deployments. - Do not mount your whole home,
.config, password store, or unrelated secret directories. - If you need LAN or public access, put the monitor behind an authenticated reverse proxy or access layer. Do not expose the monitor API directly.
- Treat the dashboard
Audittab as an operational diff of stored account-pool snapshots, not as a user-login or access audit log. - Treat support bundles, screenshots, logs,
summary.json, and SQLite history as sensitive unless you have redacted account labels and operational metadata.
Residual Risks
The monitor still sees sensitive material by design. A local attacker who can read the monitor process memory, read the auth directory, read the management key file, or control the configured CPA or proxy endpoint can exfiltrate secrets. Exposed dashboard endpoints can reveal account labels, plan tiers, quota timing, recent model activity, and service health even without raw token values. Keep the runtime local and narrowly permissioned, and review every new field added to API, metrics, history, benchmark, or logs as a privacy change.