Problem
Real bot-scanner traffic on dalenguyen.me (Cloud Run, blog-app) is
untouched by the #210/#213 fix, since it targets generic CMS/framework paths
outside our /api/v1/** namespace, not our own routes. Sampled from Cloud
Run logs (2026-07-05, various times):
/wp-login.php, /wp-admin/setup-config.php, /wp-config.php
/.git/config, /.git/HEAD
/wp-includes/wlwmanifest.xml under a dozen path prefixes (/blog/,
/shop/, /2019/, etc.) — automated CMS fingerprinting
/.env, /.env.production, /config.php, /config.xml
All currently return 200 via the Angular SSR not-found fallback (see #216
for the underlying status-code bug). None of this is blocked or reduced —
every one of these requests still spins up Cloud Run compute and pollutes
logs.
Decision
Use Cloudflare (not GCP Cloud Armor) as the edge WAF layer in front of
the apex. Chosen over Cloud Armor for cost/simplicity — Cloudflare's free
tier covers Bot Fight Mode + custom firewall rules, no GCP load balancer
needed.
Plan
- Add
dalenguyen.me as a site in Cloudflare (free plan).
- At the registrar (currently
dns1/dns2.registrar-servers.com), switch
nameservers to the two Cloudflare assigns.
- Recreate DNS records in Cloudflare:
dalenguyen.me (apex) → same target as today (Cloud Run domain
mapping), proxied (orange cloud on).
www → leave pointed at Vercel; proxying is optional since Vercel
already has its own edge.
- Security → WAF:
- Enable Bot Fight Mode.
- Add a Custom Rule, action Block:
(http.request.uri.path contains "wp-login") or
(http.request.uri.path contains "wp-admin") or
(http.request.uri.path contains "wp-includes") or
(http.request.uri.path contains "wp-content") or
(http.request.uri.path contains ".env") or
(http.request.uri.path contains ".git") or
(http.request.uri.path contains "config.php") or
(http.request.uri.path contains "xmlrpc.php") or
(http.request.uri.path contains "wlwmanifest.xml")
Safe to hard-block (not challenge) — this app (Analog/Angular) has no
legitimate path matching any of these.
- Verify: re-run the Cloud Run log query for these paths a few hours after
cutover and confirm they've dropped to zero (Cloudflare should be
rejecting them before they ever reach Cloud Run).
Notes
Acceptance
dalenguyen.me nameservers point at Cloudflare.
- Bot Fight Mode + the custom block rule are active.
- A live probe to any of the listed paths (e.g.
curl -o /dev/null -w '%{http_code}' https://dalenguyen.me/wp-login.php)
returns a Cloudflare block response, not the app's 200.
- Cloud Run log query for these paths shows near-zero hits a few hours
after cutover.
Problem
Real bot-scanner traffic on
dalenguyen.me(Cloud Run,blog-app) isuntouched by the #210/#213 fix, since it targets generic CMS/framework paths
outside our
/api/v1/**namespace, not our own routes. Sampled from CloudRun logs (2026-07-05, various times):
/wp-login.php,/wp-admin/setup-config.php,/wp-config.php/.git/config,/.git/HEAD/wp-includes/wlwmanifest.xmlunder a dozen path prefixes (/blog/,/shop/,/2019/, etc.) — automated CMS fingerprinting/.env,/.env.production,/config.php,/config.xmlAll currently return 200 via the Angular SSR not-found fallback (see #216
for the underlying status-code bug). None of this is blocked or reduced —
every one of these requests still spins up Cloud Run compute and pollutes
logs.
Decision
Use Cloudflare (not GCP Cloud Armor) as the edge WAF layer in front of
the apex. Chosen over Cloud Armor for cost/simplicity — Cloudflare's free
tier covers Bot Fight Mode + custom firewall rules, no GCP load balancer
needed.
Plan
dalenguyen.meas a site in Cloudflare (free plan).dns1/dns2.registrar-servers.com), switchnameservers to the two Cloudflare assigns.
dalenguyen.me(apex) → same target as today (Cloud Run domainmapping), proxied (orange cloud on).
www→ leave pointed at Vercel; proxying is optional since Vercelalready has its own edge.
legitimate path matching any of these.
cutover and confirm they've dropped to zero (Cloudflare should be
rejecting them before they ever reach Cloud Run).
Notes
something an agent/CLI can do without a Cloudflare API token and
registrar access. Manual execution required.
200 instead of 404) — Cloudflare stops the requests from reaching the
app at all; fix(blog-app): Angular SSR not-found route returns 200 instead of 404 #216 fixes the app's own behavior for whatever still gets
through.
Acceptance
dalenguyen.menameservers point at Cloudflare.curl -o /dev/null -w '%{http_code}' https://dalenguyen.me/wp-login.php)returns a Cloudflare block response, not the app's 200.
after cutover.