2 · Security audit
Objective — run the full security pass (headers, dependency vulnerabilities with ownership-aware decisions, exposed files, admin hardening, storage permissions) so no known-controllable risk ships to production.
Background
Section titled “Background”The full security pass: headers, dependency vulnerabilities with ownership-aware decisions, exposed files, admin hardening, and storage permissions.
The CodeCanyon twist is that you don’t own most of the dependency tree. Decide each vulnerability by who controls the package, not just by severity.
| Category | Who controls it | Update? | Conflict risk |
|---|---|---|---|
| Vendor core / dependencies | CodeCanyon author | Risky | HIGH |
| Your additions (Sentry, Spatie…) | You | Safe | NONE |
| Shared packages | Both | Check first | MEDIUM |
flowchart TD CVE[composer audit finding] --> Q{Who owns the package?} Q -->|Vendor core| Risky[Defer / vendor update path] Q -->|Your addition| Safe[Patch or bump freely] Q -->|Shared| Check[Review conflict risk first]1. Grade the headers
Section titled “1. Grade the headers”Aim for Grade A at securityheaders.com (pragmatic CSP caps at A until nonce/hash hardening) and SSL Labs (TLS) A/A+, or check the six headers from the CLI.
-
Grade the six required headers from the CLI (or a public scanner).
Terminal window curl -I https://<YOUR_DOMAIN> 2>/dev/null | \grep -iE "strict-transport|x-frame|x-content-type|referrer-policy|content-security|permissions-policy"# Expected: all six security headers present- ✅ All six are present and match the canonical set defined in Phase 7 · Security headers (this phase verifies, it does not redefine):
Strict-Transport-Security,X-Frame-Options,X-Content-Type-Options,Referrer-Policy,Content-Security-Policy,Permissions-Policy. The CSP must carry real directives (default-src 'self'+frame-ancestors/base-uri/form-action), not justupgrade-insecure-requests— a bare upgrade directive grades well while giving zero XSS protection.
- ✅ All six are present and match the canonical set defined in Phase 7 · Security headers (this phase verifies, it does not redefine):
2. Confirm sensitive files are blocked
Section titled “2. Confirm sensitive files are blocked”Every path below must return 403/404, never 200.
-
Request each sensitive path and inspect status, content type, and body. A status-only check can false-pass when the host returns a branded HTML page, a PHP warning page, or a downloaded text body with a non-200 status.
Terminal window for path in ".env" ".git/config" "composer.json" "storage/logs/laravel.log" "vendor/composer/installed.json"; doprintf "\n== %s ==\n" "$path"body="/tmp/security-path-${path//\//-}.body"curl -sk -D - "https://<YOUR_DOMAIN>/$path" -o "$body" \| awk 'BEGIN{IGNORECASE=1} /^HTTP|^content-type|^content-length/ {print}'head -c 240 "$body" | sed 's/[[:cntrl:]]/ /g'done# Expected: each path returns 403/404, non-secret body, and no PHP/JSON/env content- ✅ Every path returns
403/404; none returns200; no response body exposes.env, Composer metadata, logs, PHP warnings, or JSON package details.
- ✅ Every path returns
-
If you rely on
.htaccess(lowest-priority method), verify it actually carries the headers and a sensitive-file deny block. Prefer Cloudflare/nginx (see the caution below);.htaccessis the fallback a vendor update can overwrite.Terminal window HT="public/.htaccess"[ -f "$HT" ] && echo "OK $HT exists" || echo "MISSING $HT"for needle in "RewriteEngine On" "Strict-Transport-Security" "X-Frame-Options" \"X-Content-Type-Options" "Referrer-Policy" "Permissions-Policy"; dogrep -q "$needle" "$HT" 2>/dev/null && echo "OK $needle" || echo "WARN missing: $needle"done- ✅
.htaccesscarries the six headers (or edge/nginx owns them instead).
- ✅
-
Verify server-level denial and headers are actually configured. Edge headers are preferred, but shared hosting often still needs
.htaccessguards for dotfiles, logs, composer files, and directory listing.Terminal window grep -nE "Header set|Header always set|FilesMatch|Require all denied|RedirectMatch|Options -Indexes" .htaccess public/.htaccess 2>/dev/null || true# Expected: security headers or edge-note present; sensitive-file deny rules present; directory listing disabled- ✅
.htaccessor the documented edge layer blocks sensitive files, disables directory indexes, and does not contradict the Phase 7 header strategy.
- ✅
3. Categorize each vulnerability
Section titled “3. Categorize each vulnerability”Then act by the table below — prefer mitigation over patching vendor code.
| Action | When | Conflict risk |
|---|---|---|
| Update now | Your package, any severity | NONE |
| Disable feature | Unused vendor package | NONE |
| WAF rule | CRITICAL, can’t update | NONE |
| Accept & document | Low severity, vendor package | NONE |
| Patch vendor package | CRITICAL, no alternative | HIGH |
-
Determine ownership mechanically — diff against the vendor’s pristine manifest. The CodeCanyon author’s original
composer.jsonis the source of truth for “vendor-owned vs. your addition”. Compare it against the current manifest (substitute your pristine baseline ref — a tag or branch likeauthor/<VENDOR_TAG>captured when you first imported the item).Terminal window PKG="<vulnerable/package-name>" # from `composer audit`BASE="author/<VENDOR_TAG>" # your pristine vendor baseline (tag or branch)if git show "$BASE:composer.json" >/dev/null 2>&1; thenif git show "$BASE:composer.json" | grep -q "\"$PKG\""; thenecho "VENDOR-OWNED: $PKG is in the vendor baseline → HIGH conflict risk (prefer mitigation; see table)"elseecho "YOUR ADDITION: $PKG is NOT in the vendor baseline → safe to update"fielseecho "SKIP: baseline ref '$BASE' not found — tag the pristine import first, then re-run"fi- ✅ Each finding is labelled VENDOR-OWNED or YOUR ADDITION by manifest diff (not by guesswork), and only then mapped to a row of the action table above.
-
Assign each vulnerability an action by ownership and severity, preferring mitigation over patching vendor code.
- ✅ Every finding maps to one row of the table above, with vendor patches reserved for CRITICAL-with-no-alternative.
-
Prove ownership before editing a vulnerable package. If a package appears to be vendor-owned, check the source tree and the last vendor snapshot before changing it.
Terminal window export VENDOR_TAG=<vendor-import-tag-or-commit>grep -R "<package-or-namespace>" composer.json composer.lock app/ Modules/ packages/ routes/ config/ --line-numbergit diff "$VENDOR_TAG"...HEAD -- composer.json composer.lock app/ Modules/ packages/ | sed -n '1,180p'# Expected: package owner is clear: vendor core, your addition, or shared- ✅ Every dependency decision records whether the package is vendor core, your addition, or shared; shared packages get a conflict-risk note before updating.
4. Harden admin & storage
Section titled “4. Harden admin & storage”Lock down the admin account and the storage permissions before launch.
-
Rotate the admin login, enable 2FA, and confirm rate-limiting + permissions. Default credentials changed, 2FA on if available, login rate-limiting confirmed, and
storage/+bootstrap/cache/at775.- ✅ Default admin credentials are changed, 2FA is on (if available), rate-limiting works, and
storage/+bootstrap/cache/are at775.
- ✅ Default admin credentials are changed, 2FA is on (if available), rate-limiting works, and
-
Block sensitive files at the web server when
.htaccessis your control plane (idempotent — only appended if absent).# Append to public/.htaccess if not already present:<FilesMatch "^\.env|\.git|composer\.(json|lock)">Require all denied</FilesMatch>Terminal window # Idempotent guardgrep -q 'FilesMatch.*composer' public/.htaccess 2>/dev/null \&& echo "OK sensitive-file deny block present" \|| echo "ACTION: add the <FilesMatch> deny block above to public/.htaccess"- ✅
.htaccesscarries a deny block for.env/.git/composer.*(or those controls live at the edge/nginx instead).
- ✅
5. Record a vulnerability register
Section titled “5. Record a vulnerability register”Owner, decision, and a 30-day review date for every finding.
-
Write the register — one row per vulnerability with owner, decision, and a 30-day review date.
- ✅ Every finding has an owner, a recorded decision, and a 30-day review date.
Checklist
Section titled “Checklist”Do not mark this step done until every box below is checked.
- 🤖 Headers graded — all six required headers present; Grade A at securityheaders.com; SSL Labs (TLS) A or A+ recorded.
- 🤖 Sensitive files blocked —
.env,.git/config,composer.json, logs all return 403/404. - 🔀 Vulnerabilities decided — each finding mapped to an ownership-aware action.
- 👤 Admin hardened — default creds changed, 2FA on, rate-limiting confirmed, storage at
775. - 🤖 Vulnerability register written — owner + decision + 30-day review date per finding.