Security & Trust Center
Method: every claim on this page is checked directly against the current codebase or a committed repo document — the same rule this project uses for its E-Invoicing Coverage Matrix. Where INVOX has not built something yet, or has only a partial version of it, this page says so in plain prose instead of a vague marketing phrase. INVOX holds no third-party security certification (no SOC 2, no ISO 27001, no PCI-DSS) today. Anything below that sounds like a certification is a description of an internal architecture or process, not an audited certification — do not read it as one.
Access Control (RBAC)
The customer portal has three roles — admin, user, viewer — enforced by a fail-closed permission table (ROLE_PERMISSIONS in invox/db/auth_db.py): an unrecognized role or permission always resolves to "denied," never "allowed." Named permissions today cover managing users, managing/viewing API keys, and managing the account. This is portal-user RBAC — a separate mechanism, described next, scopes what an individual API key can do.
API Key Scopes
Sub-API keys (customer-issued keys scoped under a tenant's master key) carry one or more of 14 named scopes — validate, evidence, explain, autofix, mail, read_only, keys, transmit, extract, report, billing, generate, convert, webhooks — enforced centrally by a default-deny middleware (invox/api/scope_guard.py): every authenticated /v1 route is either mapped to a required scope or explicitly marked open, so a newly added route can never ship silently unscoped. A sub-key missing a scope gets a distinct INSUFFICIENT_SCOPE error rather than a generic "unauthorized," so integrators can tell "wrong key" from "right key, missing permission."
Key rotation: master and sub-keys do not currently support in-place rotation (issue-a-new-secret-under-the-same-id). Today, rotating a compromised or expiring key means revoking it and issuing a new one. Treat this as not yet implemented, not as an existing capability.
IP Allowlisting
Two independent mechanisms, with different scope — do not conflate them:
list of IPs/CIDR ranges (invox/tenant/keys.py). An update to a key's allowlist is validated at write time and rejects the first malformed entry outright, rather than silently ignoring it. This applies to customer sub-keys only — a tenant's master key is not IP-restricted by this mechanism.
to a configured IP allowlist, checked before admin token/TOTP auth (invox/api/hardening.py). This protects INVOX's internal operator surface, not customer traffic.
- Per-customer-key allowlist: each sub-API key can restrict itself to a
- Admin allowlist: INVOX's own backoffice admin surface can be restricted
Authentication: OIDC, SAML, SSO
These are two separate systems solving different problems — INVOX does not have one unified "enterprise SSO" story, and this page will not describe it as one:
fail-closed (every route built on it returns 503 unless every required env var is configured). This is for INVOX's own backoffice staff logging into INVOX's internal admin surface. It is not customer-facing and does not grant or replace any customer permission.
invox/api/routes/saml_portal.py) — customer-portal SSO: a tenant registers its own enterprise IdP (Okta, Azure AD/Entra, etc.) via POST /v1/auth/saml/config, and that tenant's end users can then log into the INVOX customer portal through it. Signature verification and XML processing are delegated entirely to python3-saml (backed by the xmlsec1 C library) — this is deliberate: hand-rolled SAML signature-verification code is exactly the kind of security-critical logic this project does not reinvent. Explicit limits, stated plainly: SP-initiated login only — there is no IdP-initiated flow. No SCIM** — user provisioning/deprovisioning from the IdP is not automated; INVOX does not auto-deactivate a portal user because an IdP removed them.
- OIDC (
invox/governance/admin_sso.py) — Authorization Code + PKCE, - **SAML (
invox/governance/portal_saml.py, routes in
Audit Logs
Be precise about what this is. GET /v1/audit/verify and GET /v1/audit/report (invox/api/routes/audit.py) let an external party — an auditor, a certification body, or a customer — confirm that INVOX's internal tamper-evident hash chain is intact, and learn which standards/engine/rule versions produced a given verdict. This is content-free by construction: the chain stores hashes, counters, and version strings, never invoice content or personal data. This is not a full user-activity audit log (a chronological "who clicked what, when" feed for a tenant's own portal users) — INVOX does not currently expose one. If you need per-user activity history for your own compliance program, ask; today it does not exist as a customer-facing feature.
Zero-Retention (Data Handling)
This is one of INVOX's more distinctive, verifiable architectural properties, documented end-to-end in ZERO_RETENTION_AUDIT.md (repo root, result: PASS as of its last run). Concretely, invoice bytes are:
reference dropped before the response returns (invox/api/routes/validate.py);
retained (invox/evidence/store.py, manifest field retention_status: "no_content_retained");
document hash, format, violation count and key prefix (invox/evidence/chain.py);
that is hashed before storage, never kept as plain field values (invox/dedup/detector.py).
- read once at the HTTP layer, wiped (
wipe_document()) and the local - never passed to the evidence store as raw bytes — only a SHA-256 hash is
- never included in the audit chain, which stores only a request id, a
- reduced to invoice-number + issuer-name for duplicate detection, and even
Country-pack validators (e.g. invox/country_packs/de.py) parse the submitted bytes to an in-memory XML tree and discard the byte buffer once parsed. Company tax identifiers supplied at registration (VAT-ID / Steuernummer) are stored as a salted one-way hash, never in raw form.
Encryption
TLS: INVOX's own container/API layer is designed to run behind a TLS- terminating proxy (SECURITY.md's OWASP ASVS table marks this "TLS-ready" — the Dockerfile itself exposes plain HTTP internally, with TLS termination expected at the reverse-proxy/load-balancer layer, per DEPLOYMENT_EU.md). At-rest encryption of the underlying disk/volume is an infrastructure-level (hosting-provider) property rather than something enforced by INVOX application code — we are not going to claim a specific at-rest cipher or key-management scheme here without a concrete, independently checkable implementation to point to. If your compliance process requires attestation of a specific encryption-at-rest mechanism, ask — this page will be updated once one is verifiable in the codebase, not before.
Webhooks: Signing, Replay, SSRF, Circuit-Breaking
Outbound webhook delivery (invox/webhooks/sender.py) is HMAC-SHA256 signed — every delivery carries X-Invox-Signature: sha256=<hex digest>, verified with a constant-time comparison on the receiving side. Delivery is also protected by:
embedded credentials in the URL, and — because a hostname that resolves to a public IP at registration time can be re-pointed at a private IP later (DNS rebinding) — every DNS answer is re-checked immediately before each delivery, not just once at registration.
SQLite-backed, per-webhook-id breaker (state survives an INVOX process restart) that stops hammering a persistently-failing customer endpoint with repeated 5-second timeouts, and lets exactly one probe request through per recovery window instead of a full retry storm.
- SSRF guard (
invox/webhooks/ssrf.py): HTTPS only in production, no - Circuit breaker (
invox/webhooks/circuit_breaker.py): a
Rate Limiting, Timeouts, Request Hardening
invox/api/hardening.py enforces: a per-key sliding-window rate limit (default 300 req/min, configurable), a hard cap on total request body size (default 80 MB, rejected from Content-Length before any multipart is streamed to disk), a request wall-clock timeout so a slow/stuck request cannot pin a worker, and a standard set of hardening response headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, cross-origin isolation headers, etc.). Inbound files additionally pass through the NOX Gate (invox/nox/gate.py, documented in SECURITY.md) — rate limit, replay-window + nonce check, content-type allowlist, filename guard, size cap, magic-byte detection, XXE/DOCTYPE rejection, and a compression-bomb check — fail-closed, before the validator subprocess is ever reached.
Data Residency / EU Hosting
DEPLOYMENT_EU.md documents a European deployment path (Docker-based, with pinned/checksum-verified KoSIT, XRechnung-configuration and Mustang (ZUGFeRD/Factur-X) JAR dependencies fetched and verified rather than bundled, for license compliance). If a specific hosting region or provider commitment matters for your procurement process, confirm the current production hosting location directly — this page documents the deployment path that exists in the repo, not a live infrastructure inventory.
DPA, TOMs, Subprocessors
A draft Auftragsverarbeitungsvertrag (AVV) — the German-law equivalent of a Data Processing Agreement under Art. 28 GDPR — exists at docs/legal/AVV.md. It is explicitly marked in the document itself as a draft template requiring legal review before use with customers ("ENTWURF — VOR VERTRAGSSCHLUSS MIT KUNDEN ANWALTLICH PRÜFEN LASSEN"). It describes the processing as transient, in-memory validation with zero-retention of invoice content. Treat the AVV/DPA as in progress, not as a finished, counter-signable legal instrument, until that legal review is complete. A dedicated, standalone subprocessor list is not currently published; ask directly if you need one for a vendor-risk review.
Backup & Disaster Recovery
There is no dedicated, standalone backup/disaster-recovery runbook committed in this repo today. INCIDENT_RESPONSE_RUNBOOK.md covers operational incident classes (validator unavailable, elevated error rates, evidence-PDF generation issues) and remediation steps, but a formal RPO/RTO-stated backup-and-recovery plan is not yet documented — state this as a gap rather than implying coverage that doesn't exist in writing.
Incident Management
INCIDENT_RESPONSE_RUNBOOK.md defines a four-tier severity classification (P1: service unreachable, immediate response — down to P4: UI/evidence-PDF issue, next business day) with per-incident-class runbook steps for the on-call operator. This is an internal operator runbook, not a published customer-facing SLA or breach-notification commitment; if your contract requires a specific breach-notification timeline, that belongs in the DPA/AVV negotiation, not this page.
Certifications
None held today. No SOC 2 report, no ISO 27001 certificate, no PCI-DSS attestation. Invoice-format validation itself is standards-based rather than self-certified: XRechnung and ZUGFeRD/Factur-X validation runs the official KoSIT and Mustang tools as subprocesses (not a reimplementation), and EN16931 rule IDs are mapped against the CEN TC 434 semantic model — see CERTIFICATION_READINESS.md for the full, honest breakdown of what is subprocess-verified versus what would require a separate, paid conformity assessment (e.g. formal EN16931/PEPPOL AP certification, which INVOX has not commissioned).
Questions about anything on this page, or need something not covered here (a specific encryption-at-rest attestation, a subprocessor list, a signed DPA) — ask directly. This page will be updated as real capabilities ship, not ahead of them.