Security Controls
Authentication, encryption, network isolation, key management, audit controls, and compliance posture for VaultPDF. For CISO, security teams, and pen-test scoping.
Last updated: 2026-05-31. Version: 1.0.
For architecture context see Architecture & Data Flow.
1. Authentication and Authorisation
API Authentication
| Endpoint | Method | Notes |
|---|---|---|
| Dispatcher render/workflow endpoints | Azure Function host key (HTTPS) | Key stored in SPFx property bag (encrypted at rest by SharePoint); never in source code |
| Dispatcher admin/audit endpoints | Azure AD Bearer JWT | authLevel: "function" plus explicit JWT exp check on every hop |
| Licensing API | License key (HTTPS header) | Key stored in Azure Key Vault; retrieved via MSI at startup |
| eSign Portal - Dispatcher token verify | HMAC-SHA256 portal token | 4-hour TTL; token bound to correlationId, action, and timestamp |
| SharePoint Graph calls | Managed Identity (MSI) | Sites.ReadWrite.All granted via Grant-SpAccess.ps1; no passwords or client secrets |
Authentication Flow
flowchart LR
subgraph Callers["External Callers"]
SPFx["SPFx App<br/>(SharePoint)"]
AdminTool["Admin / Audit Tool"]
Approver["Approver Browser"]
ESignPortal["eSign Portal<br/>(VaultPDF-operated)"]
end
subgraph Dispatcher["The Dispatcher - func-dispatcher (your Azure subscription)"]
RenderAPI["Render / Workflow<br/>Endpoints"]
AdminAPI["Admin / Audit<br/>Endpoints"]
TokenVerify["eSign Token<br/>Verify Endpoint"]
end
subgraph AzureServices["Azure Services - Managed Identity Only (no passwords)"]
SPO["SharePoint<br/>Graph API"]
KV["Azure Key Vault"]
SB["Azure Service Bus queues"]
end
subgraph Processor["The Vault Engine - func-processor (your Azure subscription)"]
SBTrigger["Service Bus<br/>Trigger (MSI)"]
end
SPFx -->|"Function Key · HTTPS"| RenderAPI
AdminTool -->|"Azure AD Bearer JWT (exp checked every hop)"| AdminAPI
Approver -->|"HMAC portal token - 4-hour TTL"| TokenVerify
ESignPortal -->|"HMAC portal token · HTTPS"| TokenVerify
RenderAPI -->|"MSI"| SPO
RenderAPI -->|"MSI"| KV
RenderAPI -->|"MSI"| SB
SB -->|"MSI trigger"| SBTrigger
SBTrigger -->|"MSI"| SPO
SBTrigger -->|"MSI"| KV
Authorisation
- Feature flags are enforced at render time via license-gated feature gates (
licenseFeatures.ts); theNODE_ENV !== 'production'guard prevents override in production deployments. - Multi-stage workflow approvals use per-step HMAC-SHA256 approver-email hashing with
crypto.timingSafeEqualcomparison (prevents timing oracles). - Audit-event API requires valid Bearer JWT; tenant scope is enforced on every query.
Licensing Integrity and Enforcement Model
VaultPDF implements a tiered licensing enforcement model to reduce runtime dependency on external systems while maintaining tamper-evident integrity.
Entitlement Verification
- All license entitlements are cryptographically signed by VaultPDF using JWS ES256 (ECDSA P-256).
- VaultPDF holds the private signing key. Your Dispatcher Function holds only the public verification key - it can verify but never forge entitlements.
- The JWS signature is validated before every use in runtime decision-making.
- Any modification to the cached
license.vpdffile causes signature verification to fail; the file is discarded and the tier cascade falls through to Tier 3 (Licensing API).
Runtime Enforcement
- The Dispatcher evaluates a 4-tier cascade: in-memory cache (24 h TTL) → SharePoint
license.vpdf→ Licensing API → 48-hour grace (API unavailability only). - Feature gating is enforced locally via the cached entitlement state. No external call is required when the in-memory cache (Tier 1) or SharePoint file (Tier 2) is valid.
- When both local tiers miss, the Dispatcher calls the Licensing API directly (Tier 3); this carries only
licenseKeyandtenantId- no document content.
Sync Security Controls
- Licensing sync and Tier 3 runtime calls use HTTPS (TLS 1.2+).
- Authentication uses a VaultPDF-issued identity token (JWS ES256) bound to your
licenseKeyandtenantId; no user credentials or document content are transmitted. - The signed entitlement payload includes a tenant ID binding (
tidclaim) verified before the file is written to SharePoint; atidmismatch causes the response to be rejected.
Resilience and Failure Behaviour
| Scenario | Outcome |
|---|---|
| Licensing API temporarily unreachable | System operates on Tier 1/2 cache. If both miss, 48-hour grace activates using last known-good entitlement. Independently, the VaultNotification rate-limit cache is fail-open - it keeps serving the last-known limits for up to 24 h and never blocks a send when the API is unreachable. No impact to document generation or notifications. |
| Grace period expires (API still unreachable) | Access denied entirely. System resumes automatically when API is reachable. |
| License revoked or expired (API responds) | Nightly sync deletes license.vpdf; in-memory cache cleared. Next request is denied. Grace period does not apply to genuine revocations or expired licenses. |
| VaultNotification service temporarily unavailable | Notification requests are held on a durable Azure Service Bus queue and processed when the service recovers - emails are delayed, not dropped. Document generation, rendering, and signing are unaffected because notifications are a downstream, asynchronous step. |
| eSign Portal temporarily unavailable | The portal is a stateless relay; the signing page itself is served from your own Dispatcher function. New signing-link redirects pause until it recovers; already-served signing pages and in-progress signatures are unaffected. |
2. Data Encryption
In Transit
- All HTTP endpoints require HTTPS (TLS 1.2+). The Dispatcher function app has
httpsOnlyenforced in Bicep. - Azure Service Bus queues connections use AMQP over TLS.
- Internal Azure backbone traffic (Isolated Azure Function - Azure Blob Storage, Azure Key Vault) uses TLS enforced by Azure.
At Rest
| Data | Encryption | Key location |
|---|---|---|
| Azure Blob Storage (outputs, audit, archives) | AES-256 (Azure Storage Service Encryption) | Microsoft-managed (customer-managed key option available via Azure Key Vault) |
| Azure Key Vault secrets | AES-256 | Azure HSM-backed |
| Approver email addresses in workflow state | AES-256-GCM + random IV | Your Azure Key Vault (ESIGN_ENCRYPTION_KEY) |
| Portal tokens | HKDF-derived kEnc (AES-GCM) + kMac (HMAC-SHA256) | Your Azure Key Vault |
.vpdf audit archives | AES-256 (Azure Storage SSE) + HMAC-SHA256 manifest | Storage SSE + your AUDIT_REPORT_HMAC_KEY environment variable |
3. Network Controls
VaultPDF uses a tiered network model so each deployment matches its environment and compliance needs. In all tiers, storage access is governed by Microsoft Entra ID RBAC (managed-identity only), allowSharedKeyAccess: false (no shared-key or anonymous access), and WORM immutability on the audit container.
Development / Demo deployments
Storage accounts run with defaultAction: Allow, secured by RBAC + no-shared-key + WORM. This avoids VNet provisioning for non-production environments while keeping identity-based access control fully enforced. No document data is exposed: every account still requires a valid managed-identity role to read or write.
Production - Tier 1 (Standard, recommended)
Deployed on the Azure Functions Flex Consumption plan with VNet integration. All four storage accounts (stDisp, stProc, stData, stOut) use networkAcls.defaultAction: Deny. Function App access to storage traverses the VNet via subnet service endpoints; external internet access to the data plane is blocked at the network ACL layer. This is the recommended production posture and is enabled by supplying the vnetSubnetId deployment parameter - no plan upgrade required.
Production - Tier 2 (Dedicated / Regulated)
For customers requiring dedicated compute or specific regulatory isolation (e.g. FedRAMP High, air-gapped, or internal policies mandating isolated infrastructure), Tier 2 extends Tier 1 with additional isolation. Tier 2 capabilities are at two different levels of readiness today - stated plainly here rather than bundled as a single offering:
Available today, as a deployment parameter: Service Bus network isolation (serviceBusSku=Premium, default: Standard). Namespace-level IP/VNet firewall rules (and Private Endpoints) for Service Bus are a Premium-tier-only Azure capability - Tier 1's Standard-tier namespace is RBAC-only (managed-identity access, no shared keys) with no network ACL option available at that tier at all; a deployment attempting one on Standard fails outright rather than silently no-op'ing. Premium costs materially more (~$677/month per namespace vs. Standard's ~$10/month) and is deliberately restricted to production deployments - a non-production deployment requesting it is blocked at deploy time - so it is only ever chosen as a deliberate, informed decision made with the customer at onboarding, never a default or an accidental selection.
Planning note: moving an existing customer from Standard to Premium later is a namespace migration requiring a scheduled maintenance window, not a live configuration toggle - Azure provisions a new Premium namespace and migrates entities into it (the DNS name is preserved, so client connection strings are unaffected, but messages in flight at the moment of cutover are not carried over). This is why the tier is decided once, at onboarding, rather than deferred.
Not yet implemented - roadmap, requires an engineering scoping conversation: a dedicated (Elastic Premium) compute plan for the Function Apps and Private Endpoints for storage/Key Vault. These are not currently exposed as deployment parameters. A customer whose compliance requirement depends on either of these should raise it during onboarding scoping, not assume it is available at go-live.
Choosing Standard vs. Premium
Most customers, including most compliance-driven ones, belong on Tier 1 / Standard:
- Your requirement is that the document data plane (storage) be network-isolated - Tier 1 already delivers this via
networkAcls.defaultAction: Denyand VNet service endpoints on all four storage accounts. - You don't have a specific citation requiring the messaging/queueing layer itself to be network-restricted, as distinct from the data it carries.
- Managed-identity RBAC (no shared keys, no connection strings) is an acceptable control for Service Bus on its own - reasonable here because Service Bus carries workflow metadata (job signals, delivery events), not document content or file bytes.
- Cost efficiency matters: Standard Service Bus is ~$10/month; Premium is ~$677/month per customer namespace, recurring.
Consider Tier 2 / Premium Service Bus when:
- A named regulatory framework or internal security policy explicitly requires network-level isolation of all backend Azure services - not just the storage data plane. FedRAMP High system boundaries are the common example.
- Your organization operates under a blanket policy that any Azure resource with a public endpoint is out of bounds regardless of auth model, even one gated by RBAC-only access.
- You can commit to the decision at onboarding - Premium is not something to add later without a scheduled maintenance window, so "we might need it eventually" should be resolved before go-live, not deferred.
If your requirement extends to dedicated compute or Private Endpoints for storage/Key Vault (beyond Service Bus), flag that separately during onboarding scoping - unlike Service Bus tier, those are not yet a turnkey parameter.
Application endpoints (all tiers)
The Dispatcher's HTTP endpoints are reachable over the public internet by design - they serve SPFx (browser) clients, external signers, and integration callers (Power Platform, the eSign Portal). These endpoints are secured by layered authentication rather than network restriction: Azure Function keys (a second factor on initiator routes), Entra ID JWT bearer enforcement, HMAC-SHA256 signatures on portal callbacks, and opaque signed tokens on signer and delivery routes. The data plane (storage) is network-isolated in Tier 1 and Tier 2 even though the application endpoints remain publicly reachable.
VaultPDF's outbound calls to the Licensing API and eSign Portal use HTTPS/TLS 1.2+. No inbound firewall rules need to be opened for these calls.
4. Key Management
- All secrets are stored in your Azure Key Vault. VaultPDF vendor staff have no standing access.
- HKDF root key derives
kEncandkMacper-token viaHKDF(root, tokenId, purpose). enablePurgeProtection: trueon Azure Key Vault (set by Bicep; irreversible); 90-day soft-delete retention.- Key rotation is an operator action via
az keyvault secret set. Approver-email ciphertext stores akeyIdfield to support future re-encryption on key roll.
Key Management Flow
flowchart TD
KV["🔐 Azure Key Vault<br/>(your subscription)<br/>VaultPDF vendor staff have NO standing access"]
KV -->|"HKDF root key (retrieved via MSI)"| HKDF
subgraph HKDF["HKDF Derivation - per token"]
direction LR
kEnc["kEnc<br/>AES-GCM encrypt<br/>portal token payload"]
kMac["kMac<br/>HMAC-SHA256 sign<br/>portal token"]
end
KV -->|"Approver-email AES-256-GCM key (retrieved via MSI)"| AES["AES-256-GCM<br/>Encrypt approver PII<br/>in Azure Table Storage"]
KV -->|"ACS email key (optional) - BYO email only"| ACS["Azure Communication Services<br/>BYO outbound email"]
AppSettings["Azure App Settings<br/>(your Function App config)"] -->|"AUDIT_REPORT_HMAC_KEY"| HMACSign["HMAC-SHA256<br/>Sign verification report PDFs<br/>+ upload .sig.json companion"]
style KV fill:#e8f5e9,stroke:#388e3c
5. Audit and Tamper-Resistance
| Control | Status |
|---|---|
| Append-only audit JSONL blob | Each operation appends; no overwrites |
| Sequential event ordering (hash chain) | Each event includes previousHash; verifier API checks chain integrity |
| HMAC-signed verification reports | AUDIT_REPORT_HMAC_KEY signs each verify-report PDF; companion .sig.json uploaded |
| Immutable storage containers | Deploy with -LockImmutability flag (irreversible; enables WORM for audit containers) |
| Storage geo-redundancy | Standard_GRS for audit and output storage (Bicep default) |
| Azure Key Vault purge protection | enablePurgeProtection: true; 90-day retention |
| Signed audit tip (daily) | Planned - daily signed tip for long-gap tamper detection |
6. Input Validation and Injection Prevention
| Attack vector | Control |
|---|---|
| OData injection (licensing queries) | odata tagged-template literal; single-quote doubling; no raw string concatenation |
| SSRF (external asset fetch) | DNS resolution mandatory; resolved IP validated against RFC1918 blocklist; IP pinned for duration of fetch |
ZIP slip (.vpdf extraction) | assertSafeArchiveEntryName rejects traversal, absolute, UNC, drive, and control-char paths before any extraction |
| Condition engine code injection | Zero eval/Function usage; BLOCKED_PROTOTYPE_KEYS guard; MAX_EXPRESSION_LENGTH 4096; MAX_TOKEN_COUNT 1024; MAX_EVAL_DEPTH 200; 19 security tests in CI |
| Duplicate seal (replay) | Service Bus messageId = {correlationId}-workflow-seal; requiresDuplicateDetection: true on queue |
| Concurrent approval race | Optimistic concurrency: If-Match ETag on all 4 intermediate updateWorkflowState calls; 412 - HTTP 409 to caller |
| Schema substitution mid-workflow | loadSchema verifies SHA-256 of fetched YAML against schemaSha256 stored at workflow start; throws on mismatch |
7. Dependency and Supply Chain
pnpmlockfile with exact version pinning; CI validates lockfile integrity.- SBOM generation via CycloneDX from pnpm lockfile - integration in progress.
- No
evaland no dynamicrequire()in production code paths. - ZIP-slip path validation enforced in archive extraction; third-party dependencies tracked via Dependabot.
8. Rate Limiting and Abuse Prevention
- Licensing API rate limiter uses Azure Table Storage and ETag optimistic concurrency; scale-safe across multiple Isolated Azure Function instances.
- SPFx command set has per-item in-flight debounce (
_inFlight = new Set<string>()); duplicate clicks are dropped. - Dashboard polling uses a 60-second base interval with adaptive backoff on 429 (doubles up to 8x, maximum approximately 8 minutes).
9. Compliance Posture
| Standard | Status |
|---|---|
| GDPR (EU) | Data stays in customer's Azure region; DPA available on request; sub-processor list in Sub-Processor List |
| HIPAA / HITECH | Customer-owned infrastructure model supports BAA with Microsoft; no PHI transits VaultPDF systems |
| FedRAMP Moderate | Customer-owned deployment into Azure Government regions supported; controls mapping available on request |
| SOC 2 Type I | Planned - engage external auditor |
| ISO 27001 | Planned - leverage SOC 2 control mapping |
| PDF/UA-1 (Section 508) | Structural tagging, MarkInfo, StructTreeRoot, Lang, ViewerPreferences/DisplayDocTitle implemented |
| WCAG 2.1 AA | PDF-side implemented; UI accessibility improvements tracked in audit backlog |
Pen-Test Scoping and Security Questionnaires
Contact our security team for pen-test scoping, architecture review, and compliance mapping.
Data Processing Guide
What data VaultPDF processes, where it flows, how long it is retained, and how it relates to GDPR and data-residency requirements.
Sub-Processor List
Complete list of VaultPDF sub-processors and third-party services, satisfying GDPR Art. 28(3)(d) and enterprise procurement requirements.