How to address common findings
Configuration guidance for externally observable security findings. These are reference examples — verify against your specific infrastructure, dependencies, and CDN configuration before applying.
Scope
This guide covers findings that are externally observable from public sources. It does not cover application-layer vulnerabilities, authenticated access issues, internal infrastructure, source code, or dependency vulnerabilities. Those require separate tooling (DAST, SAST, SCA, penetration testing).
Email authentication
DMARC absent
Observation
_dmarc.<domain> returned NXDOMAIN — no DMARC record published.
Why it matters
Without a DMARC policy, spoofed email from your domain cannot be rejected by receiving mail servers. Enterprise procurement questionnaires routinely check for DMARC enforcement.
Configuration
DNS TXT record — monitoring (start here)
_dmarc.yourdomain.com IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com; ruf=mailto:dmarc-failures@yourdomain.com; fo=1"
DNS TXT record — enforcement (after reviewing reports)
_dmarc.yourdomain.com IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@yourdomain.com; adkim=s; aspf=s"
Verify the fix
dig TXT _dmarc.yourdomain.com +short
Note: A p=none DMARC record resolves the "DMARC absent" finding. Moving to p=reject is the complete remediation.
SPF soft-fail (~all)
Observation
SPF record ends with ~all (soft-fail) instead of -all (hard-fail).
Why it matters
Soft-fail (~all) means unauthorised senders produce a warning, but most mail servers accept the message. Hard-fail (-all) causes rejection.
Configuration
DNS TXT record
yourdomain.com IN TXT "v=spf1 include:_spf.google.com include:sendgrid.net -all"
Verify the fix
dig TXT yourdomain.com +short | grep spf
Note: Do not change to -all until you have verified all sending sources are in the SPF record. Emails from unlisted senders will be rejected.
TLS / SSL configuration
TLS 1.0 / TLS 1.1 supported
Observation
SSL Labs active handshake detected TLS 1.1 (or TLS 1.0) accepted by the server.
Why it matters
TLS 1.0 and 1.1 were deprecated by RFC 8996 (March 2021). Enterprise security reviewers flag this as a policy violation. SSL Labs grades any server supporting TLS 1.1 at B or lower.
Configuration
nginx — ssl_protocols
# nginx.conf or server block ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off;
Apache — SSLProtocol
# httpd.conf or VirtualHost SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...
Verify (command line)
openssl s_client -connect yourdomain.com:443 -tls1_1 # Expected result: handshake should fail if TLS 1.1 is disabled
Verify the fix
openssl s_client -connect yourdomain.com:443 -tls1_1 2>&1 | grep "handshake failure"
Note: If you are running behind a CDN or load balancer (Cloudflare, AWS ALB), configure TLS protocols at the CDN level, not the origin server.
HSTS header absent
Observation
Strict-Transport-Security header not present in the HTTPS response.
Why it matters
Without HSTS, browsers may attempt HTTP connections before being redirected to HTTPS. HSTS tells browsers to always use HTTPS for the domain for a specified period.
Configuration
nginx — add_header
# In server block (HTTPS only) add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
Apache — Header always set
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Next.js — next.config.js headers()
// next.config.js
headers: async () => [
{
source: '/(.*)',
headers: [
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
],
},
],Verify the fix
curl -I https://yourdomain.com | grep -i strict-transport
Note: max-age=63072000 = 2 years. The preload directive requires registration at hstspreload.org — do not add preload until you are confident all subdomains support HTTPS.
HTTP security headers
Content-Security-Policy absent
Observation
Content-Security-Policy header not present in HTTP response.
Why it matters
CSP controls which resources browsers are allowed to load, reducing XSS attack surface. Enterprise security reviewers check for CSP presence as a hygiene indicator.
Configuration
nginx — start with report-only
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; report-uri /csp-report" always;
nginx — enforcement (after tuning)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted-cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
Verify the fix
curl -I https://yourdomain.com | grep -i content-security-policy
Note: CSP configuration depends heavily on your application. There is no universal policy — start with report-only and tune based on the violation reports.
X-Frame-Options / frame-ancestors absent
Observation
No clickjacking protection header in HTTP response.
Why it matters
Without X-Frame-Options or CSP frame-ancestors, the page can be embedded in an iframe on a third-party site.
Configuration
nginx
add_header X-Frame-Options "SAMEORIGIN" always; # Or, preferably, as part of CSP: # add_header Content-Security-Policy "... frame-ancestors 'self'" always;
Verify the fix
curl -I https://yourdomain.com | grep -i x-frame
Note: If you have CSP with frame-ancestors, X-Frame-Options is redundant but harmless.
DNS hygiene
CAA record not configured
Observation
No CAA record found for the domain — any certificate authority can issue certificates.
Why it matters
A CAA record restricts which CAs are authorised to issue certificates for your domain. Without one, any CA can issue a certificate, enabling some misissuance scenarios.
Configuration
DNS — Let's Encrypt
yourdomain.com IN CAA 0 issue "letsencrypt.org" yourdomain.com IN CAA 0 issuewild "letsencrypt.org" yourdomain.com IN CAA 0 iodef "mailto:security@yourdomain.com"
DNS — DigiCert
yourdomain.com IN CAA 0 issue "digicert.com" yourdomain.com IN CAA 0 iodef "mailto:security@yourdomain.com"
Verify the fix
dig CAA yourdomain.com +short
Note: Multiple CAA records can be added if you use multiple CAs. The iodef value specifies where to send policy violation notifications.
DNSSEC not enabled
Observation
DNS responses for the domain are not signed with DNSSEC.
Why it matters
DNSSEC prevents DNS cache poisoning by cryptographically signing DNS responses. Increasingly required in enterprise procurement questionnaires, particularly in financial and public sector contexts.
Configuration
Hetzner DNS (console)
Settings → DNS Zones → [your zone] → Enable DNSSEC
Cloudflare
DNS → Settings → DNSSEC → Enable
Route 53
Hosted zones → [zone] → Enable DNSSEC signing
Verify the fix
dig DS yourdomain.com @8.8.8.8 +short
Note: DNSSEC configuration is handled at the registrar level — the DNS provider must support it. Not all registrars support DNSSEC.
Version disclosure & exposed paths
Server version string in response header
Observation
Response header: Server: nginx/1.x.x — exact version disclosed.
Why it matters
Disclosing the exact server version enables targeted CVE lookups. Low severity on its own; combined with other findings it reduces the work required for targeted attacks.
Configuration
nginx — server_tokens
# nginx.conf — http or server block server_tokens off;
Apache — ServerTokens
# httpd.conf ServerTokens Prod ServerSignature Off
Verify the fix
curl -I https://yourdomain.com | grep -i server
Note: server_tokens off suppresses the version from the Server header and nginx error pages. The header will still say "nginx" — to remove it entirely requires a custom build or third-party module.
/.env or /.git/config exposed — HTTP 200
Observation
GET /.env (or /.git/config) returned HTTP 200 with non-empty response body.
Why it matters
.env files typically contain database credentials, API keys, and secret tokens. .git/config exposes repository information. HTTP 200 means the file is readable by any client.
Configuration
nginx — deny .env and .git
location ~ /\.env {
deny all;
return 404;
}
location ~ /\.git {
deny all;
return 404;
}Apache — .htaccess
<FilesMatch "^\.(env|git|htaccess|htpasswd|DS_Store)">
Require all denied
</FilesMatch>Caddy
@dotfiles path_regexp ^\/\.(.+) respond @dotfiles 404
Verify the fix
curl -o /dev/null -s -w "%{http_code}" https://yourdomain.com/.env
# Expected: 404 or 403Note: Rotating exposed credentials is more urgent than fixing the HTTP exposure. An exposed .env file with database credentials is a confirmed data exposure event.
Check which of these apply to your domain
Free passive assessment. Results in 90 seconds. No credentials, no installation.
Configuration examples are provided as reference guidance. They may need adaptation for your specific infrastructure, framework, or CDN. This page does not constitute security consulting. Verify all changes in a staging environment before deploying to production. See methodology for the evidence basis of each finding type.