Skip to content
Memtru
Documentation menu
Security

Threat model

Scope: the Memtru CLI (local test engine) and the web application (dashboard, report ingestion). Status column: Mitigated means a control is implemented and covered by an automated test; Partial means a control exists with a documented gap; Accepted means a known risk for this release.

Assets

  • Organization data in the dashboard: runs, findings, membership, audit events.
  • API tokens and session cookies.
  • The systems Memtru is pointed at (writes and deletes happen there).
  • The operator's machine and CI runner.

Trust boundaries

  1. Browser ↔ web server (session cookie).
  2. CLI ↔ web server (bearer API token).
  3. CLI ↔ memory provider under test (provider output is untrusted).
  4. Uploaded report content ↔ dashboard rendering (report content is untrusted).

Threats and controls

Threat Control Status
Broken access control / IDOR Every service query joins organization_members for the current user; foreign ids resolve to "not found". Tests cover reads and writes on every service across organizations, guessed project ids, run ids from another project, and removed memberships. Mitigated
Cross-tenant leakage in the dashboard Same as above; the active organization cookie is only a preference and is re-validated each request. Mitigated
Privilege escalation Owner-only operations (delete project, manage members, delete runs) are checked server-side; last-owner protection. Tested. Mitigated
SQL injection Parameterized statements only; LIKE input is escaped; sort keys come from a fixed map. Mitigated
XSS via uploaded reports React escapes all text; no dangerouslySetInnerHTML for report content. The CLI's HTML report uses Jinja2 autoescaping and a CSP with no scripts. Tested with script payloads. Documentation pages render repository Markdown (trusted content) as HTML. Mitigated
CSRF Server actions use Next.js origin checks; cookie-authenticated API mutations require a matching Origin; cookies are SameSite=Lax, HttpOnly, Secure in production. Mitigated
SSRF from the CLI Outbound URLs are validated: scheme allowlist, no URL credentials, link-local/metadata/reserved refused, loopback and private ranges opt-in, optional host allowlist, no redirects, 1 MB response cap. Partial: DNS is checked once per run, not per request (rebinding).
Unsafe file paths The CLI writes only under the configured output directory with generated names; it reads only paths the user passes. Mitigated
Secret leakage in reports Reports use fingerprints by default; CLI redaction; server rejects remaining secret patterns. Partial: pattern matching cannot recognize every secret format.
Token theft Tokens are 256-bit random, stored as SHA-256 hashes, shown once, scoped to one project, expirable and revocable; last_used_at recorded; credentials file mode 600 where supported. Mitigated
Malicious reports 1 MB streaming limit, strict schema, cross-field consistency checks, raw-content rejection, rate limit per token. Mitigated
Brute force / rate abuse Login limited per IP and per email; sign-up per IP; uploads per token. Partial: limits are in-process and reset on restart; multi-instance deployments need a shared store.
Destructive testing Synthetic data only, run-scoped namespace and principals, ledger-based cleanup of only created records, production environment refused by default, explicit authorization for remote targets. Mitigated
Untrusted adapter output HTTP responses are size-capped and schema-validated; records are treated as data; unexpected exceptions are reported by type only unless raw content is enabled. Mitigated
Sensitive data in logs No request bodies are logged; unexpected errors log only a request id and error type. Mitigated
Insecure defaults Production sign-up defaults closed; only explicit MEMTRU_SIGNUP=open enables it. Operator provisioning supports controlled beta enrollment; security headers and nonce-based CSP remain. Mitigated locally; hosted proxy verification pending
Dependency attacks Lockfile committed; Vitest updated to 4.1.12, removing vulnerable Tinypool; Python and production npm audits configured in CI. Partial: unpatched development-only braces advisory; fresh CI execution still required.
Prototype pollution Uploaded JSON is parsed then validated by strict Zod schemas that reject unknown keys; no deep merges of user input. Mitigated
Account takeover via password reset No email reset exists; administrators reset passwords with a local script. Accepted
Unverified email addresses Accounts are not email-verified in this release. Membership is granted by owners explicitly, so an unverified address alone grants no access to others' data. Accepted

Out of scope

Physical security of the hosting environment, backups, and the security of the systems being tested.

Membership and upload consistency (2026-10-22)

Bearer authentication joins current creator membership; deleted/null creators fail closed. Migration 0002 permanently revokes organization-scoped tokens on membership deletion and backfills already orphaned tokens. Restoring membership never clears revocation. Session identity does not cache organization permissions. Ingestion rechecks current token scope, expiry, membership, project archive state and organization deletion inside its BEGIN IMMEDIATE transaction, including duplicate uploads. A request that committed before removal may succeed; requests committing after removal must fail. Requests authorized before body streaming do not retain write access after revocation. This guarantee applies to the documented single-server SQLite architecture; it is not a promise of distributed consistency.

The limiter now rejects new identities when its bounded map is saturated instead of clearing live limits. A trusted proxy remains necessary to prevent forged client-address headers. Neither change guarantees a vulnerability-free application. See the repository audit reports for evidence and open operational risks.

Hosted PostgreSQL extension

For the Vercel/Supabase topology, asynchronous services preserve these checks. Service transactions acquire a shared advisory transaction lock before reading authorization; statement triggers acquire the same lock for database writes, including direct membership deletion. This preserves revocation ordering across application instances. Atomic PostgreSQL rate-limit buckets replace local memory in hosted mode. The application schema is private, RLS denies unprivileged roles, and a server-only runtime role receives explicit DML privileges. The role's policy allows server access to all tenants; per-tenant authorization remains a service boundary. This is not per-user Supabase Auth RLS. See docs/vercel-supabase.md.