Trust Center

Security Controls

Authentication, encryption, network isolation, key management, audit controls, and compliance posture for REFRACT PDF. 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

EndpointMethodNotes
Dispatcher render/workflow/gateway endpointsMicrosoft Entra ID Bearer JWTAnonymous at the Azure Functions level - there is no function key on any route. aud, iss, tid, and exp are validated inside the handler on every request.
Licensing APITenant identifier, license key, and identity tokenKey stored in Azure Key Vault; retrieved via MSI at startup
eSign Portal - session linksSession and group identifiers in the URLValidated against the session record in the portal's own store, not an HKDF-derived token
REFRACT Workflow - portal tokenHMAC-signed portal token4-hour TTL, reissued on each valid access; the initial invite token is capped at 1 day
SharePoint Graph callsREFRACT SharePoint Access app registrationMicrosoft Graph Sites.ReadWrite.All; its client secret is held in your Azure Key Vault (sp-secrets). The managed identity's Sites.ReadWrite.All (granted by Grant-SpAccess.ps1) is used only when sp-secrets holds no client secret.
Microsoft Entra group membership checksManaged Identity (MSI)Microsoft Graph GroupMember.Read.All, granted by Grant-SpAccess.ps1

Authentication Flow

flowchart LR
    subgraph Callers["External Callers"]
        SPFx["SPFx App<br/>(SharePoint)"]
        AdminTool["Admin / Audit Tool"]
        Approver["Approver Browser"]
        ESignPortal["eSign Portal<br/>(Refract Logic-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 REFRACT Engine - func-processor (your Azure subscription)"]
        SBTrigger["Service Bus<br/>Trigger (MSI)"]
    end

    SPFx -->|"Entra ID Bearer JWT"| RenderAPI
    AdminTool -->|"Azure AD Bearer JWT (exp checked every hop)"| AdminAPI
    Approver -->|"Workflow portal token - 4-hour TTL"| TokenVerify
    ESignPortal -->|"Session/group identifiers · 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); the NODE_ENV !== 'production' guard prevents override in production deployments.
  • Multi-stage workflow approvals use per-step SHA-256 approver-email hashing with crypto.timingSafeEqual comparison (prevents timing oracles).
  • Audit-event API requires valid Bearer JWT; tenant scope is enforced on every query.

Licensing Integrity and Enforcement Model

REFRACT PDF 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 REFRACT PDF using JWS ES256 (ECDSA P-256).
  • REFRACT PDF 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.json file 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.json → 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 at license.refractplatform.com (Tier 3); POST /v1/validate carries your tenant identifier, license key, an identity token, and your deployment address - no document content.

Sync Security Controls

  • Licensing sync and Tier 3 runtime calls use HTTPS (TLS 1.2+).
  • Authentication uses a REFRACT PDF-issued identity token (JWS ES256) bound to your licenseKey and tenantId; no user credentials or document content are transmitted.
  • The signed entitlement payload includes a tenant ID binding (tid claim) verified before the file is written to SharePoint; a tid mismatch causes the response to be rejected.

Resilience and Failure Behaviour

ScenarioOutcome
Licensing API temporarily unreachableAn instance that is already licensed serves from its Tier 1 cache, from license.json on a plan other than trial, or under the 48-hour grace period from its last valid answer. A new instance (after a cold start, scale-out, deployment, or platform move) has no grace period: on a trial plan, or without a valid license.json, it tries the API once more, then refuses the request with 503 and code LICENSE_UNAVAILABLE, "retryable": true, and Retry-After: 30, until the API answers. See Licensing Architecture. Independently, the REFRACT Notification 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 notifications.
Grace period expires (API still unreachable)Requests are refused with 503 and code LICENSE_UNAVAILABLE, as on a new instance. System resumes automatically when API is reachable.
License revoked or expired (API responds)Nightly sync deletes license.json; in-memory cache cleared. Next request is refused with 403 and code LICENSE_INVALID. Grace period does not apply to genuine revocations or expired licenses.
REFRACT Notification service temporarily unavailableNotification 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 unavailableThe signing UI itself is hosted by the portal; the document and signed output are served from your own Dispatcher function. New sessions pause until the portal recovers; documents already accessible remain so.

2. Data Encryption

In Transit

  • All HTTP endpoints require HTTPS (TLS 1.2+). The Dispatcher function app has httpsOnly enforced 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

DataEncryptionKey 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 secretsAES-256 (standard Key Vault SKU, not HSM-backed)Azure - your tenant
Approver email addresses in workflow stateAES-256-GCM + random IVYour Azure Key Vault (ESIGN_ENCRYPTION_KEY)
Portal tokensHKDF-derived kEnc (AES-GCM) + kMac (HMAC-SHA256)Your Azure Key Vault
.raudit audit archivesAES-256 (Azure Storage SSE); the manifest records a SHA-256 hash of each component (no HMAC)Microsoft-managed (Storage SSE)

3. Network Controls

REFRACT PDF 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) and allowSharedKeyAccess: false (no shared-key or anonymous access). Write-once (WORM) protection on the audit container is available in every tier but is deployed unlocked by default; it becomes an enforced guarantee only where the immutability policy has been applied and locked for the storage account (see the -LockImmutability deployment flag below).

Development / Demo deployments

Storage accounts run with defaultAction: Allow, secured by RBAC + no-shared-key; write-once (WORM) protection is available on the audit container but, as in every tier, is enforced only where the immutability policy has been applied and locked for the storage account. 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.

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: Deny and 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: Entra ID JWT bearer enforcement (no route uses an Azure Functions key), 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.

REFRACT PDF'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. Refract Logic staff have no standing access.
  • HKDF root key derives kEnc and kMac per-token via HKDF(root, tokenId, purpose).
  • enablePurgeProtection: true on Azure Key Vault (set by Bicep; irreversible); 30-day soft-delete retention.
  • Key rotation is an operator action via az keyvault secret set. Approver-email ciphertext stores a keyId field to support future re-encryption on key roll.

Key Management Flow

flowchart TD
    KV["🔐 Azure Key Vault<br/>(your subscription)<br/>Refract Logic 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

ControlStatus
Append-only audit JSONL blobEach operation appends; no overwrites
Sequential event ordering (hash chain) - eSign and Workflow audit trails onlyEach event includes previousHash; verifier API checks chain integrity
HMAC-signed verification reportsAUDIT_REPORT_HMAC_KEY signs each verify-report PDF; companion .sig.json uploaded
Write-once (WORM) storage containersDeploy with -LockImmutability flag (irreversible; enables WORM for audit containers)
Storage geo-redundancyStandard_GRS for audit and output storage (Bicep default)
Azure Key Vault purge protectionenablePurgeProtection: true; 30-day soft-delete retention
Signed audit tip (daily)Planned - daily signed tip for long-gap tamper detection

6. Input Validation and Injection Prevention

Attack vectorControl
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 (.raudit archive extraction)assertSafeArchiveEntryName rejects traversal, absolute, UNC, drive, and control-char paths before any extraction
Condition engine code injectionZero 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 raceOptimistic concurrency: If-Match ETag on all 4 intermediate updateWorkflowState calls; 412 - HTTP 409 to caller
Schema substitution mid-workflowloadSchema verifies SHA-256 of fetched YAML against schemaSha256 stored at workflow start; throws on mismatch

7. Dependency and Supply Chain

  • pnpm lockfile with exact version pinning; CI validates lockfile integrity.
  • SBOM generation via CycloneDX from pnpm lockfile - integration in progress.
  • No eval and no dynamic require() 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. Certifications

Refract Logic holds no independent certifications today - no SOC 2, no ISO 27001, no HIPAA BAA, and no FedRAMP authorization. Document rendering deploys into your own Azure subscription and region; see the Sub-Processor List for what does and does not transit shared services. PDF/UA-1 structural tagging (MarkInfo, StructTreeRoot, Lang, ViewerPreferences/DisplayDocTitle) is implemented; see Section 508 for what the accessibility check reports and does not enforce.


Pen-Test Scoping and Security Questionnaires

Contact our security team for pen-test scoping, architecture review, and compliance mapping.

On this page