Security

This page, audited

None of these numbers are written by hand. Every one is read at build time from the lockfile, the manifest, the pnpm policy, the workflow files or git.

At build time

  • Commit12a0b11
  • Signaturevalid
  • Built2026-10-16 17:54 UTC
  • Node24.20.0
  • pnpm11.26.0
  • Direct dependencies11
  • Total resolved478
  • Floating ranges0
  • Release quarantine7 days
  • Build scripts allowedesbuild, lefthook, sharp, workerd
  • Actions SHA-pinned6/6

In your browser, right now

  • CSP hashes…
  • External JS…
  • Response headers…

Response headers are served by Cloudflare from public/_headers, so they are absent on the dev server. That is expected, not a failure.

Prowler scans

Prowler audits the two surfaces this page lives on. The results below are real and unedited, from the most recent scan of each.

71%passing, overall
  • Controls 51
  • Passing 36
  • Failing 15
  • Surfaces 2

GitHub

Audits this repository’s own configuration: branch protection, workflow permissions, secret scanning and dependency alerts.

72%controls passing
  • Passing 13
  • Failing 5
  • Target danibarranqueroo/portfolio
  • Prowler v5.42.0
  • Scanned 2026-10-12

Failing, and why they are still failing

  • highRepository default branch dismisses stale pull request approvals
    repository_default_branch_dismisses_stale_reviews

    accepted riskDepends on a pull-request review workflow, which a solo repository does not run.

  • highRepository default branch requires code owner approval for changes to owned code
    repository_default_branch_requires_codeowners_review

    accepted riskCode owner review means reviewing your own code on a solo repository. CODEOWNERS exists so ownership is explicit, but enforcing self-review would be theatre.

  • highRepository default branch requires status checks
    repository_default_branch_status_checks_required

    to doCI already runs lint, typecheck, build, audit, gitleaks and a SHA-pin check on every push. Making them a merge gate requires moving to a pull-request workflow.

  • mediumRepository default branch requires conversation resolution before merging
    repository_default_branch_requires_conversation_resolution

    accepted riskDepends on a pull-request review workflow, which a solo repository does not run.

  • mediumRepository default branch requires at least 2 approvals for code changes
    repository_default_branch_requires_multiple_approvals

    accepted riskRequires two approving reviews. This is a one-person repository, so a second approver does not exist. Stated rather than hidden.

Need checking by hand

  • manualRepository deletes branches after pull request merge
    repository_branch_delete_on_merge_enabled

Prowler marks a check this way when it cannot determine the answer itself. Here that follows from scoping the CI token to the minimum: with fewer permissions there are settings it cannot read. They are listed anyway, because a check that appears in neither list is a check being hidden.

Passing

  • Repository default branch denies force pushes
  • Repository default branch protection applies to administrators
  • Repository enforces branch protection on the default branch
  • Repository default branch requires signed commits
  • Repository grants workflows a read-only default GITHUB_TOKEN
  • Repository has package vulnerability scanning (Dependabot alerts) enabled
  • Repository has immutable releases enabled
  • Repository has secret scanning enabled to detect sensitive data
  • Repository denies default branch deletion
  • Repository has a CODEOWNERS file
  • Repository is archived or active within the configured inactivity threshold
  • Repository default branch requires linear history
  • Public repository has a SECURITY.md file

Cloudflare

Audits the account and DNS zone this page is served from: TLS, HSTS, DNSSEC, mail records and WAF rules.

70%controls passing
  • Passing 23
  • Failing 10
  • Target danibarranquero.com, www.danibarranquero.com
  • Prowler v5.42.0
  • Scanned 2026-10-12

Failing, and why they are still failing

  • highDNS records pointing to hostnames have valid targets without takeover risk
    dns_record_cname_target_valid

    accepted riskA false positive, and acting on it would make things worse. The flagged target is the null MX from RFC 7505: an MX record pointing at "." is the standard way to declare that a domain receives no mail at all. The check reads that "." as a dangling hostname and warns about mail interception, which is precisely what the null MX prevents. Removing the record to turn this green would reopen the risk it is warning about.

  • highDNS records do not expose internal IP addresses
    dns_record_no_internal_ip

    accepted riskA false positive, and worth stating plainly. The address is 100::, the IPv6 discard prefix from RFC 6666, which is what Cloudflare writes for a record that exists only to be proxied. It is not an internal address and it never leaves the account: externally the zone answers with Cloudflare anycast addresses, and a public AAAA lookup returns 2606:4700 addresses, never this one.

  • highDNS records do not expose internal IP addresses
    dns_record_no_internal_ip

    accepted riskA false positive, and worth stating plainly. The address is 100::, the IPv6 discard prefix from RFC 6666, which is what Cloudflare writes for a record that exists only to be proxied. It is not an internal address and it never leaves the account: externally the zone answers with Cloudflare anycast addresses, and a public AAAA lookup returns 2606:4700 addresses, never this one.

  • highRate limiting is configured for the zone
    zone_rate_limiting_enabled

    accepted riskAvailable on the free plan, which allows one rule, so this is a choice rather than a limit. Rate limiting protects an origin from being overwhelmed, and there is no origin: every response is a static asset served from Cloudflare edge cache on unmetered bandwidth. The only free action is a challenge, which would put an interstitial in front of real readers to defend against nothing.

  • highWAF is enabled
    zone_waf_enabled

    blockedThe Web Application Firewall is not available on the Cloudflare free plan. Prowler says so itself in the finding. The site is static files with no origin server, no database and no user input, so the attack classes a WAF blocks have nothing here to reach.

  • highCloudflare Zone OWASP Managed WAF Rulesets Are Enabled
    zone_waf_owasp_ruleset_enabled

    blockedThe OWASP Core Ruleset cannot be deployed on the free plan; only paid plans can. Same reasoning as the WAF check above.

  • mediumBot Fight Mode is enabled
    zone_bot_fight_mode_enabled

    accepted riskMutually exclusive with the Content-Security-Policy on this site. Bot Fight Mode injects an inline challenge script whose r and t parameters change on every request, so no hash and no nonce can ever match it. Turning it on would mean adding unsafe-inline to script-src. Trading a real CSP for bot noise on a static site with no login and no forms is the wrong way round.

  • mediumCloudflare Zone Firewall Rules Use Blocking Actions to Protect Against Threats
    zone_firewall_blocking_rules_configured

    accepted riskCustom firewall rules are available on the free plan. Nothing here is worth blocking: the entire site is public static content meant to be read, there is no authenticated surface, and no path that changes state. A blocking rule written only to satisfy a check would be configuration theatre.

  • mediumDKIM record exists with valid public key
    zone_record_dkim_exists

    accepted riskThis domain sends no mail, so there is no key to publish. The record is *._domainkey with an empty p=, which RFC 6376 defines as a revoked key, and alongside SPF -all, DMARC p=reject and a null MX it is the recommended way to say "nothing here signs mail". The check looks for a valid public key and finds none, which is correct and also the point.

  • lowHotlink Protection is enabled
    zone_hotlink_protection_enabled

    accepted riskA bandwidth control rather than a security control, and static bandwidth on Workers is unmetered, so it protects nothing here. It also blocks other sites from loading images by referer, which is how link previews on social platforms fetch the portrait. Real cost, no benefit.

Passing

  • Always Online is disabled
  • DNSSEC is enabled
  • HSTS is enabled with recommended max-age and includes subdomains
  • Minimum TLS version is set to 1.2 or higher
  • SSL/TLS encryption mode is set to Full (Strict)
  • Universal SSL is enabled
  • DNS records do not use wildcard entries
  • DNS records do not use wildcard entries
  • DNS records do not use wildcard entries
  • Cloudflare proxy is enabled for applicable DNS records
  • Cloudflare proxy is enabled for applicable DNS records
  • Automatic HTTPS Rewrites is enabled
  • Always Use HTTPS is enabled
  • DMARC record exists with enforcement policy (p=reject or p=quarantine)
  • SPF record exists with strict policy (-all)
  • TLS 1.3 is enabled
  • Cloudflare Zone Browser Integrity Check Is Enabled
  • Cloudflare Zone Challenge Passage Is Configured Between 15 and 45 Minutes
  • Development mode is disabled for production zones
  • Email Obfuscation is enabled
  • IP Geolocation is enabled
  • CAA record exists with issue or issuewild tag
  • Under Attack Mode is disabled during normal operations

None of this is written by hand: Prowler’s JSON-OCSF is normalized at build time. Failures are shown as they are, with a stated reason, because hiding them would make the page pointless.

Spot the misconfiguration

Three real configurations. Click the dangerous line in each.0/3

S3 bucket policy

IAM policy

Security group