Security testing
Status: production, shipped since v0.7, on the widget plugin-UI protocol since v0.9. As of v0.9 the orange mode bar, the network panel, the flow row rendering, and the
security:flow:*WS handlers are all contributed bypackages/security/src/widget.jsvia the newwindow.__HOVER_WIDGET__host API —@hover-dev/core/client.jsno longer carries any security-specific branches. Recording semantics for security sessions shipped in v0.12 — see Save as Security spec.
@hover-dev/security is Hover's first optional plugin. Switch the widget into Security testing mode and Hover starts capturing every HTTPS call your dev page makes — then lets the AI agent re-issue any of those calls with mutations to probe for IDOR / authentication bypass / parameter tampering. Findings save as plain Playwright specs that run in CI without the proxy.
Why a separate mode
Hover's default mode is for building features. Security testing is for attacking what you just built — the agent's prompt is different ("look for authz bypass", not "test the happy path"), its captured traffic is routed through a local MITM proxy, and the mode bar tints orange so you can never forget you're in altered state.
Install
pnpm add -D @hover-dev/security
// vite.config.ts (Astro / Nuxt / Next / Webpack mirror the same pattern)
import { hover } from 'vite-plugin-hover';
import securityMode from '@hover-dev/security';
export default defineConfig({
plugins: [hover({}, securityMode())],
});
Zero external dependencies — no mitmproxy, no Python, no system CA install. The plugin uses mockttp (the engine behind HTTP Toolkit) for HTTPS MITM, generates a one-off CA the first time it starts, and pins it to the debug Chrome via --ignore-certificate-errors-spki-list — your OS trust store stays untouched.
The proxy is resident: when @hover-dev/security is installed it starts with the dev server (transparent passthrough by default) and the single debug Chrome is launched pointed through it. There is no second browser — entering Security mode just flips the proxy from passthrough to recording. The trade-off is that the one Chrome is always proxied (transparently) even in default mode; projects without the plugin are unaffected.
Don't commit the CA
The CA private key persists under <your-project>/.hover/ca/ca.key. The shipped .gitignore includes .hover/ already; if you removed it, add it back.
Usage
-
Click the ✨ launcher, then the mode bar above the panel header.
-
Pick Security testing.
-
The panel border + launcher ring turn orange to signal altered state. No new browser opens — the same debug Chrome (already routed through the resident proxy) simply starts having its traffic recorded.
-
Drive the page as a normal user would — log in, navigate, submit forms. Every HTTPS request is captured.
-
Click the 🔁 Network icon next to the agent pill to see the captured flow list.
-
In the chat textarea, ask the agent something like:
list_flows, then look for IDOR vulnerabilities in the order endpointsThe agent uses
mcp__hover_dev_security_flows__list_flowsto enumerate the API surface,get_flowto inspect specific requests, andreplay_flowto test mutations. -
When findings show up in the Result + Findings cards, click Save as → Security spec to crystallise the recorded
replay_flowchecks into a__vibe_tests__/<slug>.security.spec.tsregression test that runs in CI with vanilla@playwright/test. See Save as Security spec.
What the agent looks for
The system prompt restricts the agent to browser-reachable vulnerability classes, in this priority order:
1. Authorisation / authentication (highest signal)
- IDOR — change a resource id in a captured URL and replay. A 200 OK is the vulnerability.
- Authentication bypass — drop or swap the auth header in a replay.
- Parameter tampering — mutate request body fields (
user_id,role,price,isAdmin) and replay. - Mass assignment — add fields the form didn't expose (
admin: true,email: "victim@…") and check if they take effect.
2. Frontend / browser-side issues
- XSS — inject
<script>,javascript:, oronerror=into URL params, form inputs, and postMessage handlers. - Open redirects — find URL params that control redirect targets.
- DOM clobbering / prototype pollution — only flagged when the agent can demonstrate concrete impact, not theoretical surface.
- Missing security headers — CSP, X-Frame-Options, HSTS, SameSite cookies.
3. Compliance / privacy (GDPR / CCPA signals)
- PII in URL query strings (email, name, phone in GET params).
- Cookies without
Secure/HttpOnly/SameSitewhen carrying session data. - Third-party requests carrying user data before consent was granted.
Scope boundaries
The agent will refuse to attempt:
- SQL injection, SSRF, command injection, deserialisation attacks — these are server-side concerns this browser-driven framework can't usefully probe. The prompt explicitly forbids them.
- Automated fuzzing loops — security mode stays surgical: one hypothesis, one targeted replay, one observation.
- Modifying CSP / cookie settings before testing — the application is probed as deployed.
- Real-user-data exfiltration — this is a dev environment; the agent uses placeholder ids when demonstrating an issue.
If you need server-side fuzzing or SQL injection testing, run an actual server-side scanner (sqlmap, ZAP active scan, etc.) — Hover is not that tool.
Tools available to the agent in security mode
| Tool | Purpose |
|---|---|
list_flows() | Enumerate captured HTTP flows (no bodies — just method / url / status / mutation marker). |
get_flow(id) | Full request + response headers + body for one flow. |
replay_flow(id, mutation?) | Re-issue a captured flow with optional method / url / headers / body overrides. The new flow is added to the store with its own id. |
clear_flows() | Drop captured flows between probe rounds. |
mcp__playwright__* | Standard browser-driving tools — navigate, click, fill, screenshot, evaluate. |
Mutations to replay_flow use a small JSON shape:
{
method?: string; // override HTTP method
url?: string; // override URL — typical IDOR test
headers?: Record<string, string | null>; // overrides; null deletes
bodyText?: string; // replace UTF-8 body
}
The shape mirrors the agent-facing MCP schema, so what you see in the docs is what the agent receives in its tool catalogue.
Reporting style
When the agent finishes, findings render in a colour-coded Findings card next to the Result card. The agent uses these markers in its ## Findings block:
- Bug — concrete vulnerability with reproducible impact. Red.
- Minor — weak hardening, no immediate exploit (e.g. missing header). Amber.
- (no marker) — informational observation. Neutral.
Crystallized output
Spec output looks like:
// __vibe_tests__/orders-idor-victim-can-view-their-own.spec.ts
import { test, expect } from '@playwright/test';
test('User A cannot read User B order', async ({ page, request }) => {
// Log in as User A
await page.goto('/login');
await page.getByLabel('Email').fill('userA@example.com');
await page.getByLabel('Password').fill('test-password');
await page.getByRole('button', { name: 'Sign in' }).click();
// Attempt to read User B's order
const res = await request.get('/api/orders/userB-1', {
headers: { cookie: (await page.context().cookies()).map(c => `${c.name}=${c.value}`).join('; ') },
});
expect(res.status()).toBe(403);
});
The MITM proxy is not part of this spec. The replay primitive lives in Playwright's own request fixture — CI runs this with vanilla @playwright/test, no Hover, no @hover-dev/security, no mockttp.
Implementation primer
For contributors who want to extend the plugin or write a similar one:
packages/security/src/mitm/— mockttp lifecycle (CA generation, FlowStore, proxy wrapper, replay primitives).packages/security/src/control-plane.ts— loopback HTTP API the MCP server talks to (Bearer-token auth on a process-random secret).packages/security/src/mcp/server.ts— the stdio MCP server using@modelcontextprotocol/sdk. Tool descriptions explicitly mention IDOR / authz-bypass / parameter-tampering use cases so the agent picks the right one.packages/security/src/index.ts— the plugin manifest itself.
See Reference → Plugin API for the manifest shape @hover-dev/security is built on.
Limitations (honest)
- Service workers — Playwright's
page.route()historically can't see SW-mediated requests. The MITM proxy bypasses this (it's at the network layer, not the renderer layer), so capture works fine. But if your saved spec relies on observing a SW-routed request, you'll need to express it aspage.request.fetch()(which goes around the SW) rather thanpage.route(). - HTTP/3 / QUIC — Chrome will quietly downgrade through the proxy. Not visible as h3 in the captured flow list.
- Cross-origin iframes — captured, but the widget's panel currently flattens the flow list; correlating which iframe a flow came from is future work.
- Session recording for security sessions — the classic Record button (record clicks → spec) is hidden when security mode is engaged, since click→spec semantics don't apply to a network-probing session. The security equivalent is supported in v0.12: the agent calls
replay_flow({ intent, expectStatus })to record security checks, and Save as → Security spec crystallises them into a Playwright regression spec. See Save as Security spec.