Skip to content

Security: FERCBusters/keen

Security

SECURITY.md

Security policy and threat model

Reporting a vulnerability

Please report suspected vulnerabilities through this project's GitHub repository. Prefer Security → Advisories → Report a vulnerability for a private report. If private reporting is unavailable, open an issue asking for a private reporting channel and keep exploit details, credentials and sensitive data out of the public issue.

Every report must include a reproducible proof of concept demonstrating the issue. Include:

  • The KEEN version or commit, deployment configuration relevant to the issue, and affected component.
  • The attacker's required access, permissions and other prerequisites.
  • Minimal reproduction steps or a script, expected and actual results, and evidence of the security impact.
  • Synthetic test data and redacted logs or screenshots where useful.

Test on an installation you own or have explicit permission to assess. Use the smallest demonstration that establishes the issue, preserve other people's data, and avoid disruptive testing against public or hosted instances.

Our goal is to provide a preliminary response within 72 hours. This is an acknowledgement and initial assessment target; investigation and remediation time depends on the report. KEEN offers no monetary bounties. Authors of validated reports will be credited, using their preferred name or handle, unless they request anonymity. We coordinate disclosure with reporters after assessing impact and remediation.

What KEEN protects

KEEN stores operational evidence, artifacts, assurance mappings, audit discussions, risk and ISMS records, user accounts, integration credentials and agent identities. These records can contain sensitive organisational or personal information. Security objectives include controlling who can read or change them, keeping collection credentials within their intended connection, preserving evidence identity, and maintaining service availability.

A KEEN installation is an organisational trust boundary. Users inside it receive access through roles, groups and permissions. Source connections distinguish systems and credentials within that installation. They do not create independent tenants or separate encryption domains. Hosting mutually distrustful organisations requires isolated installations, databases, storage, broker namespaces and secrets.

Actors and realistic risks

The threat model includes an unauthenticated Internet client, a malicious website visited by a signed-in user, a user acting beyond their assigned permissions, a compromised enrolled agent or webhook credential, and hostile content returned by a configured evidence source. A valid source credential establishes who submitted evidence; reviewers still assess the truth and relevance of its contents.

Application administrators are trusted to configure sources, permissions, mapping rules and evidence retention. Deployment operators control the executable code, database, broker, storage and encryption keys. Compromise of these privileges can compromise the installation. An administrator's intended ability to delete data or grant permissions is part of that role; escaping the role's intended authority remains a security issue.

Upstream identity providers and authorised integration servers are trusted for their specific functions. A compromised provider can assert identities; a compromised collection server can return fabricated data and observe credentials issued for that server. Network controls and narrowly scoped upstream credentials reduce the resulting exposure.

Authentication, sessions and account linking

Browser sessions use random, server-generated identifiers stored in Valkey. The browser receives an HttpOnly session cookie. Successful authentication issues a fresh identifier, and CSRF-protected POST logout deletes the current server-side session. When the session store cannot confirm the deletion, sign-out returns a retryable error and leaves the browser's cookies in place for the retry. GET logout links open the account page without changing authentication state. Permission snapshots are checked against a database-backed authorization version, and inactive accounts are rejected. Deactivating an account advances its database-backed session version, invalidating previously issued sessions across authentication methods even after reactivation. In-flight requests can finish; subsequent authentication checks reject the old sessions. Session expiry is rolling; operators configure its duration.

Local passwords are stored as salted PBKDF2-HMAC-SHA256 hashes. Password login attempts are rate limited by client address and username. Current-password confirmation for password changes is limited per address and per account. Authentication rate limits fail closed when their backing service is unavailable. Local password and MFA changes invalidate local/LDAP sessions through the account's security version.

Local and LDAP login support TOTP, WebAuthn security keys/passkeys and recovery codes. A pending MFA ceremony has its own short-lived credential and grants access only to that ceremony. Account security changes require reauthentication; an existing factor must be verified before managing enabled MFA. TOTP secrets are encrypted, recovery codes are hashed and single-use, and WebAuthn checks the expected origin, relying-party ID and challenge. KEEN uses WebAuthn as a second factor alongside a password; hardware authenticators may rely on user presence without requiring a PIN or biometric verification.

KEEN's local MFA policy applies to local and LDAP authentication. Configure equivalent MFA at the identity provider when using SSO, or at the authenticating proxy when using trusted remote-user authentication. Those mechanisms constitute separate authentication authorities.

OIDC validates signatures using the configured asymmetric algorithm policy and checks identity-token claims, issuer, audience and nonce. Its configured client ID is the only trusted audience; tokens naming additional audiences are rejected. Identity-provider HTTP responses are limited to 1 MiB and a 20-second request budget, with redirects and compression disabled. Login state is expiring, single-use and bound to the initiating browser; exchanges use PKCE. GitHub sign-in uses its configured OAuth endpoints. Automatic linking to an existing account requires both KEEN mailbox verification and provider verification of the email address. Users manage linking addresses through a recently authenticated session and a mailbox proof.

Trusted remote-user authentication is an explicit deployment option. The authenticating proxy must remove client-supplied identity headers, inject its own verified identity, and be the sole route to the API. KEEN accepts a single identity header only from a direct peer in KEEN_SECURITY_TRUSTED_PROXY_CIDRS; HTTP and WebSocket authentication use the same peer check. The supplied server disables Uvicorn proxy-header rewriting so this remains the actual socket peer; custom ASGI launches must also disable that rewriting. Native OIDC takes precedence over remote-user headers. Client addresses used in audit records, sign-in rate limits and agent enrolment network restrictions follow X-Forwarded-For back from the socket peer only while each hop is in KEEN_SECURITY_TRUSTED_PROXY_CIDRS; entries beyond the first untrusted or unparseable hop cannot change the recorded address.

Authorization and browser boundaries

The application applies authentication centrally. Public HTTP exceptions are enumerated by method and complete route pattern. The API documentation and OpenAPI schema require a signed-in session. Inbound webhooks require configured secrets; missing secret configuration fails closed. They apply their rate limit and secret check before revealing whether a connection exists or ingestion is paused. Identical deliveries within the replay window are rejected; a delivery that fails to persist releases its claim so the sender's retry is accepted. Agent and webhook endpoints validate their own credentials; MFA endpoints validate their pending ceremony. Protected writes have a default administrator gate with explicit permission-controlled or self-service exceptions. Routes also enforce the permissions and object ownership appropriate to their data. UI visibility is a convenience; authorization is enforced by the backend. Audit managers create and edit audits and scheduled templates they created. The separate audits.admin permission permits editing other users’ audits and deleting audits or scheduled templates. Sampling also requires read access to the underlying evidence or source. Event records and the aggregates derived from them (statistics, volume charts and source graphs) require events.read; control lists omit evidence counts for other accounts. Membership of an organisation role that owns risks grants read access to those risks, so changing that role's members or deleting it requires risk.manage.

Cookie-authenticated writes require a matching CSRF cookie/header token and reject an unexpected browser Origin. MFA ceremonies require the configured Origin. Notification WebSockets require an allowed Origin and an authenticated identity; connections are limited and authorization is rechecked at intervals of up to 30 seconds. Notification connections release database sessions between checks. A change or revocation can take up to that interval to close an existing notification connection.

Plain text is escaped before HTML insertion. Rich text is reduced to a formatting allowlist on the server and sanitized when rendered. Links and post-login destinations are validated separately from HTML escaping. Same-origin link paths exclude backslashes and control characters, which browsers would otherwise resolve to another host, and imported upstream guidance links must be absolute HTTP(S) URLs. The supplied nginx policy restricts scripts to the installation's own origin and blocks inline scripts, string evaluation and embedded objects. Inline styles remain allowed for the UI. CSV exports neutralise formula-like text before it reaches spreadsheet cells. Download filenames are encoded and stripped of header control characters. Uploaded artifacts are served as downloads; PDF previews are rendered in a resource-limited child process and cached in memory with per-process byte and entry limits. Masked text downloads have a 20 MiB input limit.

Application responses use a no-store cache policy. Production cookies must be Secure and scoped to the installation's host. Keep unrelated or untrusted applications off the same origin, and avoid sharing session cookies across sibling domains.

Outbound connections and secrets

HTTPS connections validate the certificate chain and server hostname. KEEN has no supported option to disable HTTPS certificate verification. Install a suitable trusted CA for private certificates. TLS failure must be resolved through the trust configuration or upstream certificate.

Built-in HTTP collectors validate destination addresses and pin connections to validated addresses while preserving TLS hostname verification. Redirects and ambient proxies are disabled in that transport. Loopback, link-local and other prohibited address classes are rejected, including IPv4-mapped equivalents. KEEN_INGESTION_ALLOWED_CIDRS restricts permitted private destinations; an empty setting permits private networks apart from prohibited addresses. Upstream credentials and cached authentication state belong to the selected connection. Saved credentials are write-only: changing a connection's endpoint, origin, authentication mode or credential header requires its credentials to be supplied again. Custom API connections apply the same rule to the credential header or query parameter name and the OAuth token path.

Custom API collectors additionally require an operator-managed host and port allowlist (host permits HTTPS port 443; host:port permits that explicit port) and explicit private-network CIDRs. Their definitions are declarative; they do not execute supplied Python, shell commands or templates. AWS and Google collectors use their respective SDK transports. Outbound notification webhooks use the built-in validated, pinned HTTPS transport and its private-network restrictions. Operator-configured SSO, SMTP and storage endpoints have separate transport paths and trust requirements. SMTP authentication requires STARTTLS or implicit TLS. Apply network egress policy to constrain the destinations available to the whole deployment.

Credentials saved for integrations are encrypted with KEEN_INTEGRATION_SECRET_KEY. TOTP uses its separate MFA encryption key. These keys live outside the database and must be protected and backed up. Encryption protects a database copy without its keys; a running application with the keys can decrypt the credentials it needs. The shared ingestion path redacts recognised secret keys in structured payloads and text artifacts, and applies secret redaction to event summary fields. The ISMS metric webhook applies the same redaction, and optional event-data masking, to its stored text fields and payload copy. The audit trail omits authorisation codes and state from sign-in callbacks. URL userinfo and recognised secret query/fragment parameters are removed from displayed URLs. Collector diagnostics use bounded, generic messages; SQLAlchemy error output omits bound parameter values. Redaction is best effort: arbitrary prose, unknown key names, secrets embedded in URL paths and binary artifacts may retain sensitive information. Configure additional sensitive keys where needed and avoid placing secrets in event content, labels, URLs or definition fields that appear in evidence and exports.

Agent enrollment profiles use high-entropy shared enrollment keys. KEEN retains credential hashes and issues an individual expiring agent token after enrollment. Profiles support expiry, enrollment limits and network restrictions. Profile revocation also revokes its associated agent credentials. Individual credentials support persistent hosts. Possession of a profile's enrollment key permits enrollment within that profile's restrictions; distribute it through a protected secret channel. A compromised agent credential permits actions scoped to that agent, including evidence submission, until expiry or revocation.

IMAP mail collection

IMAP is disabled by default and unavailable in demo mode. Each named connection selects one account and folder; passwords use the existing encrypted credential store or approved file-managed secret references. A host, port or username change requires saved credentials to be supplied again. The credential destination check uses the same parsed host/port and exact quoted username as collection; username whitespace and trailing slashes remain significant. The transport uses implicit TLS with certificate-chain and original-hostname validation, pins a validated resolved address, rejects loopback/link-local/multicast/reserved destinations and requires explicit KEEN_INGESTION_ALLOWED_CIDRS for private addresses. There is no plaintext, STARTTLS fallback, redirect/referral, proxy or compression mode.

The supplied Compose imap profile runs a dedicated, single-concurrency queue worker with container CPU, memory and PID limits. Scheduled connection order rotates each five-minute period so the same later connections do not repeatedly lose their jobs to expiry under sustained load. Each connection has a server-validated interval of 5–1,440 minutes in five-minute steps, defaulting to five minutes. Poll admission time is persisted under the existing connection run lock before network work; scheduled and manual runs, including failed attempts, respect it. Failure backoff can extend the wait. The dispatcher checks every five minutes, so actual polling is rounded to a subsequent dispatch and depends on queue capacity. The administrator-only, CSRF-protected Ingest runners action queues an expiring dispatcher; HTTP requests perform no IMAP network work. Runtime enablement, demo restrictions and pause state are checked before dispatch and again by collection. Each poll performs network work in a short-lived subprocess with a minimal environment (Python encoding, executable search path and sensitive-key redaction settings), closed inherited file descriptors, a 256 MiB address-space limit, eight CPU seconds, 32 file descriptors and disabled core dumps. Its initial alarm expires after 50 seconds; collection resets the alarm to a 45-second wall-clock budget, followed by a five-second serialisation/output budget. The parent kills and reaps it after 55 seconds. Socket operations also have timeouts within the collection deadline. The independent alarm bounds slow-drip responses that keep buffered reads active. Celery adds 65/75-second soft/hard task limits. These boundaries isolate blocked DNS, TLS, server responses and MIME processing from ordinary ingestion capacity. They are resource controls, not an operating-system sandbox against a compromised Python runtime.

Responses are limited to 4 MiB and 2,048 lines per poll, 32 KiB per line and at most 1 MiB per literal; server-declared literals are checked before reading. The literal allowance is zero during greeting, authentication and metadata commands; each explicit BODY.PEEK request permits exactly one literal, including an empty one, and the allowance is cleared on success or failure. Unsolicited and extra literals are rejected before their payload is read. Each poll searches at most 100 UID positions and imports at most ten messages. Collection also measures each item’s escaped JSON representation against a 5 MiB output budget, reserving envelope space and returning a resumable batch when necessary. The final serialisation and parent process retain independent size checks. Selected headers have a 16 KiB wire limit and 200-line limit; MIME metadata has nesting, token and part-count limits. Partial BODY.PEEK requests select one inline plaintext body, or an HTML body when plaintext is absent. Attachment/named parts and attached-message subtrees are excluded. Multipart/related traverses only its identified root body, excluding sibling resources; an unresolved root is omitted. Malformed MIME parameters and dispositions fail closed. FETCH validation binds UID, body section, zero offset and declared size to the same literal; metadata UIDs must be top-level fields. Contradictory UIDVALIDITY or UIDNEXT response values are rejected; identical repeats are permitted. HTML remains a string in a JSON artifact; it is never parsed, rendered or followed. Body collection defaults off, with a 4,000-character default and 65,536-character maximum when enabled. Transfer decoding uses fixed encodings and a small charset allowlist. Sensitive-key redaction runs before character truncation and again through shared persistence; existing privacy masking applies. Indexed event actor/system labels are capped to their database column lengths after redaction and masking, while the bounded mail headers remain in the payload and artifact. Unterminated quoted values for recognised sensitive keys are redacted through the end of the available input, including whitespace and a trailing escape. Recognised redaction keys are best effort, particularly for incomplete wire data and arbitrary prose.

EXAMINE and BODY.PEEK preserve server-side read flags. Collection never writes flags, expunges or deletes mail. The first poll establishes the current UID baseline, avoiding an automatic historical import. Cursors, deduplication and artifacts are scoped to the connection, endpoint/account/folder and UIDVALIDITY. A changed UIDVALIDITY pauses collection until an administrator creates a reviewed replacement connection. Failures preserve progress and impose exponential backoff. A hostile server can misrepresent mail, attachment metadata, sizes or UIDs; evidence provenance remains subject to review. Headers-only polling does not request or parse BODYSTRUCTURE. Unsupported body charsets and invalid or unsupported transfer encodings omit the body, retain headers with a fixed omission reason and allow the cursor to advance. No raw undecoded body is retained. UID/section/size, transport and MIME-structure failures still stop the poll. Oversized headers or malformed/over-complex MIME structure with body collection enabled can block the current polling window and need operator investigation. Operators control mailbox access, evidence retention and disk capacity; per-poll limits do not cap lifetime storage.

Future IMAP work must retain actual-transport tests for TLS chain/hostname/expiry and credential ordering; DNS rebinding and private-address restrictions; literal limits before allocation; slow/dripping responses, process termination and queue isolation; MIME depth/part/decoding bounds and poison-message recovery; unsupported-charset omission before redaction; runner authorisation/CSRF and dispatch-time pause checks; attachment exclusion; HTML-as-text behaviour; custom redaction and truncation inside quoted secrets; related MIME root selection and malformed dispositions; same-response UID/section/offset/size binding, contradictory mailbox metadata and single-request literal allowances; server header projection into PostgreSQL column bounds after masking; escaped-Unicode output budgets and resumable batches; exact account identity in credential-change guards; read-only/PEEK commands; cursor reset, replay, concurrent polling and storage failures. OAuth, custom trust stores, historical imports, additional encodings or mailbox mutations require separate security review. Maintain Python/OpenSSL and exercise the supplied queue/container limits under PostgreSQL/Valkey integration tests before deployment.

Availability and evidence storage

Request bodies are bounded before parsing, including chunked bodies. JSON request bodies must be standard JSON nested no deeper than 64 levels; non-finite numbers are rejected. PostgreSQL cannot store NUL characters, so request paths and query strings containing them are rejected, while JSON request text, question form text and collected evidence store them as U+FFFD replacement characters; one such record cannot halt a collection run. Validation errors name the invalid fields without echoing submitted values. HTTP collection responses have size limits and network timeouts. Rule matching uses bounded inputs, a per-match execution timeout and a bounded compiled-pattern cache. Event volume charts reject windows needing more than 5,000 buckets before querying. Notification connections and sends are bounded. These controls address individual-request resource abuse; deployment-level connection limits, quotas, worker capacity, monitoring and network rate limiting are needed for sustained or distributed traffic.

Expensive reports, broad collection jobs and large authorised datasets consume resources. PDF resource limits reduce parser exposure; maintain the native parser and isolate the application processes. KEEN does not claim complete protection against denial of service, dependency defects or every memory/resource leak.

Artifacts have recorded SHA-256 checksums and backend-specific storage controls. Files attached to questions stay with their question threads; event views and evidence exports present only collected evidence artifacts. Checksums support integrity verification. Immutability and retention guarantees depend on storage permissions, versioning and retention configuration. Someone controlling both records and storage can alter the evidence and its recorded checksum. Evidence review should account for source trust and provenance.

Installation feature availability

Administrator-only feature settings supplement existing permissions. Disabled features reject their operational API routes, including for administrators. Settings use revision-checked database writes; guarded requests check the authoritative revision before reusing a cached configuration. Feature caches contain no user authority or credentials. Existing records, historical audit material and retention protections remain intact. Availability settings are not per-user confidentiality boundaries; shared references can remain visible through other enabled features. Authentication, account security and administration remain available. Requests and jobs already admitted can finish.

Deployment responsibilities

Use HTTPS for browser, agent and credential-bearing traffic; configure KEEN_PUBLIC_BASE_URL, Secure cookies and trusted proxies correctly. The provided Compose port bindings are local to the host. Keep PostgreSQL, Valkey, worker control interfaces and artifact storage inaccessible to untrusted clients. Use separate strong secrets, restrict upstream account permissions, preserve encryption keys securely, and test backup restoration.

Keep KEEN, locked dependencies, container images, the TLS proxy and native tools maintained. Disable unused source types and authentication methods, restrict private-network egress, and monitor authentication failures, ingestion errors, disk use and resource pressure. Validate configuration changes with the documented test workflow and operational checks appropriate to the installation.

Required security review checklist

Reviewers check these boundaries after every round of changes and before a release. A green suite establishes only the behaviours it exercises. Record each applicable check's evidence, untested prerequisites and remaining limitations in the review or delivery; do not mark an untested boundary as verified. Use synthetic credentials, local network fixtures and disposable databases. Review the final formatted tree and exercise the assembled application, including middleware, rather than relying solely on injected administrator fixtures.

  • Timing and account-discovery oracles: compare existing, absent, inactive and externally authenticated accounts on login, recovery, linking and reauthentication paths. Check response bodies, status codes, lengths and expensive operations as well as timing. Require comparable password-derivation work on rejected local-login paths, constant-time secret comparison and bounded inputs. Rate limiting supplements these controls. Test the work performed deterministically; use repeated, interleaved measurements when claiming timing resistance. Network-backed identity providers have separate timing behaviour.
  • TLS identity and credential ordering: verify trusted matching certificates succeed and trusted wrong-name, untrusted and expired certificates fail for every transport, including LDAPS and StartTLS. Prove that credentials are never bound or sent before verification. Inspect the complete library verification path: a disabled SSLContext hostname flag can coexist with explicit library hostname validation. Keep dependency-version regression tests and test custom CA stores.
  • Inbound ingestion credentials: exercise every provider and connection type with absent, empty, incorrect and correct secrets, missing configuration, duplicate credential headers and legacy environment settings. No operator switch may turn a credential-bearing ingestion endpoint into anonymous ingestion. Preserve signature, replay, connection isolation, body-limit and rate-limit checks.
  • Validation and failure propagation: send wrong magic bytes, misleading MIME types, malformed uploads and hostile filenames through the HTTP boundary. Confirm a rejected attachment rejects the operation without committing its question or post. Exercise read, storage and database failures; broad exception handlers must not turn them into partial success. Review orphaned-object cleanup separately from database rollback. Do not interpolate untrusted filenames or upstream diagnostics into logs.
  • Authorization inventory: compare every mutation's method and full route shape with middleware policy and adapter permissions. Exercise permitted non-admin users, users without the permission, other owners, inactive accounts and unauthenticated callers. Check adjacent paths, extra segments, wrong methods and encoded separators. Permission-owned exceptions remain authenticated and CSRF protected; new routes do not inherit public status.
  • Logout and CSRF: GET and other safe methods must not revoke sessions or mutate account state. Test intentional logout, cross-site GET/POST, missing and wrong CSRF tokens, expired sessions, pending MFA and provider logout. Check Origin against the configured public origin, including proxy deployments. Review cookie scope, sibling-domain cookie injection, duplicate cookies and whether double-submit tokens require session binding under the deployment's threat model. Never assume SameSite alone provides CSRF protection.
  • Proxy identities: require an explicitly trusted direct socket peer and exactly one identity header. Test untrusted peers, duplicate headers, client-supplied forwarding chains, absent headers and malformed addresses. Enforce the same boundary in HTTP and WebSockets. Upstream proxies must strip client identity headers; forwarded client addresses do not establish proxy trust.
  • Resources, storage and previews: bound cache bytes and entry counts, masking buffers, downloads, uploads, parser work, decompression, render concurrency and response bodies. Test real byte counts independently of metadata. Close streams on success, rejection and cancellation. Match media types exactly after parsing parameters. Validate object keys before backend selection, and exercise both local and S3 storage. Keep storage locations and credentials out of presentation APIs. Check cache eviction and response delivery together for races.
  • Administrative lockout and concurrency: evaluate effective administrators after proposed group-role, membership, user-role and active-state changes. Cover multiple inherited administrators in one group, replacement with inactive or explicitly normal users, other surviving administrator groups, and concurrent operations. Verify transaction rollback and session/authorisation invalidation. A sequential lockout guard does not establish a concurrency guarantee.
  • Outbound destination authority: review scheme, hostname, port, resolved addresses, redirects, DNS changes, proxy environment, timeouts and response limits independently. Distinguish app-managed integration destinations from operator-managed notification, SSO and storage destinations. Document any separate trust boundary and test its restrictions; a hostname allowlist alone does not constrain ports. Keep certificate checks enabled for all HTTPS paths and never log secret-bearing URLs.
  • Sessions, MFA and linking: exercise fixation, replay, concurrent refresh/revocation, stale permission snapshots, account deactivation/reactivation and pending-ceremony confusion. Check OIDC issuer, audience, nonce, algorithm, PKCE, single-use browser binding and verified-email linking. Test factor reauthentication and recovery-code reuse through the assembled app.
  • Untrusted data and isolation: exercise SQL/configuration injection, stored and reflected XSS, unsafe links, CSV formulas, regex resource exhaustion and malicious upstream evidence. Trace credentials, caches, cursors, artifacts and redaction through two connections of the same provider. Check audit output, errors, logs and exports for leaks and preserve evidence identity and ownership.

The full validation workflow uses PostgreSQL and Valkey. SQLite, mocked transports and DOM tests are useful focused checks; they do not establish PostgreSQL locking/migrations, real-browser behaviour or resistance to sustained distributed attacks. Missing coverage is an explicit review limitation requiring follow-up, not evidence of safety.

Required review procedure

The checklist above is an inventory of security concerns. Reviewers also follow this procedure to establish which application surfaces and behaviours have been assessed. Apply it to the affected workflows after a change and to the complete installation before a release. A review of individual components does not establish the security of their interactions.

Establish the review scope and attack surface

Record the exact source revision, final working-tree changes, dependency versions, build output and deployment configuration under review. Identify the supported authentication methods, storage backends, source types and optional features that are enabled or assessed. Do not use results from a different revision or configuration as verification of the delivered application.

Build an inventory from the actual code, build and deployment configuration. Include:

  • Browser pages, forms, navigation, static assets, direct and dynamic module dependencies, and native browser actions.
  • Reverse-proxy locations and rewrites, authentication subrequests, public asset exceptions, caching and response-header inheritance, and the externally reachable API paths.
  • API reads and writes, public protocol exceptions, WebSockets, previews, downloads, exports and alternate representations of the same records.
  • Workers, scheduled jobs, collectors, outbound notifications, queues, retries, administrative scripts and migrations.
  • Databases, session stores, credential stores, artifact backends, caches, temporary files, logging and diagnostic outputs.

For each entry, identify its owner, entry points, authentication authority, required permissions or credentials, data handled, dependencies and applicable security properties. Reconcile the inventory with registered routes, forms and imports, worker entry points, source registrations and proxy rules. Explain exclusions. A surface absent from the review record is unreviewed; absence from the existing test suite does not make it out of scope.

Trace complete workflows and sensitive data

For each sensitive workflow, trace an ordinary permitted operation and a realistic unauthorised operation from the external entry point to every durable write and externally visible result. Include the actual browser, proxy, API, service, database, session store, worker and storage boundaries that participate. Mark where identity is established, permissions are checked, untrusted data is validated, secrets are redacted, resource budgets are enforced and transactions commit.

Trace passwords, factor proofs, session identifiers, one-time links, upstream credentials and sensitive evidence through request URLs and bodies, redirects, browser history, referrers, proxy logs, application logs, exceptions, queues, caches, database records, artifacts and exports. Use synthetic recognisable values to detect unexpected retention or disclosure. A shared redaction helper protects only the paths that call it; inspect direct storage writes, bulk operations, snapshots and alternate export paths separately.

Treat reads and transformations as security boundaries as well as mutations. Search results, summaries, counts, relationship APIs, previews and exports must not disclose records that their callers cannot read. Sampling, linking, copying and sharing require separate checks for access to the source and authority over the destination. Record any intentional disclosure through authorised sharing and how that authority is established and revoked.

Define and exercise authorisation rules

Maintain a review matrix for each resource and operation. tools/security_e2e/access_policy.py records the permission rule for every served route; the backend tests fail when a route has no rule, and the security suite checks a deployment against the rules. Distinguish read, create, edit, delete, share, sample, export, administer and ownership transfer where supported. Include anonymous callers, permitted non-administrators, users without the required permission, owners, other users, delegated administrators, inactive users, pending authentication ceremonies and machine identities as applicable.

Define permission implications explicitly. Read access does not establish write access; permission to edit one resource does not establish permission to read or share another. Application-wide administrators, delegated administrators and resource owners have distinct authority. Establish trusted ownership from server-side records, including records created by workers, and reject attempts to choose identity, ownership or permissions through client data.

Exercise these rules through assembled entry points with real authentication and permission resolution. Also check secondary routes and representations of the same object. A hidden button, an injected administrator fixture or a service test alone cannot verify the externally enforced authorisation boundary.

Exercise failures, defaults and state transitions

Test what happens when the application code or a dependency that normally enforces a property is unavailable. Relevant cases include failed or delayed scripts and imports, missing assets, unavailable authentication or rate-limit stores, database errors, interrupted uploads, storage failures, malformed upstream responses, expired credentials, retries, cancellation and process restarts.

Inspect native browser, framework, library, proxy and SDK defaults wherever application code normally overrides them. For credential-bearing forms, verify their initial HTML, native method and destination, serialised fields, implicit Enter-key submission, and behaviour before handlers attach or when scripts fail. Credentials and factor proofs must not enter URLs or unsafe fallback requests. Verify that unauthenticated workflows can load their complete required asset graph with the expected status codes, media types and effective security headers.

Test state transitions, not only steady states: sign-in and sign-out, pending-to-authenticated ceremonies, permission changes, account deactivation and reactivation, credential rotation and revocation, ownership changes, source removal, audit completion and reopening, retention and deletion. Exercise duplicate delivery, replay, out-of-order operations and concurrent changes where they can affect authority, confidentiality, integrity or availability. Distinguish operations that are intentionally allowed to finish in flight from subsequent operations that must be rejected.

For rejected operations, check the absence of unintended side effects as well as the response. Examine database rows, stored objects, queues, notifications, cached authority and cleanup work. A rollback of one system does not prove rollback of another.

Verify the enforcing deployment surface

Use a disposable installation and synthetic data. Verify each security property at the surface that enforces it:

Property Required evidence
Browser form behaviour, module loading, navigation, cookies and CSP Real-browser checks through the supplied UI build and nginx configuration, including failed-script cases and unauthenticated access
Proxy path handling, static asset access and effective response headers Requests through the actual proxy configuration, including rewrites, error responses and nested locations
API authentication, authorisation, CSRF and body limits Requests through the assembled application with authentication, middleware and permission resolution active
Locking, concurrent revocation, migrations and transaction guarantees Disposable PostgreSQL with the supported migrations and representative concurrent operations
Session-store atomicity, expiry and service failures Valkey with representative configuration and controlled failure cases
Destination restrictions, TLS verification and credential ordering Local network and certificate fixtures exercising the actual transport and supported dependency versions
Artifact persistence, downloads, immutability and cleanup Representative local and S3 storage paths, including actual-byte checks and controlled failures

tools/security_e2e exercises the browser, proxy, API and PostgreSQL concurrency rows above against a running installation, using synthetic accounts and data; see its README before use. Static inspection, SQLite, DOM tests and mocks provide useful evidence within their limits. Identify those limits explicitly. An inventory or static configuration assertion does not replace a browser or proxy test; a mocked transport does not establish its library's real network behaviour. Do not infer that an untested deployment surface is safe because a substitute test passed.

Record evidence and change impact

For each applicable checklist item and workflow, retain a review record containing:

  • The surface and security property being assessed, attacker authority and prerequisites, expected permitted behaviour and expected rejection or containment.
  • The exact revision, configuration and dependencies, inspection or test performed, reproducible test reference and observed result.
  • The evidence level: static inspection, isolated or mocked test, assembled application, or representative deployment integration.
  • Status: verified at the stated evidence level, failed, unverified, or not applicable with justification.
  • Untested prerequisites, supported configurations still requiring assessment, known limitations and required follow-up.

Test totals and coverage percentages supplement this record; they do not establish that every entry point, failure mode or security property has been tested. Do not describe a review as complete when applicable required evidence is missing.

For each change, identify the workflows and security properties it can affect. Trace callers and consumers of shared helpers, middleware, dependencies, proxy rules, schemas, build tooling and vendored assets. Include generated output and indirect consumers such as module imports, exports and worker tasks. A small shared change can affect a workflow whose own source files were not edited. Reassess the relevant boundaries on the final formatted and built tree.

Retain regression coverage and enforce release checks

A confirmed issue requires a reproducible regression demonstrating the realistic prerequisite, the previously unsafe behaviour and the expected rejection or containment. Verify permitted behaviour as well. Run the test against the vulnerable version where practical to establish that it detects the defect. Inspect neighbouring entry points for the same underlying failure and add coverage for materially distinct paths.

Keep regressions in the ordinary test workflow. Where safe and practical, deliberately remove or bypass a guard in an isolated copy to check that its regression fails. A test that remains green without the guard does not demonstrate that boundary. Keep browser/proxy, PostgreSQL and Valkey integration checks in the release validation workflow rather than relying on reviewers to remember them.

Before a release, reconcile the complete attack-surface inventory, authorisation matrix and evidence record with the final build and supported deployment configuration. An applicable critical security property without its required evidence remains an unmet release check. Failed checks require resolution or a documented change to supported scope that removes the affected exposure. A skipped test, unavailable environment or broad disclaimer does not satisfy the check. Record the release decision and its supporting evidence outside this policy document.

Security claims and review records

The security-model sections describe current supported behaviour and realistic risks. Each security claim must identify its enforcing code or configuration and supporting test or other verification evidence in the review record. Where a supported configuration lacks evidence, record the gap and assess whether the claim needs qualification. Review requirements describe obligations; their presence does not assert that the required validation has been performed.

Keep findings, remediation history, per-review results, exceptions and release decisions in the corresponding review or delivery records. Do not turn this document into a cumulative list of fixes or describe an intended future control as current protection.

There aren't any published security advisories