Skip to content

Security hardening

This page describes the controls Docsentry enforces and the settings that tune them.

Built on ASP.NET Core Identity.

Control Default
Password length At least 12 characters
Breached passwords Refused on every path that sets a password, when a corpus is configured
Lockout 5 failed attempts, 15 minutes (AccountSecurity:MaxFailedAccessAttempts, LockoutMinutes)
Two-factor Authenticator app with recovery codes; mandatory for administrators
Session revalidation Every 2 minutes (AccountSecurity:SecurityStampValidationMinutes)
Session cookie Secure, HttpOnly, SameSite=Lax
Account creation Administrators only, by invitation

AccountSecurity:MandatoryTwoFactorRoles lists the roles that must enrol an authenticator before they can reach anything else. It defaults to Administrator. A member who has not enrolled is sent to an enrolment page; enrolment and sign-out always stay reachable, so enforcement cannot lock anyone out. Add Auditor and RecordsManager if you use those roles.

Removing a role, resetting a password, or signing a user out everywhere takes effect at the next session revalidation - two minutes by default, within a permitted range of 1 to 60.

BreachedPasswords:Source selects where passwords are checked:

Source Behaviour
None The check is off (shipped default).
OfflineFile Recommended. Reads a locally stored “SHA-1, ordered by hash” Pwned Passwords file. No network access.
HibpRange Queries the Have I Been Pwned range API. Only the first five characters of the password’s SHA-1 hash leave the process.

OnSourceUnavailable decides what happens if the source cannot answer: Permit (default) accepts the password and logs a warning, Reject refuses it.

There is no public registration. An administrator creates the account and the user receives an invitation link to set their own password. Invitations last AccountSecurity:InvitationLifetime (7 days by default), and resending one invalidates earlier links. See Users and roles.

Privileged screens are gated by capabilities, assigned to roles:

Role Capabilities Bypasses document permissions
Administrator All Yes
Auditor Audit read, audit integrity verification No
RecordsManager Retention and metadata configuration No
WorkflowManager Workflow authoring No
User None No

Auditor cannot be combined with Administrator, RecordsManager or WorkflowManager on the same account, so the person reviewing the audit trail is not the person it records.

Every audit entry records whether the action was authorised by an ordinary permission grant or by the administrator override, and the audit report can filter on it.

Rate limiting applies chained fixed windows:

  • Sign-in forms use two allowances at once: a strict per-account allowance that applies across all source addresses, and a looser per-address allowance sized for shared office networks.
  • Signed-in credential checks - change password, enable authenticator, delete personal data - share a per-user allowance (RateLimiting:CredentialVerificationPermitLimit, 10 per 5 minutes by default) across addresses and sessions.
  • Everything else has ordinary per-user and per-address allowances.

Static assets and the health endpoint are exempt. Rejections return 429 with Retry-After.

Authentication concurrency bounds simultaneous password verification, which is deliberately CPU-expensive. Over the limit, requests are shed with 503 and Retry-After rather than queueing until the connection pool is exhausted.

Every stored document surface is encrypted by the application:

Content Protection
Document versions AES-256-GCM, chunked and authenticated, with a per-file data key
Previews and thumbnails Same envelope
Quarantined uploads and in-progress upload chunks Same envelope
Handwritten-signature renditions Same envelope
Storage metadata (original file names) Protected by the Data Protection key ring

Data keys are wrapped by the Data Protection key ring. Storage paths come from a keyed content address rather than a plain hash, so someone who can read the storage volume cannot confirm a document’s contents by hashing a candidate file. Identical content is still stored once.

Existing installations convert content in the background with the storage-encryption-backfill job, without downtime.

Two things are outside application-level encryption and need platform controls:

  • The database itself, which includes extracted OCR text used by search. Enable Transparent Data Encryption and encrypt database backups.
  • Application logs, which contain document names and user identifiers. Keep them on an encrypted volume.

Temporary decrypted working copies used by preview, OCR and bulk export go to Storage:WorkingCopyPath. It is created owner-only and swept automatically; keep it on local disk, outside the storage root, and out of backups.

The key ring protects authentication cookies, the “remember this machine” two-factor cookie, stored secrets such as the antivirus API key, and every document data key.

Setting Requirement
DataProtection:KeyPath Required outside Development. Absolute, and outside the content root. The host refuses to start otherwise.
DataProtection:CertificateThumbprint X.509 protection. Required for non-Windows hosts, multi-instance deployments, and backups that must restore onto different hardware.

Without a thumbprint the ring is DPAPI-protected, which is machine-scoped. At startup the application verifies that it can unwrap the storage key material and refuses to start if it cannot. See Backup.

The audit log is append-only for the application’s database role, and it is also tamper-evident:

  • Every row carries a fingerprint written at insert time.
  • A background job seals rows into signed, chained ranges every few minutes.
  • A verifier checks the chain hourly (recent) and daily (full), and on demand from the Audit integrity page.

Verification reports one of three outcomes: Healthy, Broken (something was altered, removed or inserted) or Unverifiable (the evidence needed to check it, such as the key ring, is gone). A failure raises an audit entry, an error log and a banner for administrators.

Personal fields in new audit rows are fingerprinted through per-field salted digests, so clearing an IP address or erasing a person cannot be undone by guessing values against the stored hash. Lawful redactions made by retention or erasure are recorded in a signed ledger and reported as lawfully redacted, not as tampering.

  • Network identifiers - IP addresses and user agents on audit events, sign-in records and signatures - are cleared automatically after a configurable lifetime (90 days by default).
  • Erasure lets an administrator replace a person’s identity with a pseudonym everywhere it is stored, clear their network identifiers, remove their memberships and grants, and cancel queued email, while keeping business records such as documents, signatures and workflow history.

See Governance and retention.

Application pages are served with a per-request nonce policy. Inline scripts without the correct nonce are blocked, so an injected <script> does not execute even if it reaches the page.

Security:Csp:Mode switches between enforcing and reporting. Reporting mode is for validating a policy change; it is not a deployment state. Security:Csp:ReportUri collects violation reports.

Uploaded documents are untrusted content, so every response carrying document bytes - downloads, previews, thumbnails, signed renditions, certificates, bulk archives - is served with a sandboxing policy, nosniff, X-Frame-Options: DENY, Referrer-Policy: no-referrer and a cross-origin resource policy. This is always on.

For full origin isolation, configure an optional content host: a second hostname served by the same application.

Key Meaning
ContentIsolation:ContentOrigin The content host’s origin, for example https://content.example.org.
ContentIsolation:ApplicationOrigin The application’s own public origin. Required with ContentOrigin.
ContentIsolation:TokenLifetimeMinutes How long a signed content link is valid. 1 to 60, default 5.

With it configured, document links point at the content host and carry a short-lived signed token. The session cookie is never sent there, and the content host serves nothing but content. A link that has expired renews itself through the application host, which checks the same user is still signed in. AllowedHosts must list both hostnames, and your reverse proxy should not write the ct query parameter to its access logs.

Every mutating request to the JSON API (POST, PUT, PATCH, DELETE) must carry the X-Docsentry-Csrf header. A cross-site page cannot add a custom header without a CORS preflight, and Docsentry publishes no CORS policy, so forged cross-site requests never arrive. MVC forms use standard antiforgery tokens. See HTTP API.

Security:Hsts controls max-age (365 days by default), subdomain inclusion, and preload.

Preload is effectively irreversible on the timescale of a browser release cycle. Enable it only when every subdomain will serve HTTPS indefinitely.

Security:ReferrerPolicy defaults to strict-origin-when-cross-origin. Security:PermissionsPolicy disables camera, microphone, geolocation, and interest-cohort by default - the application needs none of them.

Security:ForwardedHeaders is fail-closed. With no KnownProxies or KnownNetworks configured, forwarded headers are not trusted at all, so a client cannot choose its own apparent address to evade address-based rate limiting.

Behind a real proxy, configure it deliberately and set ForwardLimit to the actual number of proxies in the chain. Otherwise every request appears to come from the proxy.

Outside Development, the application checks at startup which configuration provider supplied the connection string and the email passwords, and logs a warning if any came from a JSON file. Supply them as environment variables or from a secret store, or use integrated authentication for the database and have no secret at all.

The application runs as a member of DocsentryAppRole, distinct from the schema owner. It cannot create databases, alter schema, or write schema versions. A startup probe verifies this and refuses to run over-privileged.

Governance surfaces - the audit log, workflow action history, signing-requirement history - are append-only for this role. Redaction for retention and erasure goes through narrowly scoped database procedures that can only clear an identifier or set a pseudonym.

Uploads are allowlisted, and extension, declared MIME type, and sniffed content must all agree. Antivirus scanning is fail-closed: content that has not been cleared is not served, previews included. Files that were never scanned are picked up by the antivirus-backfill job.

  • SQL is parameterised throughout; there is no string concatenation with user input.
  • API errors are returned as problem details, with a defined taxonomy rather than leaked exception text.
  • API request and response bodies are dedicated contracts. Database row models and Identity entities are never exposed.
  • Search results, the recycle bin, and reports are permission-trimmed: a document you cannot access does not appear at all.