Skip to content
prod 352bb92
Browse

3 · Extract & snapshot the vendor

Objective — get the vendor’s code onto disk and capture a pristine reference of exactly what shipped, so every future vendor update is a reviewable diff instead of an archaeology dig.

Steps at a glance:

  1. Extract the ZIP and place the root — CodeCanyon ZIPs have unpredictable nesting — an outer ZIP wrapping inner ZIPs, docs mixed with source, sometimes multiple framework versions.
  2. Verify the Laravel root — After the user has placed the files, the agent confirms the extraction is correct. Run from the project directory.
  3. Capture vendor docs & detect plugins — Vendor docs carry context the listing page doesn’t — admin features, required cron jobs, API endpoints, server requirements.
  4. Back up the pristine originals (vendor files only) — Snapshot the untouched vendor-shipped top-level files into a versioned vault — an offline, easy-to-eyeball record of “exactly as shipped.” This is a convenience copy: the true pristine baseline is already preserved in git via the author/v branch + author-v tag (the frozen vendor import).
  5. Inspect & classify author vendor patches (the seam) — CodeCanyon authors frequently modify third-party packages inside vendor/. Some of those edits are load-bearing patches (a real bug fix the app depends on — if composer install overwrites them, install breaks).

The code arrives now (or already landed during AI dev environment setup). Capture a pristine reference of exactly what the vendor shipped and identify any patches the author baked into vendor/. This is the page that makes every future vendor update a reviewable diff.

flowchart LR
Z["CodeCanyon ZIP<br/>(👤 extract manually)"] --> R["verify the<br/>Laravel root"]
R --> D["capture vendor docs<br/>+ detect plugins"]
D --> B["back up pristine VENDOR<br/>originals → Admin-Local"]
B --> P["compare shipped vendor/<br/>vs a fresh install"]
P --> G["core-package guard<br/>+ backdoor-signature scan"]
G --> V["read + classify each diff<br/>functional vs telemetry/backdoor"]
V --> C["preserve patches · strip backdoors<br/>document in Zaj-CUSTOMIZATIONS.md"]
C --> T["boot → composer reinstall →<br/>boot: routes equal, signature gone"]

CodeCanyon ZIPs have unpredictable nesting — an outer ZIP wrapping inner ZIPs, docs mixed with source, sometimes multiple framework versions. Extract manually, then let the agent verify.

  1. Unpack and inspect. Double-click the ZIP in Finder (or use The Unarchiver) and look at what came out.

    • ✅ The archive is extracted and its top-level contents are visible.
  2. Find the Laravel root — the folder containing artisan, composer.json, app/, config/, routes/.

    What you seeWhat to do
    artisan directly in the extracted folderThat folder is the Laravel root
    A main-files/ or script/ subfolder holding artisanThe root is inside that subfolder
    Inner ZIPs (main-files.zip, documentation.zip)Extract the source ZIP — that contains the root
    vendor.zip / vendor/ inside the rootShipped vendor — extract it into the root if zipped
    documentation/, license/, readme/ foldersNot the root — move these to Admin-Local/2-Docs/
    • ✅ You can point to the one folder that contains artisan.
  3. Move (cut, don’t copy) the root into your workspace so nothing is left behind.

    Terminal window
    mv /path/to/extracted/laravel-root ~/MyWork/Projects/<ProjectName>
    # Expected: the root now lives under your workspace; the extraction folder is empty
    • ✅ The Laravel root is in your workspace and the source extraction folder is empty.

After the user has placed the files, the agent confirms the extraction is correct. Run from the project directory.

  1. Run the structure check.

    Terminal window
    echo "--- Essential files ---"
    for f in artisan composer.json .env.example; do
    [ -f "$f" ] && echo " ✅ $f" || echo " ❌ MISSING: $f"
    done
    echo "--- Essential directories ---"
    for d in app config database public resources routes storage bootstrap; do
    [ -d "$d" ] && echo " ✅ $d/" || echo " ❌ MISSING: $d/"
    done
    echo "--- Shipped (do NOT overwrite) ---"
    [ -d vendor ] && echo " 📦 vendor/ present ($(du -sh vendor | cut -f1)) — SHIPPED" || echo " ⬜ vendor/ absent — composer install later"
    [ -d public/build ] && echo " 📦 public/build/ present — pre-built assets" || echo " ⬜ public/build/ absent — npm run build later"
    echo "--- Nesting / leftover ZIPs ---"
    [ -f artisan ] && [ -d app ] || { echo " ❌ root is nested — find it:"; find . -maxdepth 3 -name artisan; }
    find . -maxdepth 2 -name "*.zip" | head -5
    # Expected: all ✅, vendor/ + public/build flagged 📦 if shipped, no ❌, no stray .zip
    • ✅ Every essential file/dir prints ✅, no nesting error, no unextracted inner ZIP.

Vendor docs carry context the listing page doesn’t — admin features, required cron jobs, API endpoints, server requirements. Mirror them in so later phases can grep them.

  1. Scan for documentation files.

    Terminal window
    echo "=== Vendor Documentation Scan ==="
    echo "--- In _source/ or _Source/ ---"
    find _source/ _Source/ -maxdepth 2 \( -name "*.pdf" -o -name "*.html" -o -name "*.txt" \
    -o -name "*.md" -o -name "README*" -o -name "INSTALL*" -o -name "CHANGELOG*" \) 2>/dev/null
    echo "--- Documentation folders in project root ---"
    for dir in documentation docs doc Documentation Docs; do
    [ -d "$dir" ] && echo " 📂 $dir/ ($(find "$dir" -type f | wc -l | tr -d ' ') files)"
    done
    echo "--- Key files ---"
    for file in README.md readme.md README.txt INSTALL.md INSTALL.txt CHANGELOG.md changelog.md; do
    [ -f "$file" ] && echo " 📄 $file ($(wc -l < "$file" | tr -d ' ') lines)"
    done
    echo "--- Online docs references ---"
    grep -rih "documentation\|docs\.\|wiki\.\|support\." README* *.md _source/* _Source/* 2>/dev/null | grep -i "http" | head -5
    # Expected: bundled docs and/or URLs surfaced — note what you found
    • ✅ You know whether docs are bundled, online-only, or both.
  2. Copy bundled docs into the docs vault (Step B).

    Terminal window
    mkdir -p Admin-Local/2-Docs/1-VendorDocs
    find . -maxdepth 2 \( -iname "*.pdf" -o -iname "readme*" -o -iname "install*" -o -iname "changelog*" \) \
    -exec cp {} Admin-Local/2-Docs/1-VendorDocs/ \; 2>/dev/null
    ls -lh Admin-Local/2-Docs/1-VendorDocs/
    # Expected: any shipped PDF/README/INSTALL/CHANGELOG files listed in the vault
    • ✅ Bundled docs are mirrored into Admin-Local/2-Docs/1-VendorDocs/.
  3. Generate 00-INDEX.md for the vendor docs vault.

    Terminal window
    cat > Admin-Local/2-Docs/1-VendorDocs/00-INDEX.md << 'EOF'
    # Vendor Documentation Index
    ## Bundled files
    EOF
    ls -1 Admin-Local/2-Docs/1-VendorDocs/ 2>/dev/null | grep -v '^00-INDEX.md$' >> Admin-Local/2-Docs/1-VendorDocs/00-INDEX.md
    [ -f Admin-Local/2-Docs/1-VendorDocs/online/source-urls.txt ] && {
    echo "" >> Admin-Local/2-Docs/1-VendorDocs/00-INDEX.md
    echo "## Online URLs" >> Admin-Local/2-Docs/1-VendorDocs/00-INDEX.md
    cat Admin-Local/2-Docs/1-VendorDocs/online/source-urls.txt >> Admin-Local/2-Docs/1-VendorDocs/00-INDEX.md
    }
    # Expected: 00-INDEX.md lists every mirrored file and any indexed URLs
    • Admin-Local/2-Docs/1-VendorDocs/00-INDEX.md exists and lists bundled + online sources.
  4. If the vendor docs are online-only (more than a handful of pages), mirror them locally so later phases can grep them instead of burning live fetches. Skip this when the vendor ships PDFs/README/docs in the archive.

    Terminal window
    # From the scraper tool's directory (see its README for the full contract):
    python3 -m venv .venv && source .venv/bin/activate
    pip install -r requirements.txt
    playwright install chromium # only needed for the JS-rendered scraper
    # Static HTML docs (interactive prompt):
    python3 simple_scraper.py
    # JS-rendered docs (positional args: <docs-url> <output-dir> <max-pages>):
    python3 docs_scraper.py "<VENDOR_DOCS_URL>" "Admin-Local/2-Docs/1-VendorDocs/scraped" 100
    # Expected: pages mirrored into Admin-Local/2-Docs/1-VendorDocs/scraped for later grep
    • ✅ The scraped docs land under a project-local Admin-Local/2-Docs/1-VendorDocs/scraped/ path.

    If you only have URLs and are not scraping yet, index them first:

    Terminal window
    mkdir -p Admin-Local/2-Docs/1-VendorDocs/online
    grep -RhoE 'https?://[^ )"]+' . | grep -Ei 'docs|documentation|help|support' \
    | sort -u > Admin-Local/2-Docs/1-VendorDocs/online/source-urls.txt
    # Expected: source-urls.txt lists docs URLs, or is empty if none were found
    • ✅ Online-only docs are indexed locally and scraped when the vendor docs are too large to grep live.
  5. Read all documentation and extract eight categories into CLAUDE.md (Step C — agents must read every file found):

    1. Server requirements — PHP version, extensions, memory limits
    2. Installation steps — anything beyond standard Laravel
    3. Admin panel features — super-admin sections and settings
    4. Cron jobs required — scheduler, queue workers
    5. API documentation — endpoints, auth, webhooks
    6. Third-party integrations — payment, mail, storage services
    7. Known issues / gotchas — FAQ or troubleshooting
    8. Update instructions — vendor’s recommended update path
    • ✅ Every category above is filled in or explicitly marked “not documented.”
  6. Sweep for plugins and addons. Many CodeCanyon apps sell or bundle plugins separately — detect them now and hold installation until the base app runs (Phase 6+).

    Terminal window
    echo "=== Plugin / Addon Detection ==="
    # Plugin directories shipped in the tree
    for d in plugins addons modules extensions packages Modules Plugins; do
    [ -d "$d" ] && echo " 📂 $d/ ($(ls -1 "$d" | wc -l | tr -d ' ') items):" && ls -1 "$d" | head -10
    done
    # Plugin-like Composer packages
    echo "--- composer.json plugin-like packages ---"
    grep -iE "plugin|addon|module|extension" composer.json 2>/dev/null | head -10
    # Plugin / marketplace routes
    echo "--- plugin routes ---"
    grep -iE "plugin|addon|module|marketplace|extension" routes/*.php 2>/dev/null | head -5
    # Separate plugin ZIPs in the source archive
    echo "--- plugin ZIPs in the source archive ---"
    find _source/ -maxdepth 2 -name "*.zip" 2>/dev/null | grep -iv "codecanyon\|main\|source" | head -10
    # Expected: any shipped/separate plugins surfaced for the action table below
    FindingAction
    Bundled pluginsNote which are included; they may need activation in the admin panel (Phase 6).
    Separate plugin ZIPsStage them under the source archive’s plugins/ for later install.
    Available for purchaseNote on the CodeCanyon listing; record in CLAUDE.md as “available but not purchased.”
    InstallationDefer to post-deploy (Phase 6+). Never install plugins before the base app runs.
    • ✅ Any plugins/addons are surfaced and slotted into the action table; none installed yet.
  7. Record the essentials in CLAUDE.md under a ## Vendor Documentation heading: source/version/author, server requirements, admin-panel features, cron jobs, API/webhooks, known gotchas, update process, and any plugins/addons (bundled, for-purchase, or installed). Defer installing any plugin until the base app runs (Phase 6+).

    • CLAUDE.md has a ## Vendor Documentation section covering all eight extraction categories plus plugins.

4. Back up the pristine originals (vendor files only)

Section titled “4. Back up the pristine originals (vendor files only)”

Snapshot the untouched vendor-shipped top-level files into a versioned vault — an offline, easy-to-eyeball record of “exactly as shipped.” This is a convenience copy: the true pristine baseline is already preserved in git via the author/v<VERSION> branch + author-v<VERSION> tag (the frozen vendor import). So this vault only needs the files the vendor shipped — never the project/AI-overlay files you add on top.

  1. Copy each vendor-shipped top-level file with a version suffix. The exclude list below drops the project + AI-overlay files we add (so they never reach the vault), then writes metadata beside the backup.

    Vendor root scripts such as server-requirements.php, extract-translations.php, phpinfo.php, and the public index.php are vendor/framework-owned, not Zaj-owned. Do not rename them with the Zaj- prefix. List them in the Zaj-CUSTOMIZATIONS.md ownership map so any unmarked root file is understood as vendor/framework territory.

    Terminal window
    export VERSION="x.y.z" # replace with the vendor's actual version, e.g. "10.4"
    mkdir -p "Admin-Local/3-Versions/1-v${VERSION}/1-Originals"
    # Files WE add on top of the vendor tree — never snapshot these.
    # (Personal/secret files are gitignored by exact name; their -vX copies would bypass that.)
    OVERLAY_EXCLUDE="CLAUDE.md CLAUDE.local.md AGENTS.md GEMINI.md \
    Zaj-CUSTOMIZATIONS.md Zaj-PROGRESS.md Zaj-BACKLOG.md Zaj-CHANGELOG.md \
    _onboarding-summary.md .mcp.json .env .env.tpl"
    for f in $(ls -A | while read x; do [ -f "$x" ] && echo "$x"; done); do
    case " $OVERLAY_EXCLUDE " in
    *" $f "*) echo " ⏭️ skip (overlay/secret, not vendor): $f"; continue ;;
    esac
    cp "$f" "Admin-Local/3-Versions/1-v${VERSION}/1-Originals/${f}-v${VERSION}"
    echo " 📦 snapshot: $f"
    done
    cat > "Admin-Local/3-Versions/1-v${VERSION}/Originals-v${VERSION}.md" << EOF
    # Originals backup — v${VERSION} (vendor-shipped files only)
    - **Date:** $(date +%Y-%m-%d)
    - **Source ZIP:** _source/[PROJECT]-v${VERSION}.zip (or CodeCanyon download name)
    - **Path:** Admin-Local/3-Versions/1-v${VERSION}/1-Originals/
    - **Scope:** vendor-shipped top-level files ONLY (overlay/secret files excluded)
    - **True baseline:** git \`author/v${VERSION}\` branch + \`author-v${VERSION}\` tag
    - **Checksum (optional):** shasum -a 256 _source/*.zip
    EOF
    # Expected: only vendor files copied; overlay/secret files printed as ⏭️ skip; Originals-vX.md present
    • Admin-Local/3-Versions/1-v<VERSION>/1-Originals/ holds version-suffixed copies of vendor files only; overlay/secret files were skipped; Originals-v<VERSION>.md records date, ZIP name, path, and notes the git baseline.
  2. Belt-and-suspenders: gitignore the -vX snapshot copies of any root-gitignored file. Even if a future edit re-introduces a broad cp, these rules guarantee a secret/personal file can never be committed through its snapshot copy. Append once.

    Terminal window
    if ! grep -q '3-Versions/\*\*/.env' .gitignore 2>/dev/null; then
    cat >> .gitignore << 'EOF'
    # Snapshot guard — never commit -vX copies of gitignored personal/secret files.
    # (The root rules ignore the EXACT names; the `-vX` suffix would otherwise slip past them.)
    Admin-Local/3-Versions/**/.env
    Admin-Local/3-Versions/**/.env-*
    Admin-Local/3-Versions/**/.env.*
    Admin-Local/3-Versions/**/CLAUDE.local.md*
    Admin-Local/3-Versions/**/_onboarding-summary.md*
    /.env-*
    /.env.*
    /CLAUDE.local.md*
    /_onboarding-summary.md*
    EOF
    fi
    # Expected: the snapshot guard block appended once (idempotent — re-running won't duplicate it)
    • .gitignore contains the snapshot-copy guards (Admin-Local/3-Versions/**/.env*, /.env-*, /.env.*, /CLAUDE.local.md*, /_onboarding-summary.md*).
  3. The vendor’s own .env (if the archive shipped one) belongs in the gitignored credential vault, not in 1-Originals/. Keep it as a reference of the vendor’s default config keys without risking a committed snapshot.

    Terminal window
    # If the vendor shipped a real .env (rare, but some archives do):
    if [ -f .env ] && grep -qiE 'codecanyon|vendor|demo' .env 2>/dev/null; then
    mkdir -p Admin-Local/1-Project/2-Vault
    cp .env Admin-Local/1-Project/2-Vault/.env.vendor-original
    echo " 🔐 vendor .env → vault/.env.vendor-original (gitignored vault, NOT 1-Originals)"
    fi
    # Expected: any vendor-shipped .env lands in the gitignored vault as .env.vendor-original
    • ✅ Any vendor-shipped .env is preserved as Admin-Local/1-Project/2-Vault/.env.vendor-original (gitignored) — never in 1-Originals/.
  4. Verify no secret/personal snapshot is stageable. After the snapshot, git status must show no -vX copy of a secret/personal file staged or untracked-but-trackable.

    Terminal window
    git add -A --dry-run 2>/dev/null \
    | grep -iE '\.env(-|\.|$)|CLAUDE\.local\.md|_onboarding-summary\.md' \
    && echo " ❌ a secret/personal snapshot is stageable — fix the exclude/gitignore above" \
    || echo " ✅ git status clean of .env-* / CLAUDE.local.md-* / _onboarding-summary.md-* snapshots"
    • git status (via git add --dry-run) shows no .env-*, CLAUDE.local.md-*, or _onboarding-summary.md-* snapshot staged or trackable.

5. Inspect & classify author vendor patches (the seam)

Section titled “5. Inspect & classify author vendor patches (the seam)”

CodeCanyon authors frequently modify third-party packages inside vendor/. Some of those edits are load-bearing patches (a real bug fix the app depends on — if composer install overwrites them, install breaks). Others are license-injection backdoors disguised as patches. You must find every diff, read and classify each one, and then decide — preserve, strip, or surgically split — on evidence, not on the file count.

  1. If no vendor/ shipped, there’s nothing to compare — you’ll composer install in Phase 3. Skip to the gate.

    • ✅ Confirmed vendor/ absent → nothing to preserve.
  2. If vendor/ shipped, compare it against a fresh install in a temp directory (never the project root). This produces the review list — not a keep list.

    Terminal window
    mkdir -p /tmp/vendor-compare
    cp composer.json composer.lock /tmp/vendor-compare/ 2>/dev/null
    ( cd /tmp/vendor-compare && composer install --no-scripts --no-autoloader 2>&1 | tail -3 )
    diff -rq vendor /tmp/vendor-compare/vendor 2>/dev/null \
    | grep -v "autoload\|installed.json\|installed.php\|\.git" | tee /tmp/vendor-diff.txt
    echo "=== $(wc -l < /tmp/vendor-diff.txt | tr -d ' ') differing files to REVIEW (not to keep) ==="
    # Expected: a count of differing files (often 0). Every line is a file you must read.
    • ✅ A review-list count printed — proceed to the core-package guard before classifying anything.
  3. Run the core-package guard. Diffs inside the framework core (laravel/framework, symfony/*, illuminate/*) are SUSPECT by default — legitimate author patches almost never live there; nulled/license-injection backdoors almost always do. Partition the review list.

    Terminal window
    echo "=== 🔴 CORE-FRAMEWORK diffs — SUSPECT, never auto-preserve ==="
    grep -iE 'vendor/(laravel/framework|symfony/|illuminate/)' /tmp/vendor-diff.txt | tee /tmp/vendor-diff-core.txt
    echo "=== 🟡 NON-CORE diffs — could be a real author patch, still must read ==="
    grep -ivE 'vendor/(laravel/framework|symfony/|illuminate/)' /tmp/vendor-diff.txt | tee /tmp/vendor-diff-noncore.txt
    # Expected: core diffs isolated. If /tmp/vendor-diff-core.txt is non-empty → treat as a likely backdoor until proven otherwise.
    • ✅ Review list split into 🔴 core (suspect) and 🟡 non-core. Any non-empty core file triggers the signature scan in step 4 and is not a preserve candidate.
  4. Scan every differing file for backdoor signatures before you classify it as a patch. These are the fingerprints of the nulled-license / phone-home injections shipped in real CodeCanyon archives. Run against the actual differing files (both core and non-core).

    Terminal window
    # Build the list of differing files as real paths under vendor/
    awk '/^Files /{print $2}' /tmp/vendor-diff.txt | sed 's#/tmp/vendor-compare/##' | sort -u > /tmp/vendor-diff-files.txt
    # Fallback if your diff lines read "Files vendor/... and /tmp/... differ":
    [ -s /tmp/vendor-diff-files.txt ] || grep -oE 'vendor/[^ ]+' /tmp/vendor-diff.txt | sort -u > /tmp/vendor-diff-files.txt
    echo "=== Backdoor-signature scan over $(wc -l < /tmp/vendor-diff-files.txt | tr -d ' ') file(s) ==="
    while read -r f; do
    [ -f "$f" ] || continue
    hits=$(grep -nE \
    'chr\(|\\x[0-9a-f]{2}|[0-9]{2,3}[,.|][0-9]{2,3}[,.|][0-9]{2,3}|getScript|eval\(|gzinflate|gzuncompress|str_rot13|base64_decode|getCourant|HeaderCodec|\.dist["'\'']|envato\.|verify\.js|product_id|->name *== *["'\'']\s*(login|register)' \
    "$f")
    [ -n "$hits" ] && { echo " 🔴 SUSPECT: $f"; echo "$hits" | sed 's/^/ /'; }
    done < /tmp/vendor-diff-files.txt
    echo "=== scan done — any 🔴 line = do NOT preserve; treat as malicious until disproven ==="

    The signatures, and why each matters:

    SignatureWhat it betrays
    chr( / \xNN / decimal- or hex-delimited number blobs (72,84,84,80…)Obfuscated string assembly hiding a URL or payload (the WorkDo HeaderCodec.dist decoder)
    getScript / eval( / gzinflate / gzuncompress / str_rot13 / base64_decodeRuntime fetch or decode-and-execute of remote/packed code
    getCourant / HeaderCodec / hidden methods shadowing framework coreAuthor-added methods that don’t exist in clean upstream — the route-name oracle that gates injection
    Vendor phone-home domains (envato., verify.js, product_id, any non-app host)License/anti-null beacon calling home
    Gating on login / register route names (->name == 'login')Injection that fires only on credential-entry pages (hardest to notice, highest harm)
    • ✅ Every 🔴 hit is recorded. A signature hit means strip, never preserve — even if the file is also a “patch.”
  5. Validate before trust — read, diff-vs-clean, classify. For each file still on the review list, do all four (never skip on a small count):

    1. Read the live shipped file end-to-end. You are looking for added methods, injected output, encoded blobs, and anything that touches the network or the response body.

    2. Diff it against clean upstream (the temp install you already built) to see exactly what the author changed — not the whole file:

      Terminal window
      f="vendor/<vendor>/<package>/<file>.php" # one path from /tmp/vendor-diff-files.txt
      diff -u "/tmp/vendor-compare/$f" "$f" | sed -n '1,120p'
      # Expected: the precise added/removed lines. Telemetry shows as injected output/network calls;
      # a real patch shows as a logic fix (null guard, type cast, bug workaround).
    3. Classify the change as one of:

      ClassWhat it looks like in the diffDecision
      Functional patchA real bug fix / behavior change the app needs (null guard, signature fix, compat shim) — no network call, no encoded blob, no response injectionPreserve (step 6)
      Telemetry / maliciousPhone-home, license beacon, response-body injection, encoded payload, hidden oracle methodStrip — clean upstream is the fix (step 6); never preserve
      MixedOne file holds both a real fix and an injected beaconSurgical edit — re-apply only the functional hunk onto clean upstream; drop the malicious hunk
    4. Cross-check core files hard. If the file is in the 🔴 core set and you think it’s a functional patch, raise the bar: a genuine app-required edit to laravel/framework or symfony/* is rare. Confirm the same fix isn’t already available by bumping the locked version, and get a second pair of eyes before preserving anything in core.

    • ✅ Every review-list file is classified (functional / telemetry / mixed) with the diff evidence captured.
  6. Act on the classification, then clean up the temp dirs.

    Terminal window
    # FUNCTIONAL PATCH → preserve into the tracked customization area (this is the real bug-fix case)
    mkdir -p "resources/vendor-customizations/<vendor>/<package>"
    cp -r "vendor/<vendor>/<package>/src" "resources/vendor-customizations/<vendor>/<package>/"
    # TELEMETRY / MALICIOUS → preserve NOTHING. Do not copy it anywhere "to keep".
    # The fix is a clean upstream copy, applied in §5.5 (composer reinstall) — NOT composer install (see caution).
    # MIXED → re-apply only the functional hunk onto the clean upstream copy, then preserve that edited file.
    [ -d /tmp/vendor-compare ] && rm -rf /tmp/vendor-compare
    rm -f /tmp/vendor-diff*.txt
    # Expected: only functional/mixed patches land in resources/vendor-customizations/; temp dirs removed
    • ✅ Functional patches preserved; telemetry/malicious diffs preserved nowhere; mixed files split surgically.
  7. Append findings to Zaj-CUSTOMIZATIONS.md — one row per reviewed file, including the ones you deliberately did not keep (the audit trail matters most for those):

    ## Vendor patches (vX.X.X)
    | Package | File | Classification | Decision | Preserved in / removed by |
    | --- | --- | --- | --- | --- |
    | vendor/foo/bar | src/Baz.php | functional | preserve | resources/vendor-customizations/foo/bar/ |
    | vendor/laravel/framework | …/Routing/Router.php | telemetry (backdoor) | strip | clean upstream via composer reinstall (§5.5) |

    If you find a backdoor, also record an incident block (what it does, exposure, remediation, and a “re-scan after every vendor update” hard rule) — see the worked example below.

    • Zaj-CUSTOMIZATIONS.md lists every reviewed file with classification + decision (or states “0 differences”).

5.5. Boot, clean the seam, smoke-test (static diff is not proof)

Section titled “5.5. Boot, clean the seam, smoke-test (static diff is not proof)”

A diff -rq tells you which files changed on disk — it cannot tell you whether a clean tree still boots and serves the same routes. A telemetry-only diff is safe to overwrite; a load-bearing patch is not, and only a runtime test distinguishes them. Prove it.

  1. Boot the app and baseline it — BEFORE any clean/reinstall. Use Herd or php artisan serve and capture a baseline you can compare against.

    Terminal window
    php artisan route:list 2>/dev/null | wc -l # baseline route count, e.g. 919
    php artisan route:list 2>/dev/null | sort > /tmp/routes-before.txt
    # Boot it (one of):
    # herd link <project> && herd secure <project> # then open https://<project>.test
    # php artisan serve # http://127.0.0.1:8000

    With the backdoor signature still present, also confirm the beacon is observable (so you can prove it’s gone after):

    Terminal window
    grep -rEl 'getCourant|envato\.|verify\.js' vendor/ 2>/dev/null # baseline: lists the tampered file(s)
    • ✅ Baseline captured: route count, /tmp/routes-before.txt, and the list of files still carrying the signature.
  2. Smoke-test the public seam BEFORE the clean — visit each surface and note that it loads (and, for the backdoor case, that the beacon fires). Cover home, login, register, dashboard, and the admin entry point — the exact pages a credential-injection backdoor targets.

    Terminal window
    for path in / /login /register /dashboard /admin; do
    code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:8000${path}")
    echo " BEFORE ${path}${code}"
    done
    # Expected: a working/expected status per route (200/302/401 as appropriate) — this is your baseline shape.

    (When a browser is available, use a Playwright run instead so JS-injected <script src="…verify.js"> is actually observable in the rendered page / network tab.)

    • ✅ Baseline behavior recorded for every seam page.
  3. Clean the seam — reinstall the affected packages (or the whole tree). This is the step that actually removes a tampered/backdoored file; composer install would not (see the danger above).

    Terminal window
    # Targeted (preferred when you know which packages were tampered):
    composer reinstall laravel/framework symfony/http-foundation # ← your actual flagged packages
    # Full rebuild (when in doubt, or many packages diff):
    # rm -rf vendor && composer install
    # grep-verify the signature is gone:
    grep -rEl 'getCourant|envato\.|verify\.js|HeaderCodec' vendor/ 2>/dev/null \
    && echo " ❌ signature STILL present — reinstall did not cover it" \
    || echo " ✅ signature gone from vendor/"
    • grep -r over vendor/ returns no backdoor signature.
  4. Re-apply only the functional patches you classified in §5 step 6 (Mixed/Functional), restoring them onto the now-clean tree from resources/vendor-customizations/. Re-apply nothing from the telemetry/malicious set.

    • ✅ Clean tree + load-bearing patches only; no stripped beacon re-introduced.
  5. Boot again and compare — AFTER the clean. The gate is AFTER ≥ BEFORE: equal-or-greater route count, no new boot errors, and the backdoor signature gone.

    Terminal window
    php artisan route:list 2>/dev/null | wc -l # must equal (or exceed) the baseline
    php artisan route:list 2>/dev/null | sort > /tmp/routes-after.txt
    diff /tmp/routes-before.txt /tmp/routes-after.txt && echo " ✅ route set unchanged" || echo " ⚠️ routes changed — investigate the diff above"
    for path in / /login /register /dashboard /admin; do
    code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:8000${path}")
    echo " AFTER ${path}${code}"
    done
    # Expected: same/better route count, same seam-page statuses, no new errors, signature gone.
    • ✅ Gate passes: AFTER route count ≥ BEFORE, seam pages unchanged, no new boot errors, signature confirmed gone.

The shipped vendor/ stays exactly where it is — git-ignored later, never deleted — until this §5.5 gate proves a clean tree boots and serves the same seam. Telemetry/backdoor diffs are never preserved; load-bearing patches are re-applied surgically and proven by the boot-clean-boot test, not by a static diff.

Do not mark this step done until every box below is checked.

  • 🔀 Laravel root verified — all essential files/dirs present, no nesting, no unextracted inner ZIPs.
  • 🤖 Vendor docs captured — copied to Admin-Local/2-Docs/1-VendorDocs/; key facts in CLAUDE.md.
  • 🤖 Pristine VENDOR backup taken — vendor-shipped files only in Admin-Local/3-Versions/1-v<VERSION>/1-Originals/ (overlay/secret files excluded); -vX snapshot guards added to .gitignore; git status clean of any .env-* / CLAUDE.local.md-* / _onboarding-summary.md-* snapshot; vendor .env (if any) in vault as .env.vendor-original.
  • 🤖 Every vendor/ diff classified — core-package guard run, backdoor-signature scan run, each file read + diffed-vs-clean and labelled functional / telemetry / mixed in Zaj-CUSTOMIZATIONS.md (or confirmed: 0 differences). No diff was auto-preserved.
  • 🤖 Backdoors stripped, never preserved — any signature-hit / phone-home / response-injection file copied nowhere; only functional/mixed patches landed in resources/vendor-customizations/.
  • 🤖 Seam proven by boot-clean-boot — baseline routes captured → composer reinstall (not install) the tampered packages → routes equal-or-greater, seam pages unchanged, backdoor signature grep -r confirmed gone (AFTER ≥ BEFORE).
  • 🤖 Shipped vendor/ intact until proven — not renamed/deleted; only the verified-clean tree advances to Phase 3.