Scanned both web properties, killed a critical RCE the same day, and turned the long tail into a dated to-do.
A security pass over the VietConnect Next.js repo and the live tradewithviet.com WordPress site. The repo had 21 dependency vulnerabilities including a critical remote-code-execution package and a Next.js middleware-bypass that touched the dashboard auth guard — both fixed and verified. WordPress was read-only: no code there is unsafe today, but 11 plugins are stale with auto-updates off, which is the real exposure.
StatusRepo fixed · WP reportCritical1 found → 0 leftnpm audit21 → 2Date2026-06-12
ReasoningLập luận
6-layer thinking chainChuỗi tư duy 6 tầng
L1
Raw requestYêu cầu gốc
"dùng đúng skill, scan the security của website tradewithviet và cả vietconnect"
L2
Reframe — what was really neededDiễn giải lại — nhu cầu thật
Two very different targets under one word, 'website'. VietConnect is source code we own and can change; tradewithviet.com is a live production WordPress site that customers hit right now. 'Scan the security' means find what's exploitable — but the safe action differs per target: edit-and-verify for the repo, look-don't-touch for the live site.
L3
ConstraintsRàng buộc
- The repo can be built and tested locally — fixes are verifiable before they ship.
- The WordPress site is production: any plugin install or option write is a change to a live asset and can break it.
- WP-CLI/SSH access exists (key-based) and a security-scan skill exists — but that skill scans codebases, not live WordPress.
- No WPScan API key, so WordPress CVE matching is manual.
L4
Options weighedCác lựa chọn cân nhắc
- Run one generic scanner over both — cheap, but the skill only understands codebases; it would learn nothing about the live WP site. Cost: blind on half the scope.
- Install a scanner (Wordfence) on WordPress for a deep scan — thorough, but mutates the production site. Cost: changes a live asset without need.
- Split by target: security-scan skill + Supabase advisors for the repo, read-only WP-CLI/REST/SSH audit for WordPress. Cost: more manual work, but nothing on the live site changes.
L5
Principle invokedNguyên tắc áp dụng
Match the blast radius of the method to the reversibility of the target — for production you own but didn't break, read-only first.
L6
Pick + recognition signalLựa chọn + dấu hiệu nhận biết
Picked the split approach: applied and verified fixes in the repo, produced a report-only assessment for WordPress with a dated remediation task. Rejected the Wordfence install (asked the user; they chose read-only) and the single-scanner shortcut (would have missed the WP site entirely). Recognition signal: when a request bundles a codebase you control with a live system you don't, never let one method span both — the right tool for a repo is the wrong tool for production.
DecisionsQuyết định
Layered decision cardsCác quyết định theo tầng
Fix the critical RCE by bumping Next, not by force-fix
L1npm audit showed 1 critical (protobufjs RCE) + 5 high, fix paths offered include `npm audit fix --force`.
L2`--force` can drag unrelated packages across major versions and silently break the build. The bulk of the high-severity noise was actually 14 Next.js advisories — one of them an App Router middleware/proxy bypass, which matters because middleware.ts is exactly what guards /dashboard auth.
L3Next was pinned at an exact 16.2.1; the fix release 16.2.9 is a patch within the same minor; the 2 truly stuck moderates are build-time transitive deps under Next that only 'resolve' by downgrading Next to v9.
L4
- npm audit fix --force — clears everything on paper. Cost: would downgrade Next to v9, a breaking change.
- npm audit fix (non-force) + manual bump Next 16.2.1→16.2.9 — clears the critical, all 5 highs, and the middleware bypass. Cost: 2 harmless moderates remain.
- Do nothing, just report — Cost: leaves a known RCE live.
L5A 'fix' that breaks the build is not a fix — accept a documented low-risk residue over a destructive clean sweep.
L6Bumped Next to 16.2.9 (patch), ran non-force audit fix; 21→2 moderate (build-time only, accepted). Rejected --force. Recognition signal: when `audit fix --force` wants to change a major version, stop and bump the offending package by hand instead.
Cron endpoint: fail closed, not fail open
L1The renewal-alerts cron route only checked the bearer token `if (secret)` — i.e. only when CRON_SECRET happened to be set.
L2That is a fail-open default: if the env var is ever missing or misconfigured, the endpoint runs unauthenticated and anyone can trigger the alert generator. A protected endpoint should refuse to run when its own protection is absent.
L3
- On Vercel the secret is normally set, so the bug is latent — but env drift is exactly the failure mode you can't see.
- Local dev without the secret would now be blocked, which is the correct trade.
L4
- Leave it — works today. Cost: one missing env var = open endpoint.
- Invert to fail closed: no secret → 401. Cost: must set the secret to run, by design.
L5Security controls default to deny — absence of a key means 'no', never 'skip the check'.
L6Changed to return 401 when CRON_SECRET is unset. Recognition signal: any `if (secret) { check }` is a fail-open smell — the guard belongs outside the if, with the missing-secret case denied.
WordPress findings are real but not a live 0-day
L111 WordPress plugins are behind, including the backup and code-exec plugins, with auto-updates off for all of them.
L2It's tempting to call outdated plugins 'critical'. But the most recent disclosed CVE on the riskiest plugin (Essential Addons, CVE-2026-1512) affects versions <= 6.5.9, and the site runs 6.6.6 — already past it. So today's risk is the update lag and disabled auto-update, not an actively exploitable hole.
L3
- Read-only access only — can report and recommend, can't update for them.
- No WPScan key, so the long tail of CVEs is matched manually (lower confidence).
- Core itself verifies against checksums — no tampering.
L4
- Label it critical to force action — Cost: cries wolf, erodes trust in the severity scale.
- Rate it HIGH/preventive, state plainly there's no known live exploit on the installed versions. Cost: needs the nuance spelled out.
L5Honest severity beats scary severity — report exploitability, not just staleness.
L6Marked the plugin lag as HIGH-preventive with the CVE-vs-installed-version reasoning shown. Recognition signal: 'outdated' does not equal 'vulnerable' — always check the disclosed-affected range against the installed version before assigning severity.
PipelineQuy trình
What ran, in order
1
Scope split
Repo = editable + verifiable; WordPress = read-only production. Confirmed scan depth with the user (read-only WP, apply safe repo fixes).
2
Repo: deps + secrets
npm audit (21 vulns, 1 critical); secret grep over source + git history (clean); confirmed no env files tracked.
3
Repo: code + RLS
Reviewed contact route, compliance checker, cron, middleware, auth callback; pulled Supabase security advisors.
4
WordPress: read-only audit
WP-CLI core/plugin/theme/user audit + checksums; curl probes for user enumeration, XML-RPC, stray files, headers; SSH config-hardening review.
5
Apply safe fixes
audit fix + Next 16.2.9; cron fail-closed; CI audit gate. Verified: tsc clean, 63/63 tests, build OK.
6
Report + task
Prioritised report to plans/reports/; calendar task for tomorrow with the full remediation checklist.
FilesTệp
Artifact mapBản đồ tệp tạo ra
| PathĐường dẫn | WhatLà gì | Who reads itAi dùng |
|---|---|---|
plans/reports/security-scan-2026-06-12.md | Full prioritised report, both sites, Critical to Low with evidence | Source of truth for remediation |
package.json + package-lock.json | next & eslint-config-next 16.2.1 -> 16.2.9; npm audit fix | 21 -> 2 vulnerabilities |
.github/workflows/ci.yml | Added npm audit --audit-level=high step | Fails CI on future high/critical deps |
src/app/api/cron/compliance-alerts/route.ts | Cron auth now fails closed when CRON_SECRET is unset | Closes the unauthenticated-trigger window |
Pending manual actions — required before fully liveViệc cần làm thủ công — trước khi hoàn tất
- TWV (HIGH): update all 11 plugins, then enable auto-updates for security releases.
- TWV (HIGH): block REST/author user enumeration and create non-email admin usernames; confirm 2FA on both admins.
- TWV (MED): disable XML-RPC if unused; add define('DISALLOW_FILE_EDIT', true) to wp-config.php; delete readme.html; remove unused themes.
- VietConnect: require supplierId in /api/contact and resolve recipient email server-side (close the open email relay).
- VietConnect: rate-limit the public compliance lead capture; add migration 006 (revoke EXECUTE on increment_*/is_admin from anon+authenticated, set function search_path, enable leaked-password protection).
- VietConnect: commit the applied fixes (next 16.2.9, cron fail-closed, CI audit step) — currently uncommitted on the working tree.
- Plan the WordPress 6.9.4 -> 7.0 major upgrade on staging.
Self-testTự kiểm tra
Check your understanding
Why was the WordPress site audited read-only while the VietConnect repo got actual edits?
Why was `npm audit fix --force` rejected in favour of a manual Next.js bump?
In your own words: why does `if (secret) { checkToken() }` count as a security bug, and what's the fix?
In your own words: why were 11 outdated WordPress plugins rated HIGH-preventive rather than critical?
Mastery checklist — tick what you can explain unpromptedBảng tự đánh giá — tích những gì bạn tự giải thích được