Security hardening
This page describes the controls Docsentry enforces and the settings that tune them.
Authentication
Section titled “Authentication”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 |
Mandatory two-factor
Section titled “Mandatory two-factor”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.
Revocation
Section titled “Revocation”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.
Breached passwords
Section titled “Breached passwords”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.
Accounts are created by administrators
Section titled “Accounts are created by administrators”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.
Separation of duties
Section titled “Separation of duties”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 and shedding
Section titled “Rate limiting and shedding”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.
Encryption at rest
Section titled “Encryption at rest”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.
Data protection key ring
Section titled “Data protection key ring”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.
Audit tamper-evidence
Section titled “Audit tamper-evidence”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.
Privacy retention and erasure
Section titled “Privacy retention and erasure”- 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.
Content Security Policy
Section titled “Content Security Policy”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.
Content isolation
Section titled “Content isolation”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.
API request forgery
Section titled “API request forgery”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.
Transport
Section titled “Transport”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.
Forwarded headers
Section titled “Forwarded headers”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.
Secrets in configuration
Section titled “Secrets in configuration”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.
Database least privilege
Section titled “Database least privilege”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
Section titled “Uploads”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.
Application surface
Section titled “Application surface”- 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.
Related
Section titled “Related”- Deployment - the pre-flight checklist.
- Permission model - authorisation, as distinct from authentication.
- Reports - the security health and audit reports.