Skip to Content
Security & TrustDeployment Context Security

Deployment Context Security

The Customer Portal’s behavior depends on whether a VeriProof deployment is already configured. A fresh installation needs to show a setup wizard; an existing installation must never show that wizard to unauthorized users. Determining which case applies presents a challenge: the portal cannot ask the API which mode it’s in if the API might be unreachable.

VeriProof solves this with a signed static file — deployment-config.json — that the portal reads before making any API calls.


The problem: fail secure at startup

Two scenarios must be handled correctly:

ScenarioRequired behavior
Fresh installation (first-time setup)Show the setup wizard to the authenticated admin
Existing deployment with API unavailableNever show setup wizards — treat as locked

An API-dependent check fails the second requirement: if the API is down, the portal cannot reach it to determine that it is an existing deployment, and must not fall back to showing the setup wizard (which would expose infrastructure configuration to attackers).


The solution: signed static configuration

/deployment-config.json is a small JSON file served as a static asset alongside the portal:

{ "deploymentId": "f4c2a1e0-...", "deploymentMode": "saas", "isSetupComplete": true, "deploymentEnvironment": "production", "configVersion": 1, "lastUpdated": "2026-03-01T00:00:00Z", "signature": "<base64-hmac-sha256>" }

The portal reads this file before attempting any API call. The signature field is an HMAC-SHA256 signature of the file’s content (excluding the signature field itself), using a key stored in Azure Key Vault. The portal validates the signature using the Web Crypto API before trusting any field.

If the file is absent, malformed, or has an invalid signature, the portal defaults to the locked state — setup wizards are hidden, and infrastructure settings are not accessible.


Security properties

ConditionPortal behavior
Valid config, isSetupComplete: trueNormal operation
Valid config, isSetupComplete: falseShow setup wizard to authenticated admin only
Invalid signatureLocked — no setup wizard, no infrastructure settings
File missingLocked
API unavailable (file present and valid)Portal renders normally; API-dependent features show error states

This means an attacker who tampers with the static file — for example, changing isSetupComplete: false to trick a deployed portal into showing the setup wizard — will produce an invalid HMAC signature. The portal detects this and locks.


How the file is generated

The deployment configuration file is generated and signed by the backend:

  1. Initial deployment: the backend generates deployment-config.json with isSetupComplete: false and a fresh HMAC signature
  2. Setup completion: the backend regenerates the file with isSetupComplete: true
  3. Deployment mode changes: the backend regenerates with the updated deploymentMode

The generation endpoint (/v1/admin/regenerate-deployment-config) requires Staff Portal authentication. The signing key is fetched from Azure Key Vault at generation time and never stored in the config file itself.

After generation, the deployment pipeline uploads the file to the frontend’s static asset storage (Azure Blob / Azure Static Web Apps). No portal rebuild is required — the runtime reads the file on each page load.


Signing key management

The HMAC-SHA256 signing key is:

  • A 64-byte base64-encoded key generated cryptographically
  • Stored in Azure Key Vault under {env}-deployment-context-signing-key
  • Never present in source code, configuration files, or environment variables
  • Rotated by generating a new key in Key Vault and regenerating the static config file
// Backend: reading the signing key at generation time var signingKey = configuration["DeploymentContext:SigningKey"] ?? throw new InvalidOperationException( "Deployment context signing key not configured. " + "Set DeploymentContext:SigningKey via Key Vault."); if (signingKey.Length < 32) throw new InvalidOperationException("Signing key is too short.");

The application throws (rather than falling back to a default key) if the key is missing or too short. This fail-secure behavior prevents deployment with a weak or absent signing key.


Frontend validation

The portal validates the signature using the browser’s native Web Crypto API — no third-party crypto library is required:

async function validateDeploymentConfig(config: DeploymentConfig): Promise<boolean> { const { signature, ...payload } = config; const encoder = new TextEncoder(); const key = await crypto.subtle.importKey( "raw", encoder.encode(process.env.VITE_DEPLOYMENT_CONTEXT_SIGNING_KEY), { name: "HMAC", hash: "SHA-256" }, false, ["verify"] ); const sigBytes = Uint8Array.from(atob(signature), c => c.charCodeAt(0)); return crypto.subtle.verify("HMAC", key, sigBytes, encoder.encode(JSON.stringify(payload))); }

The public signing key used for frontend validation is a separate derived value from the backend signing key. It is embedded at build time as an environment variable and is not a secret — it can only verify signatures, not create them.


Next steps

Last updated on