FerrVault
FeaturesPricingChangelogDocs
ENFR
Waitlist→

Legal document

Security — FerrVault

Last updated: May 27, 2026

FerrVault inherits the baseline controls of the FerrLabs platform — see ferrlabs.com/security for identity, infrastructure, vulnerability disclosure, and breach notification. The addenda below cover what is specific to a secrets manager: how a secret value flows from your CLI or pod, through TLS, into envelope-encrypted storage, and back out under audit.

  <h2>Transport</h2>
  <ul>
    <li>
      <strong>TLS 1.2+ only.</strong> Mozilla "Modern" profile pinned at the edge: AEAD ciphers
      only (AES-GCM, ChaCha20-Poly1305), ECDH P-384/P-521, SNI strict.
      <a href="https://www.ssllabs.com/ssltest/analyze.html?d=api.ferrvault.com&amp;latest"
        >SSL Labs grade A+</a
      >
      at the time of writing.
    </li>
    <li>
      <strong>Post-quantum hybrid key exchange.</strong>
      <code>X25519MLKEM768</code> negotiated with capable clients (Chrome 124+, Firefox 132+,
      Edge 124+). Defends against "harvest now, decrypt later" attacks where an adversary records
      ciphertext today and tries to decrypt it with a future quantum computer.
    </li>
    <li>
      <strong>HSTS preload</strong> on every FerrVault subdomain:
      <code>max-age=31536000; includeSubDomains; preload</code>, plus a 301 redirect on
      <code>www.ferrvault.com</code>. Submitted to the Chrome / Firefox / Edge preload lists.
    </li>
    <li><strong>DNS CAA</strong> pins certificate issuance to Let's Encrypt only.</li>
    <li>
      <strong>No response compression</strong> on reveal endpoints. Removes the theoretical
      BREACH side channel.
    </li>
    <li>
      <strong>HTTP→HTTPS</strong> permanent redirect; the HTTP entrypoint serves no real content.
    </li>
  </ul>

  <h2>Storage and encryption</h2>
  <ul>
    <li>
      <strong>Envelope encryption.</strong> Each secret version is encrypted with a unique
      256-bit Data Encryption Key (DEK) generated by a CSPRNG. The DEK is itself wrapped by a
      per-vault Key Encryption Key (KEK) held outside the application database. We never write a
      DEK to disk in plaintext, and we never write a KEK at all — only the KMS identifier.
    </li>
    <li>
      <strong>AES-256-GCM</strong> with a 96-bit random nonce per write. No (key, nonce) pair is
      ever reused: each rotation mints a new DEK.
    </li>
    <li>
      <strong>KMS-backed KEK.</strong> Production runs against HashiCorp Vault Transit; LocalKMS
      is explicitly refused at startup when <code>FERRVAULT_ENVIRONMENT=production</code>.
    </li>
    <li>
      <strong>KEK rotation</strong> is auditable; rotation events emit a dedicated
      <code>kek.rotated</code> entry.
    </li>
    <li>At-rest disk encryption on the underlying Postgres volume.</li>
  </ul>

  <h2>Authorization and isolation</h2>
  <ul>
    <li>
      <strong>Three-level hierarchy:</strong> Vault → Environment → Secret. A staging operator
      token literally cannot decrypt production ciphertext — the SAT is bound to one
      <code>(vault, environment, role)</code> tuple and every SQL query scopes by
      <code>environment_id</code>.
    </li>
    <li>
      <strong>RBAC roles:</strong> Viewer (read), Writer (read + rotate), Admin (full + grants).
    </li>
    <li>
      <strong>Per-SAT IP allowlist.</strong> Each service-account token can declare a list of
      CIDRs / IP literals (v4 + v6) outside of which it is rejected with 403.
    </li>
    <li>
      <strong>Per-SAT + per-IP rate limit.</strong> 60 requests / minute on each axis;
      forged-token floods cannot bypass via random bearer rotation.
    </li>
    <li>
      SAT tokens are hashed with <strong>Argon2id</strong> at rest; lookup uses an indexed
      SHA-256 hash so verification stays O(1) under load.
    </li>
    <li>
      JWT auth uses <strong>ed25519</strong> signatures issued by the central FerrLabs identity
      provider.
    </li>
  </ul>

  <h2>Audit</h2>
  <ul>
    <li>
      <strong>Every read of a secret value is logged</strong> — that is the product, and a failed
      audit insert blocks the read. The plaintext does not leave the process if the trail cannot
      be persisted.
    </li>
    <li>
      Every write, version restore, grant change, KEK rotation, and environment lifecycle event
      is also recorded.
    </li>
    <li>
      Audit rows carry the org, the vault, the secret, the actor (user JWT or SAT id), the
      action, and structured metadata. Reachable via the API and the web UI under the
      <em>Audit</em> tab of each vault.
    </li>
    <li>Tamper-evident audit (append-only hash chain + immutable export) is on the roadmap.</li>
  </ul>

  <h2>HTTP security headers</h2>
  <p>
    Applied to every <code>api.ferrvault.com</code>, <code>app.ferrvault.com</code>,
    <code>auth.ferrvault.com</code>, and <code>ferrvault.com</code> response:
  </p>
  <ul>
    <li><code>Strict-Transport-Security: max-age=31536000; includeSubDomains; preload</code></li>
    <li>
      <code>Content-Security-Policy: frame-ancestors 'none'</code> +
      <code>X-Frame-Options: DENY</code>
    </li>
    <li><code>X-Content-Type-Options: nosniff</code></li>
    <li><code>Referrer-Policy: strict-origin-when-cross-origin</code></li>
    <li><code>Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()</code></li>
    <li><code>Cross-Origin-Opener-Policy: same-origin</code></li>
    <li><code>X-DNS-Prefetch-Control: off</code></li>
  </ul>

  <h2>Operational hardening</h2>
  <ul>
    <li>Hard cap on inbound request bodies (1 MiB) — kills OOM attempts via arbitrary-sized POSTs.</li>
    <li>SQL fully parameterised; no user input ever concatenated into a query string.</li>
    <li>
      <code>ON DELETE CASCADE</code> on every vault-scoped foreign key — no dangling references
      after vault or environment removal.
    </li>
    <li>
      Database errors are mapped to a generic <code>"Database error occurred"</code> body; the
      full error is logged server-side only.
    </li>
    <li>
      Container images pulled from <code>ghcr.io/ferrlabs/*</code>; pods run non-root with
      read-only root filesystem.
    </li>
  </ul>

  <h2>What we do not claim</h2>
  <ul>
    <li>
      FerrVault is <strong>not yet SOC 2 / ISO 27001 certified.</strong> Tracked on the FerrLabs
      compliance roadmap.
    </li>
    <li>
      No external penetration test has been performed yet. The controls above are the result of
      internal review.
    </li>
    <li>
      Mutual TLS for operator → API and append-only hash-chained audit are on the roadmap but not
      shipped today.
    </li>
  </ul>

  <h2>Contact</h2>
  <p>
    <a href="mailto:security@ferrlabs.com">security&#64;ferrlabs.com</a> ·
    <a href="https://ferrlabs.com/security">FerrLabs platform security →</a>
  </p>
FerrVault

Secrets management for product teams. EU-hosted, audited.

← Back to ferrlabs.com
Products
  • FerrVault
  • FerrFlow
  • FerrTrack
  • FerrGrowth
  • FerrFleet
  • FerrLens
Resources
  • Changelog
  • Docs
  • RSS
  • GitHub
Legal
  • Legal notice
  • Privacy
  • Terms
  • Cookies
  • DPA
  • Subprocessors
  • Security
© 2026 FerrLabs. FerrVault is a FerrLabs product.Set in Fraunces · Hand-built in Lille, FR