HASTE — Secure Configuration Guidance

Contents

HASTE — Secure Configuration Guidance#

Field

Value

Repository

microsoft/haste

Audience

Customers / consumers / operators deploying HASTE

Last updated

2026-10-02


1. Overview#

HASTE (High-speed Assessment and Satellite Tracking for Emergencies) is an AI-driven framework for rapid disaster assessment using satellite imagery. The repository is published as a library + reference deployment: Microsoft operates the public reference deployment, while external consumers integrate the hastegeo Python library and may optionally redeploy the full stack (UI + Function Apps + Azure Batch) into their own subscription.

This guide tells you how to configure HASTE securely in your environment. It covers:

  • Authentication, authorization, and role assignment

  • Secrets and key management

  • Network and transport configuration (CORS, CSP, TLS)

  • Container and infrastructure hardening

  • Logging, monitoring, and incident response

  • Known security limitations and backward-compatibility caveats

  • How to report a vulnerability

It is not a substitute for your own threat model. Treat HASTE as one component within a larger Azure workload and apply the Microsoft Cloud Security Benchmark (MCSB) controls that apply to your overall workload.


2. Deployment models and their security posture#

HASTE supports three deployment modes. Each has a distinct threat model.

Mode

Intended use

Security posture

Local development (Docker Compose)

Single-developer evaluation only

Disabled auth, wildcard CORS, hardcoded dev keys (Azurite). Never expose to a network beyond localhost.

Self-hosted Azure deployment

Customer running HASTE in their own subscription

Production-grade if configured per this guide. Customer is responsible for SWA auth, Key Vault, RBAC, and infra hardening.

Library-only integration

Consuming the hastegeo Python package in your own application

You inherit only the library’s process-level threat model; deployment-time controls are yours.

Required disclosure: The local-development Docker Compose stack (docker/docker-compose.yml) mounts /var/run/docker.sock into containers to enable a local execution mode. This grants root-equivalent host access and is acceptable only on developer workstations. The file header documents this; this guide repeats it because it is the single most consequential local-vs-production gap.


3. Identity, authentication, and authorization#

3.1 Authentication: rely on Azure Static Web Apps Easy Auth#

HASTE’s REST API does not implement its own authentication. It trusts the x-ms-client-principal header injected by Azure Static Web Apps Easy Auth. For this to be safe:

  • The Function App must only be reachable through the Static Web App linked-backend route (/api/*). Do not expose *.azurewebsites.net directly to the public internet. Use one of:

    • SWA linked backend (default and recommended)

    • VNet integration + private endpoint

    • Function App access restrictions (az functionapp config access-restriction add) that allow only SWA’s outbound IPs

  • Use AAD (Entra ID) as the identity provider, not the SWA mock auth or GitHub identity. Configure in staticwebapp.config.json and tenant settings.

  • Confirm principal.userDetails is the user’s email in your tenant. HASTE’s user-management endpoint compares this field to the target user’s email; if your IdP returns a different claim shape, self-service flows will fail closed (safe) but you should validate before rollout.

3.2 Authorization: roles#

HASTE uses two SWA roles:

Role

Capability

administrators

Full admin: user management, group management, admin settings, deletes

contributors

Standard authenticated user: read project data, run jobs, edit own profile

Configuration guidance:

  • Assign roles via the SWA role invitation flow or via the HASTE UI’s user-invitation flow (gated by the administrators role).

  • Apply the principle of least privilege: most users should be contributors. The administrators role grants the ability to manage other users, change roles, and modify global settings.

  • Audit the list of administrators periodically. The HASTE UI’s Admin → Users page shows the current set.

3.3 DEVELOPMENT_MODE must never be set in Azure#

The DEVELOPMENT_MODE environment variable disables authentication and admin-role checks. It exists for local emulator testing only.

Production: Ensure DEVELOPMENT_MODE is not set in your Function App’s Application Settings. Treat any nonzero value of this setting on an Azure-hosted Function App as a misconfiguration.


4. Secrets and key management#

4.1 Use managed identity wherever possible#

HASTE’s Function Apps support system-assigned managed identity for:

  • Azure Maps — Use VITE_AZURE_MAPS_CLIENT_ID (managed-identity client ID) instead of a subscription key. The UI fetches an AAD token from a dedicated GetAzureMapsToken endpoint exposed by the Function App. Do not bake subscription keys into the client bundle.

  • Azure Blob Storage — Configure BLOB_ACCOUNT_URL and grant the Function App’s identity the Storage Blob Data Contributor RBAC role on the storage account. Prefer this over BLOB_CONNECTION_STRING in production.

  • Azure Cosmos DB — Use AAD authentication via DefaultAzureCredential; assign Cosmos DB Built-in Data Contributor.

4.2 Where connection strings are unavoidable, use Key Vault references#

For services that do not yet support managed identity in your topology (e.g., Azure Communication Services email, Azure Batch account key on classic auth), store the secret in Key Vault and use Key Vault references in Application Settings:

EMAIL_CONNECTION_STRING = @Microsoft.KeyVault(SecretUri=https://<vault>.vault.azure.net/secrets/email-conn/...)

4.3 Function master keys#

The CI deploy script (.github/scripts/deploy_apps.sh) writes the Function App master key to .env during deployment. Treat this file as sensitive and remove it from any machine that is not your local workstation. (The azd workflow instead injects the Function host key into the APIM backends via the postdeploy hook and does not persist it to disk.) Rotate this key periodically in production via:

az functionapp keys set --resource-group <rg> --name <function-app> --key-type masterKey --key-name default

4.4 Do not pass secrets as positional CLI arguments#

If you author your own deploy scripts based on the HASTE templates, use the file form az functionapp config appsettings set --settings @<file>.json rather than --settings KEY=VALUE on the command line. Positional secrets leak via process tables and CI debug logs.

4.5 Disable basic-auth publishing after deployment#

The deploy scripts set properties.allow=true for SCM/FTP publishing during deployment. Disable basic-auth publishing on the Function App after deployment:

az resource update --resource-group <rg> --name scm --namespace Microsoft.Web \
  --resource-type basicPublishingCredentialsPolicies \
  --parent sites/<function-app> --set properties.allow=false
az resource update --resource-group <rg> --name ftp --namespace Microsoft.Web \
  --resource-type basicPublishingCredentialsPolicies \
  --parent sites/<function-app> --set properties.allow=false

5. Network and transport security#

5.1 CORS#

  • Local development: Use explicit origins (http://localhost:5173,http://localhost:4280). See local.settings.example.jsonc.

  • Production: Set CORS to your UI’s domain only. Never use * in production.

az functionapp cors add --resource-group <rg> --name <function-app> \
  --allowed-origins https://your-haste-ui.azurewebsites.net

5.2 HTTP security headers#

HASTE ships a staticwebapp.config.json (under ui/public/) with the following headers. This is the policy the reference UI runs under — verified against the live Azure Maps and labeling pages with a clean console:

{
  "globalHeaders": {
    "Content-Security-Policy": "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; script-src 'self' https://atlas.microsoft.com; style-src 'self' 'unsafe-inline' https://atlas.microsoft.com; font-src 'self' data: https://atlas.microsoft.com https://c.s-microsoft.com https://*.cdn.office.net; img-src 'self' data: blob: https://atlas.microsoft.com https://*.atlas.microsoft.com https://*.tile.openstreetmap.org https://vantor-opendata.s3.amazonaws.com https://data.source.coop; connect-src 'self' https://atlas.microsoft.com https://*.atlas.microsoft.com https://*.tile.openstreetmap.org https://vantor-opendata.s3.amazonaws.com https://data.source.coop; worker-src 'self' blob:; child-src blob:",
    "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
    "X-Content-Type-Options": "nosniff",
    "X-Frame-Options": "DENY",
    "Referrer-Policy": "strict-origin-when-cross-origin"
  }
}

Notes on why each non-'self' source is present — tighten if your build removes the dependency:

  • atlas.microsoft.com / *.atlas.microsoft.com (script, style, font, img, connect) — the Azure Maps Web SDK is loaded from the CDN in index.html and fetches map styles, vector tiles, and glyph fonts at runtime. 'unsafe-inline' in style-src is required by Fluent UI’s runtime style injection.

  • c.s-microsoft.com and *.cdn.office.net (font) — Fluent UI loads Segoe UI and its icon/language fonts from these Microsoft CDNs at runtime. To drop them, self-host the Fluent fonts and register them locally.

  • blob: (img, worker, child) — the Azure Maps control renders tiles and spawns web workers from blob URLs.

  • *.tile.openstreetmap.org (img, connect) — only needed if you configure an OSM basemap; remove otherwise.

  • vantor-opendata.s3.amazonaws.com and data.source.coop (img, connect) — the Open Data Catalog explorer fetches the Vantor and Planet STAC catalogs (and scene thumbnails) directly from the browser. Remove if the Open Data Catalog is not enabled, or move discovery behind a same-origin backend proxy so only 'self' is needed.

Per-deployment adjustment: if your VITE_API_URL points at a separate origin (a direct *.azurewebsites.net Function App rather than the same-origin SWA /api linked-backend route), add that origin to connect-src. The default policy assumes the recommended same-origin linked-backend topology (§3.1).

5.3 TLS#

  • Static Web Apps and Function Apps enforce HTTPS by default. Do not disable.

  • For custom domains, use Azure-managed certificates and verify auto-renewal.

5.4 CSRF#

The UI’s API client does not send explicit CSRF tokens; HASTE relies on the SWA Easy Auth session cookie having SameSite=Lax and on Origin/Referer enforcement at the SWA edge. If you front HASTE with a different reverse proxy or auth layer, validate that equivalent CSRF protection is in place — see §8.2.

5.5 Function App inbound access#

The browser reaches the TiTiler tile server only through the Static Web App and APIM (/api/titiler/*); no HASTE backend calls it directly. The reference deployment therefore applies this baseline to the TiTiler Function App:

Site

Default action

Allowed sources

Main (<app>.azurewebsites.net)

Deny

The APIM VNet integration subnet only (needs the Microsoft.Web service endpoint, which infra/modules/network.bicep enables)

SCM / Kudu (<app>.scm.azurewebsites.net)

Deny

None. The SCM site does not inherit the main-site rules.

The app also requires HTTPS, TLS 1.2 or later on both sites, and has FTP/FTPS disabled.

The baseline is enforced in three places so redeployment cannot widen it:

  • Bicep — infra/modules/functionApp.bicep (allowedInboundSubnetIds, restrictMainSite, restrictScmSite) with the TiTiler wiring in infra/modules/functions.bicep.

  • deploy_apps.sh — before every TiTiler deploy it re-applies the rule and default actions, then verifies the result. An allow rule it does not expect stops the deployment instead of being deleted, so an operator decides what the rule was for.

  • Publishing — a temporary haste-ci-* /32 rule lets the deploying machine reach the locked sites while it publishes (see Network access during deployment).

Operational notes:

  • Direct requests to <app>.azurewebsites.net and <app>.scm.azurewebsites.net return 403 Forbidden. Test TiTiler through the UI or the APIM gateway (/api/titiler/healthz).

  • The Kudu console and log stream are unreachable from the internet. Use Application Insights or Log Analytics, or add a short-lived /32 rule and remove it afterwards.

  • The API and queues Function Apps are not covered by this baseline yet. For them, §3.1 and §8.1 still apply.

# Inspect the effective rules for an app
az functionapp config access-restriction show --resource-group <rg> --name <function-app>

5.6 TiTiler input, APIM surface and identity#

TiTiler opens whatever dataset URL the caller passes, so it is treated as untrusted input on a service with as little reach as possible.

Dataset URLs (api/titilerfuncapi/app/dataset_url.py):

  • Only plain https:// URLs on an exact host list are accepted: the two public open-data hosts the catalog explorer previews, plus the hosts in TITILER_ALLOWED_HOSTS. Bicep and deploy_apps.sh set it to the environment’s blob host. Wildcard domains are not supported.

  • Everything else gets a generic 400 before GDAL is involved. That covers other schemes, local and GDAL virtual-file-system paths, inline documents, archive paths, traversal, credentials, explicit ports, IP literals, control and non-ASCII characters, and fragments. The log records the reason, never the URL, since it can carry a SAS token.

  • The first use of a URL sends one HEAD with redirects disabled. Redirects, non-2xx responses, and hosts resolving to loopback, link-local, metadata or (for public hosts) private addresses are refused. Passing URLs are cached for 10 minutes.

  • Datasets open with the GTiff driver only. GDAL settings that disable plugin drivers, sibling-file probing, PAM, and Python or raw-file VRT bands are forced at startup and per request.

  • Only /cog/tiles/WebMercatorQuad/{z}/{x}/{y} (with its format and scale variants) and /healthz are served.

APIM (infra/modules/apimApis.bicep): get-tiles (GET) is TiTiler’s only operation. The API policy refuses requests with a body (413) and URLs over titilerMaxUrlLength (414), and rate limits tile requests per user (titilerCallsPerClientPerMinute, default 1,200) and in total (titilerCallsPerMinute, default 20,000).

The per-user key is the user’s address without its port. The Static Web App appends the user as ip:port to any X-Forwarded-For the browser sent, and APIM’s front end appends the Static Web App the same way. So on the Static Web App’s product, which accepts only requests the Static Web App signed, the policy uses the second-to-last entry and ignores anything before it. Other callers are keyed on the address APIM sees.

APIM v2 tiers count these limits approximately. An over-limit key is slowed rather than cut off, and throttling can start some way past the configured number, so size them as throttling, not as a hard cap.

Identity and storage (infra/modules/functionApp.bicep, infra/modules/storage.bicep):

  • TiTiler runs from its own host storage account. That account accepts identity auth only, has public blob access off, and is reachable only from the functions subnet.

  • TiTiler does not get the shared user-assigned identity, the account-key /data mount, the Blob Delegator role, or any role on the shared storage account.

  • Bicep never deletes what it stops declaring, so the azd postprovision hook deploy/reconcile-titiler-baseline.ps1 deletes TiTiler role assignments outside its own account and any TiTiler APIM operation other than get-tiles. Provisioning fails if either remains.

Verifying an environment:

pwsh deploy/verify-titiler-baseline.ps1 -EnvName <env>

The script prints PASS or FAIL per check and exits non-zero on any failure. Add -ReportPath <file> for a JSON report. Add -SwaCookie <value> to also test the per-user rate limit through the Static Web App. That test sends up to 3,000 requests and can throttle the signed-in user for a minute, so run it on dev environments only. Both options are described in the script header.


6. Containers and infrastructure#

6.1 Pin Docker base images to digests#

For reproducible and supply-chain-safe builds, pin Docker base images to immutable digests rather than floating tags:

FROM mcr.microsoft.com/azure-functions/python:4-python3.11@sha256:<digest>

Use Dependabot for Docker to keep digests current.

6.2 Run containers as a non-root user#

For production deployments to Azure Batch, AKS, or Container Apps, add a non-root USER directive to each Dockerfile after install steps:

RUN useradd -m -u 10001 hasteuser
USER hasteuser

Verify file permissions on mounted volumes match the non-root UID.

6.3 Use ACR scanning and signed images#

6.4 Docker Compose hardening#

The shipped docker/docker-compose.yml is local-development only. If you adapt it for any networked environment, add:

security_opt:
  - no-new-privileges:true
cap_drop:
  - ALL
read_only: true   # where the workload allows it

And bind ports to 127.0.0.1 explicitly rather than 0.0.0.0.


7. Logging, monitoring, and incident response#

7.1 Enable Application Insights#

az monitor app-insights component create --app haste-insights --location <region> --resource-group <rg>
az functionapp config appsettings set --name <function-app> --resource-group <rg> \
  --settings APPLICATIONINSIGHTS_CONNECTION_STRING="<conn-string>"

Recommended log retention: 90 days minimum for security events; longer if regulatory requirements apply.

7.2 What HASTE logs (and what it does not)#

  • Logs: HTTP method + route, principal userId, job state transitions, errors with stack traces.

  • Does not log: Request bodies, imagery contents, or values of admin settings — by design. If you require auditing of admin-config changes, layer Azure Activity Log or a custom audit trail on top.

7.3 Alerts to configure#

At minimum, alert on:

  • 5xx error rate > 1% for 5 minutes (API availability)

  • Repeated 401/403 from a single principal (credential stuffing / role probing)

  • Spikes in user-management calls (anomalous admin activity)

  • Function App restart loops (potential exploit / crash)

7.4 Incident response — reporting vulnerabilities#

If you discover a vulnerability in HASTE, do not file a public GitHub issue. Report it via:

  • Microsoft Security Response Center: https://aka.ms/SECURITY.md and https://msrc.microsoft.com

  • Encrypted email to secure@microsoft.com (PGP key on the MSRC site)

See SECURITY.md in the repository.


8. Known security limitations and backward-compatibility notes#

This section documents current limitations that customers must understand to configure HASTE securely. Each entry describes the limitation and the operational mitigation you should apply.

8.1 The API trusts the SWA-injected principal header without independent verification#

HASTE’s API trusts the x-ms-client-principal header from Azure Static Web Apps without performing independent signature verification. This is safe only when the Function App is unreachable except through SWA’s linked-backend route. If you deploy the Function App with a publicly reachable endpoint, you must either:

  • Front it with an API gateway that strips and re-injects identity, or

  • Add network-level access restrictions so the Function App accepts traffic only from SWA’s outbound IP ranges, or

  • Use VNet integration with a private endpoint.

See §3.1 for the recommended SWA linked-backend topology.

8.3 CI runners — applies if you fork the repo#

CI runs on GitHub Actions with GitHub-hosted runners (CodeQL and Gitleaks secret scanning); the legacy azure-pipelines.yml, which used a self-hosted pool, has been removed. If you fork HASTE and add self-hosted runners, note that fork pull-request builds can execute arbitrary code on a self-hosted runner. Keep untrusted-fork PRs on GitHub-hosted runners, or restrict workflow triggers to internal branches before exposing the fork.

8.4 Path traversal hardening in chunked uploads#

The chunked file-upload endpoint accepts a file_id parameter and does not yet enforce a strict character allow-list against path-traversal sequences. The endpoint requires authentication and the parameter is constrained by the UI, but operators should not expose the chunked-upload route to untrusted authenticated clients. A character-class allow-list and realpath prefix check are recommended additions if you extend the API.

8.5 Imagery URL allowlist and outbound egress controls#

HASTE enforces an application-layer allowlist on user-supplied imagery URLs. The PutLayer API rejects (HTTP 400) any submission whose preEventImageryUrls or postEventImageryUrls contains a host outside *.blob.core.windows.net (Azure Blob Storage) or *.amazonaws.com (AWS S3, including s3.amazonaws.com and regional subdomains). The UI mirrors this check client-side so users get inline feedback when adding a URL rather than after submitting a layer. The imagery downloader (ImageryDownloader._validate_url) applies the same allowlist as defense-in-depth at fetch time.

This closes the application-layer SSRF risk that previously existed in the imagery-download path: the Function App and Batch workers cannot be coerced into fetching arbitrary user-controlled URLs (which could otherwise reach the Instance Metadata Service at 169.254.169.254, internal VNet addresses, localhost-bound services on the host, or attacker-controlled HTTP endpoints from the server’s network position).

Defense-in-depth recommendation: even with the application-layer allowlist, restrict outbound egress from the Function App and Batch node pool (NSG, Azure Firewall, or Function App outbound IP restrictions) to the specific imagery providers you actually use. This protects against:

  • Future code paths that bypass the allowlist

  • Compromise of an allowlisted hostname (e.g., a hostile Azure Blob container in another tenant)

  • DNS rebinding — the application-layer check validates the hostname string, but the actual TCP connection performs a separate DNS resolution at fetch time. Pinning egress at the network layer to known imagery-provider IP ranges removes that window.

If your imagery sources are a small static set (customer-owned storage accounts, specific public S3 buckets), use that exact set for the egress allowlist as well rather than the broad *.blob.core.windows.net / *.amazonaws.com patterns.

8.6 Open redirect on logout#

The UI’s logout flow validates the redirectPath parameter via sanitizeRedirectPath() (ui/src/util/validation.js) before building the post_logout_redirect_uri: the value is collapsed to a same-origin relative path, so absolute URLs, protocol-relative (//host), backslash-normalized (/\host), and any scheme-bearing input fall back to /. This closes the previously-documented open-redirect; no operator action is required.

8.7 Backward-compatibility notes for upgrades#

  • User-management endpoint scope (post-v1.4.1): The PutUser endpoint was originally gated entirely on the administrators role. A subsequent release narrowed the gate to allow self-service updates only when the caller’s email matches the target’s email, and rejects role-change attempts from non-admins. Customers upgrading from v1.4.0 or earlier should re-test any external integrations that call PutUser on behalf of non-admin users.

  • VITE_AZURE_MAPS_KEY removed (post-v1.4.1): The UI no longer reads VITE_AZURE_MAPS_KEY. Set VITE_AZURE_MAPS_CLIENT_ID to your Function App’s managed-identity client ID. If you ever set VITE_AZURE_MAPS_KEY, rotate the corresponding Azure Maps subscription key, because it was previously baked into the client bundle and may have been distributed.

  • azurite removed from package.json: Developers must now install Azurite globally (npm install -g azurite). No production impact.

  • Imagery URL allowlist enforced (post-v1.4.1): PutLayer now rejects requests whose preEventImageryUrls or postEventImageryUrls contain a host outside *.blob.core.windows.net or *.amazonaws.com. The pre-existing batch-download code silently skipped such URLs, so this change makes the rejection visible at submission time rather than producing failed jobs with empty outputs. Customers with API integrations that submit imagery from other sources must move that imagery into an allowlisted source before upgrading, or the affected layer-creation calls will fail.

8.8 GDAL driver allowlist and ingestion size/type limits#

The shared-runtime build pins GDAL 3.13.3 and builds the GDAL-linked Python bindings against the same native libraries. Existing deployments continue using their currently deployed packages until each consumer receives the verified runtime and passes its startup check; the legacy runtime exception remains open until rollout is complete (see known-vulnerabilities.md Root Cause C and ADR-0004).

  • Shared runtime and driver allowlist. The immutable runtime bundle contains one libgdal and libproj, with GDAL 3.13.3 bindings for osgeo, Rasterio, Fiona, Pyogrio, and PyProj. At process startup hastegeo.core.utils.gdal_security.harden_gdal() verifies those package versions and restricts GDAL/OGR to the drivers HASTE uses (raster GTiff, COG, VRT, JPEG, PNG, MEM; vector GPKG, GeoJSON, Memory) by deregistering every other driver. The check fails closed when HASTE_GDAL_RUNTIME_REQUIRED=true and the runtime is missing or inconsistent.

  • VRT and plugin configuration. Bicep, deploy_apps.sh, Dockerfiles, and local Compose set GDAL_VRT_ENABLE_RAWRASTERBAND=NO, GDAL_VRT_ENABLE_PYTHON=NO, GDAL_DRIVER_PATH=disable, GDAL_DISABLE_READDIR_ON_OPEN=EMPTY_DIR, and CPL_VSIL_CURL_HEADER_FILE_KVP_ENABLED=NO. TiTiler’s shared-runtime policy additionally deregisters VRT; internal mosaic workflows retain it.

  • Ingestion size/type limits. Chunked uploads are capped (HASTE_MAX_UPLOAD_BYTES, default 5 GiB) and the assembled file’s magic bytes must match its declared format before GDAL parses it. Remote imagery fetches are capped (HASTE_MAX_IMAGERY_DOWNLOAD_BYTES, default 8 GiB) and refuse cross-host redirects (SSRF guard). Tune the same-named GitHub Environment configuration variables, then run Update Size Limits. Both deployment and lightweight updates apply these desired values. Bicep declares the caps explicitly; local azd environments must supply matching byte counts before provisioning. The workflow verifies stored settings and samples HTTP worker responses, not queue workers or Batch enforcement. New Batch tasks capture the queues worker’s download cap; existing tasks retain their submitted values. See the operator procedure for reset, rollback, resource budgets, and verification.

Operator action: keep the GDAL hardening settings in any custom GDAL-bearing image and do not add file formats to the pipeline without extending the allowlist in gdal_security.py. If you legitimately need a format outside the allowlist, add its driver explicitly rather than disabling the control.

Each HASTE release documents security-relevant changes in CHANGELOG.md. The rolling list of acknowledged dependency vulnerabilities — fixed, dismissed with rationale, or pending — is maintained at docs/known-vulnerabilities.md.


9. Secure configuration checklist (pre-production)#

Before exposing HASTE to anyone other than its developers, verify:

  • [ ] Function App is reachable only via SWA linked backend (or equivalent access restriction)

  • [ ] The TiTiler Function App’s main site allows only the APIM subnet, and its SCM site denies all traffic (§5.5)

  • [ ] deploy/verify-titiler-baseline.ps1 passes for the environment, and TITILER_ALLOWED_HOSTS lists only hosts you serve imagery from (§5.6)

  • [ ] AAD (Entra ID) is configured as the SWA identity provider; mock auth is disabled

  • [ ] DEVELOPMENT_MODE is not set in any Application Setting

  • [ ] All secrets are in Key Vault or supplied via managed identity (no inline connection strings in source control)

  • [ ] VITE_AZURE_MAPS_KEY is not set; VITE_AZURE_MAPS_CLIENT_ID is set instead

  • [ ] CORS is restricted to your UI’s exact origin (no *)

  • [ ] staticwebapp.config.json defines CSP, HSTS, X-Content-Type-Options, X-Frame-Options

  • [ ] Basic-auth publishing is disabled on the Function App post-deployment

  • [ ] Docker images are pinned to digests; ACR scanning is enabled

  • [ ] Application Insights is enabled with ≥90-day retention

  • [ ] Alerts are configured for 5xx rate, auth failures, and admin-action spikes

  • [ ] Outbound network egress from the Function App is restricted (NSG or Azure Firewall) — see §8.5

  • [ ] All administrators have been reviewed and assigned via least-privilege

  • [ ] The known-limitations section (§8) has been read and the listed operational mitigations applied


10. References#

  • Microsoft Cloud Security Benchmark — https://learn.microsoft.com/security/benchmark/azure/

  • Azure Static Web Apps — Authentication and Authorization — https://learn.microsoft.com/azure/static-web-apps/authentication-authorization

  • Azure Functions — Securing Functions — https://learn.microsoft.com/azure/azure-functions/security-concepts

  • Azure Key Vault references in App Service — https://learn.microsoft.com/azure/app-service/app-service-key-vault-references

  • Azure RBAC for storage — https://learn.microsoft.com/azure/storage/blobs/assign-azure-role-data-access

  • Microsoft Defender for Cloud — Containers — https://learn.microsoft.com/azure/defender-for-cloud/defender-for-containers-introduction

  • MSRC vulnerability reporting — https://msrc.microsoft.com and https://aka.ms/SECURITY.md

  • HASTE Security Policy — https://github.com/microsoft/haste/blob/main/SECURITY.md

  • HASTE CHANGELOG — https://github.com/microsoft/haste/blob/main/CHANGELOG.md

  • HASTE Known Vulnerabilities (rolling) — https://github.com/microsoft/haste/blob/main/docs/known-vulnerabilities.md