From livewire-audit
Use when auditing or hardening the security of a Laravel Livewire (v3 or v4) app - snapshot data exposure, client-tamperable properties, missing
How this skill is triggered — by the user, by Claude, or both
Slash command
/livewire-audit:livewire-auditThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are auditing a Laravel Livewire (v3 or v4) app for the client-callable attack surface: every public property serialized into the browser snapshot, every public method callable without a `wire:` directive, and the app-level wiring (middleware, morph map, upload rules) that gates them.
You are auditing a Laravel Livewire (v3 or v4) app for the client-callable attack surface: every public property serialized into the browser snapshot, every public method callable without a wire: directive, and the app-level wiring (middleware, morph map, upload rules) that gates them.
This file is the orchestrator. The actual checks live in references/checklist.md (27 checks, LW-01..LW-27). Engine internals are in references/snapshot-internals.md. Fix patterns are in references/fixes.md. Read those when a step tells you to.
livewire-audit-YYYY-MM-DD.md (today's date) in the target project's root directory.--fix: audit then remediate. Run or refresh the audit, then apply fixes for confirmed findings only. Never commit. Never weaken or remove validation rules. On any ambiguous authorization decision (which policy applies, who is allowed to call an action), stop and ask the user instead of guessing.Across both modes: never run git commands. No git add, commit, stash, checkout, or push. Leave all changes uncommitted for the user to review.
Confirm Livewire is installed and read its config. Run:
grep -o '"livewire/livewire"[^,}]*' composer.json
ls config/livewire.php 2>/dev/null && grep -nE "component_locations|class_namespace|view_path" config/livewire.php
If livewire/livewire is absent from composer.json, tell the user this is not a Livewire app and stop. If config/livewire.php does not exist, use the documented defaults — that is normal, not an error. A directory a later step greps (routes/, app/Providers/) that does not exist makes its checks not-applicable; record that in the report rather than failing.
Note the major version (v3 vs v4) — it determines the v3-vs-v4 notes in the checklist, though the security model is substantively identical. Record component_locations, class_namespace, and view_path if they deviate from defaults (resources/views/components, App\Livewire, resources/views/livewire); later steps honor the configured values.
Every component gets audited. No sampling. Enumerate all three forms.
Class-based components (v3 standard, still valid in v4):
find app/Livewire app/Http/Livewire -name '*.php' 2>/dev/null
Match each class to its view via view_path (default resources/views/livewire).
v4 single-file and multi-file components. The discovery marker is the embedded anonymous class block, NOT the filename prefix. The ⚡ (U+26A1, high-voltage) glyph prefix is purely cosmetic and not load-bearing — an un-prefixed file is equally valid. The real gate is content matching <?php ... new ... class:
grep -rlE --include='*.blade.php' 'new[[:space:]].*class' resources/views/ 2>/dev/null
grep -rl --include='*.blade.php' 'extends Component' resources/views/ 2>/dev/null
find resources/views -name '*⚡*' 2>/dev/null
Union the results of all three. The engine's own gate is the regex <?php ... new ... class (new, then class, with anything between), so the first grep matches new class, new #[Layout(...)] class, and attribute-decorated anonymous classes a literal 'new class' search would miss; the second catches components extending a custom base; the find catches prefixed files whose PHP block a grep might miss. Open any candidate and confirm it contains a new ... class extends ...Component block before counting it.
Honor any configured component_locations from config/livewire.php if it deviates from resources/views/components. For multi-file components, include the sibling .php, .blade.php, and .js files in the audited set for that component.
Full-page component routes and their middleware. Components reach routes several ways — class references, Route::view() pointing at a component view name, and Volt:
grep -rnE "Route::(get|post|any)" routes/ | grep -E "::class"
grep -rnE "Route::view\(" routes/
grep -rnE "Volt::route\(" routes/
Cross-reference every Route::view() target against the component inventory — a Route::view('/x', 'components.foo.bar') that resolves to a Livewire component file IS a full-page component route and gets the same middleware scrutiny.
State the total inventory count to the user (class-based + SFC/MFC + full-page routes). If the inventory is empty, say so and stop — do not audit non-Livewire code.
Dispatch read-only Explore-type subagents in batches of 5-10 components (count components, not files — one component = its class plus view plus any JS sibling), running batches in parallel. Each subagent prompt must include:
references/checklist.md and require it to read the ENTIRE file (all LW checks) before auditing — subagents have Read access, and this keeps prompts small. Alternatively paste the full text verbatim. Either way: NEVER summarize, condense, or excerpt the checklist — condensation is how checks get silently dropped (it has caused real missed findings). Shrink the batch before you shrink the checklist;.js);file, line, checkId (LW-NN), severity (Critical/High/Medium/Low/Info), evidence (a quoted line from the file), exploit (a concrete attacker scenario).Tell each subagent:
needs main-thread confirmation — do NOT drop it and do NOT inflate it to a confident finding.Nothing unverified reaches the report. For every finding returned by a subagent:
Then run the app-level checks in the main thread (these span the whole app, not one component, so subagents cannot own them):
providers/ for enforceMorphMap, morphMap, Relation::morphMap).config/livewire.php (temporary_file_upload rules / max size / mimes).Livewire::addPersistentMiddleware(...) registration in providers.Livewire::setUpdateRoute(...) (custom update endpoint and its middleware).Confirm each against actual code before it becomes a finding.
Only attempt this if the app demonstrably runs: php artisan about succeeds, or a dev server is already up. Never run migrations or seeders. If the app is not runnable, skip this step gracefully and say so in the report.
When runnable:
wire:snapshot attribute from the HTML.confirmed dynamically or statically inferred accordingly.See references/snapshot-internals.md for what the snapshot contains and how to read it.
Write the report to livewire-audit-YYYY-MM-DD.md (today's date) in the target project's root directory, using this exact template:
# Livewire Security Audit - <project> - <date>
Livewire version: <x> | Components audited: <n> | Findings: <n> (C/H/M/L/I: ...)
## Critical
### [LW-NN] <title> - <file>:<line>
Evidence: `<quoted code>`
Exploit: <concrete attacker steps>
Fix: <specific change, referencing the fixes.md pattern>
## High
### [LW-NN] <title> - <file>:<line>
Evidence: `<quoted code>`
Exploit: <concrete attacker steps>
Fix: <specific change, referencing the fixes.md pattern>
## Medium
...
## Low
...
## Info
...
## Clean components
<every component with zero findings, listed by name/path>
## Discarded during verification
<count> subagent findings did not survive main-thread verification.
Repeat the finding block per severity tier (Critical, High, Medium, Low, Info). Order findings within a tier by severity of impact.
Consolidation and counting rules — follow exactly:
Findings: <n> is the number of ### finding headers. Components audited counts components; full-page routes that point at counted components are noted, not double-counted.Filenames containing the ⚡ prefix: write the literal character in the report. If your environment blocks emoji output, substitute the placeholder [U+26A1] and state that convention once at the top of the report.
"Nothing found" is a valid result. If zero findings survive verification, say so plainly and list the audited inventory as evidence of coverage. Never pad the report or invent findings to justify the run.
After writing the report, end your reply by stating its exact path (livewire-audit-YYYY-MM-DD.md in the project root) so the user knows where to find it.
--fix only)For each confirmed finding, in severity order (Critical first):
references/fixes.md, smallest change first (the ordering note at the top of fixes.md explains the precedence).If a fix requires a product decision (which authorization policy applies, what the valid value set for a property is, whether an action should be reachable at all), do NOT guess. Collect those into a list of questions and ask the user.
Finish with a summary table:
| Finding | Fix applied | Re-check result |
| LW-NN | ... | pass/needs-decision |
livewire-audit-YYYY-MM-DD.md in the project root), so the user knows where to find it.Provides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.
npx claudepluginhub timgavin/claude-plugins --plugin livewire-audit