We actively support and provide security updates for the following versions of LibreDB Studio:
| Version | Supported |
|---|---|
| 0.10.x | ✅ |
| < 0.10.0 | ❌ |
Note: We recommend always using the latest version to ensure you have the most recent security patches.
We take the security of LibreDB Studio seriously. If you believe you have found a security vulnerability, please report it to us as described below.
Please do not report security vulnerabilities through public GitHub issues.
Instead, please report them via email to:
Email: info@sekoya.tech
Reports are handled by Sekoya Grup Bilişim ve Teknoloji Ltd. Şti., the maintainer of LibreDB Studio.
When reporting a security vulnerability, please include the following information:
- Type of issue (e.g., buffer overflow, SQL injection, cross-site scripting, etc.)
- Full paths of source file(s) related to the manifestation of the issue
- The location of the affected source code (tag/branch/commit or direct URL)
- Step-by-step instructions to reproduce the issue
- Proof-of-concept or exploit code (if possible)
- Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
- Acknowledgment: We will acknowledge receipt of your vulnerability report within 48 hours
- Initial Assessment: We will provide an initial assessment within 7 days
- Updates: We will keep you informed of our progress every 7-10 days
- Resolution: We will work with you to understand and resolve the issue quickly
- We will coordinate with you to fix the vulnerability before public disclosure
- We will credit you for the discovery (unless you prefer to remain anonymous)
- We will not take legal action against security researchers who:
- Act in good faith
- Do not access more data than necessary
- Do not modify or delete data
- Do not disrupt our services
- Report vulnerabilities promptly
When using LibreDB Studio, please follow these security best practices:
- Keep Updated: Always use the latest version of LibreDB Studio
- Secure Credentials: Use strong passwords for
ADMIN_PASSWORDandUSER_PASSWORD - JWT Secret: Generate a secure
JWT_SECRETusingopenssl rand -base64 32 - Environment Variables: Never commit
.env.localor.envfiles to version control - Network Security: Deploy behind a firewall or VPN when accessing production databases
- Database Access: Use read-only database users when possible
- HTTPS: Always use HTTPS in production environments
- API Keys: Store LLM API keys securely and rotate them regularly
- LibreDB Studio uses JWT-based authentication
- Credentials for the built-in local login are supplied through the
ADMIN_PASSWORDandUSER_PASSWORDenvironment variables and are compared directly; they are not stored in a database and are not hashed. Protect them the way you protect any other server environment variable, or use the OIDC provider instead. - Session tokens (the JWT and its cookie) have a fixed 24-hour lifetime; this is not configurable today
- Connection details, including database passwords and SSH private keys, are stored unencrypted
in browser
localStorage. Treat that browser profile as secret material: anyone who can read it can read every configured credential. This is deliberate — it is what allows Studio to work without a master password — and it is why cross-site scripting is treated as a top-severity issue in this project. - In the server-side store (
STORAGE_PROVIDER=sqliteorpostgres), those same fields are encrypted at rest with AES-256-GCM before they are written: the database password, the connection string, the TLS client key, and the SSH password, private key and passphrase. The key isSTORAGE_ENCRYPTION_KEYwhen set, and is otherwise derived fromJWT_SECRET, so there is no new required configuration. Host, port, user and database name stay readable so an operator can still identify what a dump contains. A leaked database file or backup is therefore not by itself enough to read the credentials — but anyone who can read the server's environment still can. Rotating the key makes stored credentials unreadable; see docs/STORAGE.md. - Database credentials are not written to application logs, but they are returned in plaintext
to the authenticated owner through storage API responses (for example
GET /api/storage), because the app must be able to redisplay a saved connection's password for editing and reuse - Connection pooling is used to prevent connection exhaustion
- All API endpoints require authentication except
POST /api/auth/login,POST /api/auth/logout,GET /api/auth/oidc/login,GET /api/auth/oidc/callback,GET /api/db/health(for load balancer probes), andGET /api/storage/config(which returns the storage mode only).GET /api/auth/meandPOST /api/db/healtheach check for a session themselves and return 401 without one, the same as every other endpoint. - Running arbitrary SQL against your connected database is the product's purpose, not an injection surface. Where the application builds SQL of its own — generated starter queries, schema browsing, inline cell edits — values are bound as parameters, and identifiers the application knows from schema metadata, column names and table names alike, are quoted for the target dialect, since SQL has no parameter syntax for identifiers. The one identifier it infers rather than knows — the table name behind an inline cell edit, read from a tab title or guessed from the query text — is validated as a bare identifier instead, and the edit is refused when it is not, because a guess cannot be safely quoted
- Login attempts, the AI endpoints and every database-reaching route (query execution, schema
browsing, maintenance operations, and the admin fleet-health check) are rate limited in the
application. The counters live in the application process, so with more than one replica the
limits apply per replica; multi-replica deployments should enforce the same budgets at the
ingress as well (see
charts/libredb-studio/README.md). The budgets are configurable through theRATE_LIMIT_*environment variables documented in.env.example - Every state-changing request (
POST,PUT,PATCH,DELETE) must carry anOrigin(orReferer) whose host matches the deployment's own host, as a second layer behind theSameSite=Laxsession cookie — except a request whoseContent-Typeis exactlyapplication/jsonand carries neither header at all, the one shape a cross-site browser cannot forge; seedocs/API_DOCS.md's "CSRF: Origin Check" section for the full carve-out and why it is safe. A non-browser client that does NOT send that content type (a webhook sender, for instance) must sendOrigin: <your public origin>instead — that requirement is on clients which skip the JSON carve-out, not on every non-browser client unconditionally. Deployments behind a reverse proxy that rewritesHostsetALLOWED_ORIGINS - Every response that passes through the app's request middleware carries
Content-Security-Policy,Strict-Transport-Security,X-Content-Type-Options,X-Frame-Options,Referrer-PolicyandPermissions-Policy(static assets and the storage-config bootstrap path are excluded from the middleware and carry none of these — seedocs/BACKLOG.md)./api/db/healthis NOT excluded: itsPOSThandler is a session-gated, provider-reaching route like any other, so it goes through the same Origin check and carries the same headers;GET /api/db/healthis unaffected because the Origin check exempts GET by method and the load-balancer/probe path stays a public route. The CSP permits inline scripts, because the application is statically prerendered and its hydration scripts are inline and nonce-less; what it does contain is where an injected script could send data, not whether one can run. The CSP is enforced by default; if an upgrade breaks a blocked resource, setCSP_REPORT_ONLY=true(no rebuild required — it is a runtime environment variable) to downgrade it to report-only while you identify the violated directive - Logins, logouts, missing-session denials, Origin-mismatch denials and rate-limit trips are
written to the in-app audit log and emitted as one structured JSON line each on stdout, for
whatever log pipeline you already run. A denial based on ROLE rather than session or Origin — the
proxy's
/adminredirect for a non-admin token, and the in-handler admin-only checks onadmin/audit,admin/fleet-healthanddb/maintenance— is not audited today; seedocs/BACKLOG.md. A failed login records the submitted email verbatim (truncated to 254 characters) as the event's actor, not just a real, known account: a user who mistypes their password into the email field puts that password on a retained audit log line
- API keys are stored server-side only
- User queries sent to LLM providers may be logged by the provider
- Consider privacy implications when using cloud-based LLM services
- Dependencies are scanned on every pull request against the npm, Go and Rust
inputs that actually build the shipped artefacts:
bun.lock, the Windows launcher'sgo.mod, and the desktop shell'sCargo.lock. Findings appear in the run's job summary and, for branches in this repository, in the GitHub Security tab - The scan fails only for a CRITICAL advisory that has a fixed version
available and is not covered by an unexpired entry in
.trivyignore.yaml, and only outside pull requests. Findings with no available fix are reported and never gate: the runtime container image inherits Debian package advisories for which no fixed package exists, and a gate over those would be permanently red without making anyone safer - Every suppression in
.trivyignore.yamlcarries a written justification and an expiry date. An expired suppression is re-reported, so a decision to accept a risk has to be made again rather than inherited - The published container image is scanned daily and its findings are published to the Security tab. Most OS-package findings in any Debian-based image have no fixed package available at the time they appear; the ones that do are taken by bumping the base image
- Every commit is scanned for credentials. The full history was swept once and
classified: 24 matches across 753 commits, every one of them a fabricated test
fixture, a documented example password or UI placeholder copy. No credential
has ever been committed to this repository, and none has been rotated for that
reason. The classification is
.gitleaks.toml, and the full sweep runs again on every push tomainand daily - The production build type-checks.
next.config.tssets notypescript.ignoreBuildErrors, so a type error fails the build rather than shipping
Every release carries libredb-studio-<version>.cdx.json, a CycloneDX 1.7 SBOM
of the production dependency closure, attached as a release asset and signed with
a GitHub build-provenance attestation. It covers all three ecosystems the release
is built from — npm, Go and Rust — and therefore describes the dependency
closure of the npm package, the standalone tarballs, the Windows zip, the
.deb and .rpm packages, the snap, the AppImage and the desktop package alike.
It does not describe the pinned Node.js runtime that packaging/linux/fetch-node.sh
and packaging/windows/fetch-node.sh download and bundle into every one of those
artefacts except the npm package. That runtime is the largest single binary in
most of them, it is fetched by a shell script rather than resolved from a
lockfile, and the SBOM's only node-named component is pkg:npm/@types/node, a
type-declarations package with no relationship to the runtime that actually
ships. This is a known gap, tracked in docs/BACKLOG.md.
Verify it:
gh attestation verify libredb-studio-<version>.cdx.json --repo libredb/libredb-studioThe container image is not covered by that document, because the image is built
after the release is published and this project's releases are immutable. Its
SBOM is generated daily from the published image, and you can regenerate it
yourself from any digest at any time. A tag such as :0.9.67 is mutable - it
resolves to whatever manifest it currently points at - so resolve it to the
immutable digest first:
digest=$(docker buildx imagetools inspect ghcr.io/libredb/libredb-studio:0.9.67 --format '{{json .Manifest.Digest}}' | tr -d '"')
trivy image --format cyclonedx --scanners license \
--output libredb-studio-image.cdx.json \
"ghcr.io/libredb/libredb-studio@$digest"Security updates will be released as:
- Patch releases (e.g., 0.5.4 → 0.5.5) for critical security fixes
- Minor releases (e.g., 0.5.x → 0.6.0) for security improvements
- Security advisories will be published in the GitHub Security tab
If you are conducting a security audit or penetration test, please contact us in advance at info@sekoya.tech so we can coordinate and ensure your testing does not impact other users.
Thank you for helping keep LibreDB Studio and our users safe!