Remediation

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 authenticationTLS / SSL configurationHTTP security headersDNS hygieneVersion disclosure & exposed paths

Email authentication

HIGH

DMARC absent

source: DNS lookupref: NIS2 Art. 21(2)(d)

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.

MEDIUM

SPF soft-fail (~all)

source: DNS lookupref: NIS2 Art. 21(2)(d)

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

HIGH

TLS 1.0 / TLS 1.1 supported

source: SSL Labs APIref: GDPR Art. 32(1)(a)

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.

MEDIUM

HSTS header absent

source: HTTP headersref: NIS2 Art. 21(2)(e)

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

MEDIUM

Content-Security-Policy absent

source: HTTP headersref: GDPR Art. 32(1)(b)

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.

LOW

X-Frame-Options / frame-ancestors absent

source: HTTP headersref: ISO 27001 A.8.28

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

MEDIUM

CAA record not configured

source: DNS lookupref: ISO 27001 A.8.24

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.

LOW

DNSSEC not enabled

source: DNS lookupref: NIS2 Art. 21(2)(h)

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

LOW

Server version string in response header

source: HTTP headersref: ISO 27001 A.8.8

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.

HIGH

/.env or /.git/config exposed — HTTP 200

source: HTTP path proberef: ISO 27001 A.8.12

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 403

Note: 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.

Assess your domain →

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.