Security is part of minddy's release contract. This document describes the public security model, supported versions, disclosure path, and controls that self-hosters must configure.
Security fixes target the latest stable release and main. Older releases may
receive a backport when the maintainers judge it safe and practical. Upgrade to
the latest stable release before requesting support for an older version.
Minddy uses Supabase Auth. The server validates the authenticated user before authorizing project or account operations. Important properties include:
- redirect targets are normalized and restricted to the configured origin;
- authentication errors do not disclose whether an account exists;
- password reset, email change, account deletion, and other sensitive actions require a recent authentication state where applicable;
- MFA factors are enrolled and challenged through server-validated flows;
- active sessions can be listed and revoked;
- session cookies use production-safe attributes through
@supabase/ssr; - rate limits apply to password verification and other abuse-prone paths.
The UI password rules are defined in lib/password-policy.ts. The Supabase
project remains the enforcement authority and must not use weaker settings.
Most server writes use a service-role client after explicit authorization in TypeScript. Row Level Security (RLS) is the independent second line of defense for direct PostgREST access.
The current schema is defined by
supabase/migrations/20270106090000_baseline.sql
and later migrations. Security invariants include:
- every application table enables RLS;
- policies target the minimum required roles and enforce project membership or ownership;
- cross-project identifiers are reauthorized before service-role access;
- project ownership columns cannot be moved between tenants through a client update;
- author identifiers are bound to the authenticated caller;
- privileged
SECURITY DEFINERfunctions use a safesearch_pathand revoke execution from unintended roles; - sensitive columns are protected with column privileges in addition to RLS;
- destructive operations use server-side authorization and storage cleanup.
Release validation includes schema guardrails and negative multi-tenant probes. See docs/security-model.md for the threat model and docs/security-release-checklist.md for the promotion gate.
Reversible application secrets are encrypted before storage with AES-256-GCM and purpose-specific keys. Examples include Git provider tokens, BYOK AI keys, webhook secrets, and feedback-board SSO secrets. One-way API credentials and OAuth grants are stored as hashes when the original value is not needed.
Secrets must never appear in client bundles, URLs, captures, logs, release
artifacts, test fixtures, or security reports. Only explicitly public values may
use a NEXT_PUBLIC_-style exposure. Operators must provide independent
encryption keys through protected runtime configuration.
The publication barrier scans the current tree and all publishable Git history. CI also runs a maintained secret scanner with fully redacted output.
next.config.mjs applies the production browser-header policy:
| Header | Required behavior |
|---|---|
Strict-Transport-Security |
At least one year with includeSubDomains |
X-Content-Type-Options |
nosniff |
X-Frame-Options |
Deny framing |
Referrer-Policy |
Restrict cross-origin referrer detail |
Permissions-Policy |
Disable unused sensors and limit microphone access |
Content-Security-Policy |
Restrict framing, base URLs, and form targets |
The OAuth authorization page has a documented form-action exception because
the protocol redirects a form response to the registered client URI. The
exception does not relax frame or base-URI protection.
Self-hosted operators must terminate HTTPS correctly, redirect HTTP to HTTPS, and keep internal database and runner ports off the public network.
Storage buckets are reconciled by
scripts/reconcile-storage-buckets.mjs
and protected by the baseline and later migrations.
- Private attachments use signed, short-lived access paths.
- Public project icons are constrained by content type and size.
- Server-side checks enforce ownership, project access, and object-key shape.
- Active content is not served as executable content.
- Deletion removes both metadata and the corresponding storage object.
Operators must back up database state and object bytes together.
API routes validate runtime schemas, identifiers, sizes, and allowed values. Cookie-authenticated state changes verify the request origin or an equivalent anti-CSRF signal. Public webhook and OAuth endpoints use protocol-specific signatures, state, redirect-URI validation, and replay controls.
Outgoing fetches restrict protocols, redirects, hostnames, and private network targets. Error responses must not reveal credentials, internal paths, SQL, or stack traces.
Abuse-prone authentication, feedback, AI, import, webhook, and integration operations use scoped rate limits. Self-hosted operators must configure the shared rate-limit store required by their topology; in-memory limits are not a multi-instance substitute.
Public feedback, shared pages, OAuth discovery, health, and version endpoints return only their documented projections. Public visibility does not imply access to team-only comments, internal identities, private attachments, billing data, or service credentials.
Server-side agents run in isolated sandboxes with a restricted environment and network policy. The sandbox receives only the scoped material required for a run. It does not receive the host Docker socket, private Supabase network, or unrelated service credentials.
Repository code is untrusted input. Agent execution, dependency installation, tests, and build scripts must run away from operator credentials. See CONTRIBUTING.md.
The repository provides:
- schema and authorization tests in the normal test suite;
scripts/security-probe.mjsfor fixture-driven live boundary checks;npm run check:public-repofor current and reachable-history publication checks;npm run check:public-repo:remotefor a fresh canonical mirror, including server-advertised pull-request refs;- dependency, container, workflow-pin, and release-policy gates;
- the mandatory release checklist in docs/security-release-checklist.md.
Run live probes only against an authorized disposable or staging environment. Never put real credentials or personal data in probe output.
The live authorization probe requires a dedicated synthetic fixture with two users and separate projects. It deliberately uses public/session credentials; never provide a service-role key. Configure these variables in the restricted probe environment:
SUPABASE_URL
SUPABASE_ANON_KEY
SECURITY_PROBE_CROSS_TENANT_TOKEN
SECURITY_PROBE_DUAL_MEMBER_TOKEN
SECURITY_PROBE_SOURCE_PROJECT_ID
SECURITY_PROBE_FOREIGN_PROJECT_ID
SECURITY_PROBE_FOREIGN_ISSUE_ID
SECURITY_PROBE_REASSIGNABLE_ISSUE_ID
SECURITY_PROBE_HARD_DELETE_PAGE_ID
SECURITY_PROBE_SECRET_CONNECTION_ID
SECURITY_PROBE_FOREIGN_STORAGE_PATH
After provisioning only synthetic records, run:
node scripts/security-probe.mjs --confirmIf a vulnerability may be active:
- Preserve minimal, access-restricted evidence.
- Revoke or rotate affected credentials immediately.
- Contain the exposed route, integration, or deployment.
- Determine affected versions, tenants, and data without copying unnecessary personal information.
- Prepare and verify a fix, migration, and rollback path.
- Coordinate disclosure and notify affected users when required.
- Record follow-up controls without publishing exploit details prematurely.
Do not open a public issue or Discussion.
Use GitHub's private vulnerability reporting on the repository's Security tab. If that option is unavailable, email hello@minddy.app. Encrypt highly sensitive evidence or ask for a secure transfer channel before sending it.
Include the affected version, impact, reproduction conditions, and the minimum evidence needed to validate the report. Do not include real user data unless it is essential and you are authorized to share it.
We will acknowledge the report, agree on a correction and disclosure schedule, and credit the reporter if requested and safe.