Skip to content

21 — Security Penetration Testing (OWASP)

PRD Document · Savoy Signature Hotels — Multi-Site Headless Platform
Version: 1.0 · Date: 2026-03-19
Related docs: 15_Security_and_Data_Protection.md, 02_Infrastructure_and_Environments.md, 08_API_Contracts.md, 18_QA_Pipeline_and_Testing.md


This document defines the penetration testing strategy for the Savoy Signature platform. It maps the OWASP Top 10 (2021) to the project’s headless architecture, catalogs every mandatory test, specifies the CI/CD integration points, and establishes the security gate that must pass before any environment goes live.

The headless architecture (Next.js + Umbraco + Cloudflare + Azure) shifts the attack surface compared to a monolithic CMS. The public-facing layer is the CDN and the Node.js SSR app — the CMS backoffice and database are network-isolated. This document addresses both layers.


Azure — Umbraco

Azure — Next.js

Edge (Cloudflare)

Public Internet

HTTPS

Cache Miss

fetch

POST

Attacker

WAF + Bot Mgmt

Rate Limiting

Edge Cache

Middleware (Auth Gate + Routing)

api/webhooks/umbraco

api/gate/login

api/draft

api/cloudflare/stats

SSR Pages

Delivery API v2

api/auth/validate-backoffice

Backoffice (umbraco)

Azure SQL

Blob Storage

EndpointMethodAuthPurposeRisk Level
/* (SSR pages)GETNone (public) / Auth Gate (DEV)Hotel websitesMedium
/api/webhooks/umbracoPOSTHMAC SHA-256Cache purge triggerHigh
/api/gate/loginPOSTCredentialsDEV auth gate loginHigh
/api/gate/logoutPOSTCookieDEV auth gate logoutLow
/api/draftGETSecret query paramContent preview modeMedium
/api/draft/disableGETSecret query paramDisable previewLow
/api/cloudflare/statsGETAPI key header/paramDashboard stats proxyMedium
/umbraco/delivery/api/v2/*GETAPI key (optional)Content Delivery APIHigh
/umbracoGET/POSTAzure AD SSO + MFACMS backofficeCritical
/api/auth/validate-backofficePOSTX-Gate-Key headerGate credential checkHigh
EndpointAccessProtection
Azure SQLPrivate endpoint (VNet)TDE, AAD auth
Blob StoragePrivate endpoint (VNet)TDE, SAS tokens
Key VaultManaged Identity onlyRBAC, audit log

3. OWASP Top 10 (2021) — Platform Mapping

Section titled “3. OWASP Top 10 (2021) — Platform Mapping”

Each OWASP category is mapped to the Savoy architecture with specific test procedures.

#TestTargetProcedureSeverity
A01.1Cross-site data leakageDelivery APIRequest content without Start-Item header — verify response is empty or error, not cross-site dataCritical
A01.2Start-Item spoofingDelivery APISend Start-Item: savoy-palace but request URLs belonging to royal-savoy — must return 404Critical
A01.3Backoffice public access/umbracoAttempt to load backoffice from public IP (not VPN/office) — must be blocked by Cloudflare Zero Trust or Azure IP restrictionCritical
A01.4RBAC bypassBackofficeLogin as “Savoy Palace Editor” and attempt to edit content under royal-savoy siteRoot — must be deniedHigh
A01.5Auth Gate bypassDEV middlewareAccess DEV pages without __dev_gate cookie — must redirect to /gate/loginHigh
A01.6Auth Gate JWT tamperingDEV middlewareModify JWT payload (change sub email) without re-signing — must rejectHigh
A01.7Auth Gate expired tokenDEV middlewareUse JWT with exp in the past — must redirect to loginMedium
A01.8Preview mode without secret/api/draftRequest /api/draft?secret=wrong&path=/ — must return 401High
A01.9Webhook without signature/api/webhooks/umbracoPOST without X-Webhook-Signature — must return 401High
A01.10Stats endpoint without key/api/cloudflare/statsRequest without x-dashboard-key header — must return 401Medium
A01.11CORS origin bypassDelivery APISend Origin: https://evil.com — response must NOT include Access-Control-Allow-Origin: https://evil.comHigh
A01.12Direct Azure URL access*.azurewebsites.netAccess Next.js via direct Azure URL — must redirect 301 to Cloudflare domainMedium
#TestTargetProcedureSeverity
A02.1TLS versionAll domainsVerify TLS 1.2+ only — TLS 1.0/1.1 must be rejectedHigh
A02.2HSTS headerAll domainsVerify Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadHigh
A02.3SSL certificate chainAll domainsVerify valid chain, no mixed content, no expired certsHigh
A02.4Weak ciphersAll domainsScan for deprecated ciphers (RC4, DES, 3DES, NULL) — must not be presentHigh
A02.5HMAC timing attackWebhookVerify crypto.timingSafeEqual() is used (code review) — already implementedMedium
A02.6JWT algorithm confusionAuth GateSend JWT with alg: none — must be rejectedHigh
A02.7Secrets in client bundleFrontend JSAnalyze Next.js client bundle for leaked env vars (API keys, secrets) — must find noneCritical
A02.8Secrets in Docker imageDockerInspect Docker image layers for embedded secrets — must find noneHigh
A02.9Database encryptionAzure SQLVerify TDE is enabled on Azure SQL — check via Azure Portal or CLIHigh
A02.10Blob Storage encryptionAzure BlobVerify TDE + HTTPS-only access — public anonymous access must be disabledHigh
#TestTargetProcedureSeverity
A03.1XSS via CMS contentSSR pagesInject <script>alert(1)</script> in CMS Rich Text field — verify CSP blocks executionHigh
A03.2XSS via URL parametersSSR pagesInject script in query params (?q=<script>alert(1)</script>) — must not executeHigh
A03.3Stored XSS via SVG uploadMedia libraryUpload SVG with <script> tag — must be sanitized or strippedHigh
A03.4SQL injection via Delivery APIAPI filtersInject SQL in filter, sort, skip query params — verify EF Core ORM parameterizesHigh
A03.5Path traversalStart-Item headerSend Start-Item: ../../../etc/passwd — must return error, not file contentsHigh
A03.6Header injectionNext.js middlewareInject CRLF in custom headers (x-site-key: savoy\r\nX-Evil: hack) — must not split headersMedium
A03.7JSON injection in webhookWebhook handlerSend malformed JSON payload — must return 400, not crashMedium
A03.8Template injectionBlock List labelsInject {blText: ../../etc/passwd} in Block List label UFM — verify sandboxed parsingLow
A03.9NoSQL / OData injectionDelivery APIInject OData operators in filter params — verify API rejects or sanitizesMedium
#TestTargetProcedureSeverity
A04.1Rate limiting on login/api/gate/loginSend 100 login attempts in 1 minute — verify Cloudflare or app rate limits kick inHigh
A04.2Rate limiting on forms/api/forms/*Send 20 form submissions in 1 minute — verify 429 response after threshold (5 req/5min per IP)High
A04.3Rate limiting on search/api/searchSend 50 search requests in 1 minute — verify 429 after threshold (20 req/min per IP)Medium
A04.4Account lockoutGate loginSend 10 failed login attempts — verify account lockout via ASP.NET IdentityHigh
A04.5Credential enumerationGate loginTest valid email + wrong password vs invalid email — response must be identical (no enumeration)High
A04.6Open redirect/api/gate/login?returnUrl=Send returnUrl=https://evil.com — must redirect to / not to external domainHigh
A04.7Open redirect via preview/api/draft?path=Send path=https://evil.com — must return 400High
A04.8Webhook replay attackWebhookReplay a previously captured valid webhook payload — verify idempotency or timestamp checkMedium
A04.9Excessive data exposureDelivery APIRequest content and verify response does NOT include sensitive fields (passwords, internal IDs, editor emails)High
#TestTargetProcedureSeverity
A05.1Security headers auditAll domainsVerify presence of: X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, CSPHigh
A05.2CSP policyNext.jsVerify Content-Security-Policy header blocks inline scripts (or uses nonces) — current unsafe-inline + unsafe-eval in script-src is a known gapHigh
A05.3Server information leakageAll domainsCheck Server, X-Powered-By, X-AspNet-Version headers — must not reveal stack detailsMedium
A05.4Directory listingAzure BlobAttempt to list blob container contents — public listing must be disabledHigh
A05.5Debug modeAll environmentsVerify NODE_ENV=production in STAGE/QA/PROD — no debug endpoints exposedHigh
A05.6Default credentialsUmbracoVerify Admin1234! (dev default) is NOT used in STAGE/QA/PRODCritical
A05.7TLS_REJECT_UNAUTHORIZEDProductionVerify NODE_TLS_REJECT_UNAUTHORIZED=0 is NOT set in any deployed environmentCritical
A05.8Unnecessary HTTP methodsDelivery APISend PUT, DELETE, PATCH to Delivery API — must return 405 Method Not AllowedMedium
A05.9CORS wildcardDelivery APIVerify CORS does NOT use Access-Control-Allow-Origin: * — must be specific originsHigh
A05.10Error page information500 errorsTrigger server errors — verify no stack traces, file paths, or internal details in responseMedium
A05.11Cloudflare WAF activeAll domainsVerify WAF Managed Ruleset is enabled and blocking known exploitsHigh
A05.12Permissions-Policy headerAll domainsVerify restrictive Permissions-Policy (camera, microphone, geolocation blocked)Medium

A06 — Vulnerable and Outdated Components

Section titled “A06 — Vulnerable and Outdated Components”
#TestTargetProcedureSeverity
A06.1npm auditFrontendRun pnpm audit — zero high/critical vulnerabilitiesHigh
A06.2NuGet auditUmbracoRun dotnet list package --vulnerable — zero high/criticalHigh
A06.3Docker base imagenode:20-alpineScan with trivy image — zero critical CVEsHigh
A06.4Umbraco versionCMSVerify running latest patch of Umbraco 17 — check release notes for security fixesMedium
A06.5Next.js versionFrontendVerify running latest patch of Next.js 16 — check security advisoriesMedium
A06.6Outdated JS librariesClient bundleAnalyze client-side JS for known vulnerable libraries (e.g., outdated jQuery, lodash)Medium

A07 — Identification and Authentication Failures

Section titled “A07 — Identification and Authentication Failures”
#TestTargetProcedureSeverity
A07.1MFA enforcementBackofficeAttempt login without MFA — must be blocked at Azure AD levelCritical
A07.2Session fixationAuth GateCheck if __dev_gate cookie changes after successful login (new JWT issued)High
A07.3Cookie security flagsAuth GateVerify __dev_gate cookie has HttpOnly, Secure, SameSite=LaxHigh
A07.4API key in URLStats endpointVerify API key is passed via header (x-dashboard-key), not query string in production (query string appears in logs)Medium
A07.5Password policyUmbracoVerify ASP.NET Identity enforces minimum complexity (length, special chars)Medium
A07.6JWT expirationAuth GateVerify JWT exp claim is validated — expired tokens rejectedHigh
A07.7Logout invalidationAuth GateAfter logout, verify old JWT cookie is cleared and cannot be reusedMedium

A08 — Software and Data Integrity Failures

Section titled “A08 — Software and Data Integrity Failures”
#TestTargetProcedureSeverity
A08.1Webhook signature bypassWebhookModify payload body after signing — verify HMAC check failsCritical
A08.2Subresource IntegrityCDN scriptsVerify third-party scripts (GTM, reCAPTCHA, Cookiebot) use SRI hashes or are loaded from trusted originsMedium
A08.3CI/CD pipeline integrityAzure DevOpsVerify pipeline YAML is protected — only maintainers can modify .azure/pipelines/High
A08.4Package integritypnpmVerify pnpm-lock.yaml is committed and --frozen-lockfile used in CIMedium
A08.5Docker image provenanceACRVerify Docker images are pulled from trusted base (node:20-alpine official)Medium

A09 — Security Logging and Monitoring Failures

Section titled “A09 — Security Logging and Monitoring Failures”
#TestTargetProcedureSeverity
A09.1Failed login loggingAuth GateTrigger 5 failed logins — verify events appear in Application InsightsHigh
A09.2Webhook rejection loggingWebhookSend unsigned webhook — verify 401 is logged with source IPHigh
A09.3Azure activity logKey VaultAccess Key Vault secret — verify audit log captures the access eventMedium
A09.4WAF loggingCloudflareTrigger a WAF rule (e.g., SQLi attempt) — verify it appears in Cloudflare Security EventsMedium
A09.5Alerting on anomaliesAllVerify alerts fire when error rate exceeds 1% (5xx) for 5 minutesMedium
A09.6No sensitive data in logsApplication InsightsReview logs — verify no passwords, API keys, or PII in log entriesHigh

A10 — Server-Side Request Forgery (SSRF)

Section titled “A10 — Server-Side Request Forgery (SSRF)”
#TestTargetProcedureSeverity
A10.1SSRF via media proxyNext.js rewritesManipulate /media/:path* rewrite — attempt to access internal Azure resources (169.254.169.254 metadata)Critical
A10.2SSRF via preview path/api/draft?path=Send path=http://169.254.169.254/metadata — must be rejected (path validation exists)High
A10.3SSRF via image loaderCloudflare image loaderManipulate image URL to point to internal resource — verify loader restricts to allowed domainsHigh
A10.4SSRF via webhook URLWebhook handlerIf webhook triggers fetch to external URL, verify URL is validated against allowlistMedium

Beyond OWASP Top 10, these tests are specific to the Savoy headless architecture.

#TestProcedureSeverity
CF.1WAF bypassTest known bypass techniques for Cloudflare Managed Ruleset — verify rules are currentHigh
CF.2Bot detectionUse automated tools (Selenium, Puppeteer) — verify Super Bot Fight Mode challenges or blocksMedium
CF.3DDoS resilienceVerify DDoS protection is active (Cloudflare dashboard, not actual attack)High
CF.4Cache poisoningInject Host, X-Forwarded-Host, X-Original-URL headers — verify cache key is not poisonedHigh
CF.5Origin IP exposureSearch for origin IP in DNS history, Shodan, Censys — verify origin is not directly reachableHigh
#TestProcedureSeverity
MS.1Cross-site content accessAs authenticated user of site A, attempt to read/modify content of site B via APICritical
MS.2Cross-site cookie scopeVerify __dev_gate cookie domain scope doesn’t leak to unrelated subdomainsMedium
MS.3Theme/asset isolationVerify one site’s assets cannot reference another site’s resourcesLow
MS.4Shared Content securityVerify Shared Content node is read-only for hotel-scoped editorsHigh
#TestProcedureSeverity
API.1Pagination abuseRequest ?skip=0&take=999999 — verify server enforces max page sizeMedium
API.2Filter injectionTest filter parameters with OData operators ($filter, eq, ne, or) — verify sandboxedMedium
API.3Draft content leakageWithout preview mode, request unpublished content — must return 404High
API.4API key enumerationBrute-force API key — verify rate limiting or lockoutMedium
API.5Response size limitRequest deeply nested Block List content — verify response size is boundedLow

4.4 Form Security (when Forms module is live)

Section titled “4.4 Form Security (when Forms module is live)”
#TestProcedureSeverity
FORM.1CSRF protectionSubmit form without CSRF token — must be rejectedHigh
FORM.2File upload validationUpload .php, .exe, .html as form attachment — must be rejected or sanitizedCritical
FORM.3File size limitUpload oversized file (100MB+) — must be rejected with clear errorMedium
FORM.4GDPR consent requiredSubmit form without consent checkbox — must be rejectedHigh
FORM.5Email injectionInject CRLF in form email fields (user@example.com\r\nBcc:evil@attacker.com) — must be sanitizedHigh
FORM.6Spam / bot protectionSubmit form without reCAPTCHA token — must be rejectedMedium
FORM.7PII in responseSubmit form — verify response does NOT echo back submitted PIIMedium

These are security gaps identified in the current codebase that must be resolved before production go-live.

#GapCurrent StateRequired StatePriority
GAP.1No CSP headerNot configured in next.config.ts or middlewareStrict CSP with nonces (remove unsafe-inline, unsafe-eval)Critical
GAP.2No security headersMissing X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-PolicyAll headers configured in next.config.tsCritical
GAP.3No HSTS headerNot setStrict-Transport-Security: max-age=63072000; includeSubDomains; preloadHigh
GAP.4Rich Text rendered as raw HTMLCMS content rendered with raw HTML insertion (trusted source)Acceptable for trusted CMS content — document as known risk, add CSP as mitigationMedium
GAP.5No rate limiting on API routesOnly Cloudflare rate limits (if configured)Add application-level rate limiting on /api/gate/login (5 req/min), /api/webhooks (10 req/min)High
GAP.6Stats API key in query param?key= accepted alongside headerRemove query param option — keys in URLs appear in server logs and browser historyMedium
GAP.7No webhook replay protectionSame payload can be replayedAdd timestamp check — reject payloads older than 5 minutesMedium
GAP.8NODE_TLS_REJECT_UNAUTHORIZED=0Set in .env.localEnsure NOT set in any deployed environment (STAGE/QA/PROD)Critical
GAP.9Default credentials in dev configAdmin1234! in appsettings.Development.jsonVerify these credentials are NOT used in any deployed environmentCritical
GAP.10No API key rotationStatic UMBRACO_API_KEY, REVALIDATE_SECRETDocument rotation procedure, implement Key Vault rotation scheduleMedium

PhaseWhenScopeWho
Per PREvery PR to developAutomated: pnpm audit, lint, typecheckCI pipeline (automatic)
Per DeployEvery push to deploy/devAutomated: dependency scan, header checkCI pipeline (automatic)
Pre-Go-LiveBefore first production deployFull pentest — ALL tests in this documentSecurity team / external
QuarterlyEvery 3 months after go-liveFull OWASP scan + dependency auditSecurity team
On IncidentAfter any security incidentTargeted re-test of affected areaSecurity team
On Major ReleaseNew Umbraco/Next.js major versionFull pentest — focus on new attack surfaceSecurity team

6.2 Pre-Go-Live Checklist (MANDATORY — Hard Gate)

Section titled “6.2 Pre-Go-Live Checklist (MANDATORY — Hard Gate)”

Production deployment is blocked until ALL items pass.

Infrastructure:

  • Cloudflare WAF Managed Ruleset active on all production domains
  • Cloudflare Bot Fight Mode enabled
  • Cloudflare rate limiting rules configured (/api/forms/*, /api/search)
  • TLS 1.2+ enforced, TLS 1.0/1.1 disabled
  • Azure SQL TDE enabled
  • Azure Blob Storage — public access disabled
  • Key Vault — Managed Identity access only (no access policies)
  • VNet private endpoints for SQL and Blob active
  • Origin IP not discoverable (Cloudflare Always Online off, no DNS history leaks)

Application:

  • All security headers configured (CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy)
  • CSP policy tightened — unsafe-eval removed, unsafe-inline replaced with nonces
  • NODE_TLS_REJECT_UNAUTHORIZED=0 not present in production config
  • Default credentials (Admin1234!) not present in production
  • No secrets in Next.js client bundle (verified via bundle analysis)
  • No secrets in Docker image layers (verified via docker history / trivy)
  • NODE_ENV=production in all deployed environments
  • Auth Gate enabled for DEV/STAGE/QA, disabled for PROD
  • GATE_ENABLED=false confirmed in production App Settings

Authentication & Access:

  • Backoffice (/umbraco) blocked from public IPs
  • Azure AD SSO + MFA enforced for all backoffice users
  • RBAC verified — hotel editors cannot access other hotels’ content
  • API key authentication enabled for Delivery API in production

API Security:

  • CORS restricted to production domains only
  • Webhook HMAC verification working end-to-end
  • Rate limiting active on login, forms, search endpoints
  • Delivery API rejects requests without valid Start-Item header
  • All API endpoints return generic errors (no stack traces)

Compliance:

  • Cookiebot configured and blocking third-party scripts before consent
  • Form submissions include mandatory GDPR consent
  • PII auto-deletion scheduled (90 days)
  • Privacy Policy page published on all 8 sites

Scanning:

  • pnpm audit — zero high/critical vulnerabilities
  • dotnet list package --vulnerable — zero high/critical
  • Docker image scan (trivy) — zero critical CVEs
  • SecurityHeaders.com grade: A or higher
  • SSL Labs grade: A+
  • OWASP ZAP baseline scan — zero high alerts

Add these stages to .azure/pipelines/deploy-dev.yml:

Lint + TypeCheck

Security Audit

Unit Tests

Build

Image Scan

Header Check

Deploy

Smoke + Security

StageToolCommandBlocks Deploy?
Dependency Auditpnpm auditpnpm audit --audit-level=highYes (high/critical)
NuGet Auditdotnetdotnet list package --vulnerable --include-transitiveYes (high/critical)
Docker Image ScanTrivytrivy image --severity HIGH,CRITICAL acrsavoydev.azurecr.io/savoy-nextjs:latestYes (critical)
Bundle Analysiscustom scriptScan .next/static/ for leaked env vars (grep for UMBRACO_API_KEY, REVALIDATE_SECRET, etc.)Yes
Header Verificationcurl + scriptVerify all security headers present on deployed URLWarning
OWASP ZAP BaselineZAPzap-baseline.py -t $DEPLOY_URL -r report.htmlWarning (report-only initially, blocking after hardening)
Terminal window
# Dependency audit (CI stage)
pnpm audit --audit-level=high
# NuGet vulnerability check (CI stage)
~/.dotnet/dotnet list apps/cms/Savoy.Cms/Savoy.Cms.csproj package --vulnerable
# Docker image scan (after build)
trivy image --severity HIGH,CRITICAL --exit-code 1 acrsavoydev.azurecr.io/savoy-nextjs:latest
# Bundle secret scan (after build)
grep -r "UMBRACO_API_KEY\|REVALIDATE_SECRET\|CLOUDFLARE_API_TOKEN\|GATE_SECRET" \
apps/web/.next/static/ && echo "FAIL: SECRETS FOUND IN BUNDLE" && exit 1 || echo "PASS: Clean"
# Security headers check (after deploy)
HEADERS=$(curl -sI "$DEPLOY_URL")
MISSING=0
echo "$HEADERS" | grep -qi "x-frame-options" || { echo "MISSING: X-Frame-Options"; MISSING=1; }
echo "$HEADERS" | grep -qi "x-content-type-options" || { echo "MISSING: X-Content-Type-Options"; MISSING=1; }
echo "$HEADERS" | grep -qi "strict-transport-security" || { echo "MISSING: HSTS"; MISSING=1; }
echo "$HEADERS" | grep -qi "content-security-policy" || { echo "MISSING: CSP"; MISSING=1; }
echo "$HEADERS" | grep -qi "referrer-policy" || { echo "MISSING: Referrer-Policy"; MISSING=1; }
[ $MISSING -eq 0 ] && echo "PASS: All security headers present"
# OWASP ZAP baseline (after deploy)
docker run --rm -v $(pwd):/zap/wrk owasp/zap2docker-stable zap-baseline.py \
-t "$DEPLOY_URL" -r zap-report.html -l WARN

ToolPurposeInstallationFrequency
OWASP ZAPAutomated web scanner + proxybrew install zaproxyPer deploy + quarterly
Burp Suite CommunityManual interception and testingDownload from PortSwiggerPre-go-live + quarterly
nucleiTemplate-based vulnerability scannerbrew install nucleiWeekly (automated)
niktoWeb server scannerbrew install niktoPre-go-live + quarterly
testssl.shTLS/SSL configuration auditbrew install testsslPre-go-live + monthly
trivyContainer image CVE scannerbrew install trivyPer Docker build (CI)
SecurityHeaders.comHTTP header auditOnline toolPer deploy
SSL LabsSSL certificate + config gradingOnline toolMonthly
Shodan / CensysOrigin IP discovery checkOnline toolPre-go-live + quarterly

For pre-go-live and quarterly pentests, follow this workflow:

1. Reconnaissance
|-- DNS enumeration (subdomains, MX, TXT records)
|-- Port scanning (Nmap — verify only 443 exposed via Cloudflare)
|-- Technology fingerprinting (Wappalyzer)
+-- Origin IP discovery (Shodan, Censys, DNS history)
2. Automated Scanning
|-- OWASP ZAP full scan (authenticated + unauthenticated)
|-- nuclei scan (community templates)
|-- nikto server scan
|-- testssl.sh TLS audit
+-- trivy image scan
3. Manual Testing (per OWASP section above)
|-- A01 — Access control tests (cross-site, RBAC, auth bypass)
|-- A02 — Crypto tests (TLS, JWT, secrets)
|-- A03 — Injection tests (XSS, SQLi, SSRF)
|-- A04 — Design flaws (rate limiting, enumeration)
|-- A05 — Misconfiguration (headers, debug, defaults)
+-- A06-A10 — Remaining OWASP categories
4. Reporting
|-- Document all findings with severity (Critical/High/Medium/Low)
|-- Provide reproduction steps for each finding
|-- Prioritize remediation timeline
+-- Re-test after fixes
Terminal window
# -- Reconnaissance -------------------------------------------------------
nslookup -type=ANY savoysignature.com
dig +short savoysignature.com
nmap -sV -T4 savoysignature.com
# -- TLS Audit ------------------------------------------------------------
testssl --severity HIGH https://savoysignature.com
# -- OWASP ZAP Scans ------------------------------------------------------
# Baseline (passive — safe)
zap-baseline.py -t https://savoy.dev-signature.wycreative.com -r baseline.html
# Full scan (active — runs attacks, use on DEV only)
zap-full-scan.py -t https://savoy.dev-signature.wycreative.com -r full-scan.html
# API scan (Delivery API)
zap-api-scan.py -t https://savoy.dev-cms.wycreative.com/umbraco/delivery/api/v2 \
-f openapi -r api-scan.html
# -- nuclei ----------------------------------------------------------------
nuclei -u https://savoy.dev-signature.wycreative.com -severity high,critical -o nuclei-report.txt
# -- nikto -----------------------------------------------------------------
nikto -h https://savoy.dev-cms.wycreative.com -o nikto-report.html -Format htm
# -- Docker Image Scan -----------------------------------------------------
trivy image --severity HIGH,CRITICAL acrsavoydev.azurecr.io/savoy-nextjs:latest
# -- Dependency Audit ------------------------------------------------------
pnpm audit --audit-level=high
~/.dotnet/dotnet list apps/cms/Savoy.Cms/Savoy.Cms.csproj package --vulnerable
# -- Security Headers ------------------------------------------------------
curl -sI https://savoy.dev-signature.wycreative.com \
| grep -iE "x-frame|csp|hsts|x-content|referrer|permissions"
# -- Bundle Secret Scan ----------------------------------------------------
grep -rn "UMBRACO_API_KEY\|REVALIDATE_SECRET\|CLOUDFLARE_API_TOKEN\|GATE_SECRET" \
apps/web/.next/static/
# -- SSL Labs --------------------------------------------------------------
# Use https://www.ssllabs.com/ssltest/ — target: A+ grade

SeverityDescriptionSLA (Fix Time)Blocks Go-Live?
CriticalRemote code execution, auth bypass, data breach, secret exposure24 hoursYes
HighXSS, CSRF, IDOR, missing security headers, weak crypto72 hoursYes
MediumInformation disclosure, rate limiting gaps, session issues2 weeksNo (but must fix before next quarterly audit)
LowMinor info leak, verbose errors, missing best practicesNext sprintNo

Each pentest report must follow this structure:

# Security Pentest Report — Savoy Signature
**Date:** YYYY-MM-DD
**Tester:** [Name / Company]
**Scope:** [DEV / STAGE / PROD] — [URLs tested]
**Tools Used:** [ZAP, Burp, nuclei, etc.]
## Executive Summary
- Total findings: X (Y Critical, Z High, W Medium, V Low)
- Go-live recommendation: PASS / FAIL
## Findings
### [SEVERITY] Finding Title
- **OWASP Category:** A0X
- **Test ID:** A0X.Y (from this document)
- **Target:** [URL / endpoint]
- **Description:** [What was found]
- **Reproduction Steps:** [Step-by-step]
- **Evidence:** [Screenshot / curl command / response]
- **Impact:** [What an attacker could do]
- **Remediation:** [How to fix]
- **Status:** Open / In Progress / Fixed / Accepted Risk
## Remediation Summary
| # | Finding | Severity | Status | Fix Date |
|---|---------|----------|--------|----------|

Cloudflare’s security features will interfere with penetration testing. Follow this procedure:

  1. Whitelist tester IP in Cloudflare WAF > Tools > IP Access Rules > Allow
  2. Disable Bot Fight Mode temporarily (or whitelist scanner UA)
  3. Disable rate limiting for tester IP (or increase thresholds)
  4. Note: Some tests must target the origin directly (*.azurewebsites.net) to bypass Cloudflare — document which
Test ScopeTarget URLCloudflare State
WAF bypass testsProduction Cloudflare domainWAF ON (testing the WAF itself)
Application-level testsDirect Azure URL or whitelisted IPWAF bypassed (testing the app)
TLS testsProduction Cloudflare domainFull proxy ON
Origin exposure testsShodan / Censys / DNS historyN/A
  1. Remove IP whitelist from WAF rules
  2. Re-enable Bot Fight Mode
  3. Restore rate limiting rules
  4. Verify WAF event log shows expected blocks from test traffic

  • All tests from Section 3 (OWASP Top 10) executed and documented
  • All tests from Section 4 (Platform-Specific) executed and documented
  • All gaps from Section 5 resolved or accepted with documented risk
  • Pre-Go-Live Checklist (Section 6.2) — 100% pass
  • CI/CD pipeline includes automated security stages (Section 7)
  • SecurityHeaders.com grade: A or higher
  • SSL Labs grade: A+
  • OWASP ZAP baseline scan — zero high/critical alerts
  • pnpm audit and dotnet list package --vulnerable: zero high/critical
  • Docker image scan: zero critical CVEs
  • Pentest report delivered with all findings documented (Section 10)
  • All Critical and High findings fixed before production deploy
  • Quarterly pentest schedule established and documented

Next document: Return to 15_Security_and_Data_Protection.md for architecture-level security controls.