You’ll install vet, scan a URL, read the results, fix a finding, and verify the fix.
Prerequisites
- Bun v1.0+
- A running HTTPS web application to scan
1. Install vet
Clone the repo and compile a standalone binary:
git clone https://github.com/acoyfellow/vet.git
cd vet
bun install
bun build src/cli.ts --compile --outfile vet
Copy it somewhere on your PATH:
cp vet ~/.bun/bin/vet
Verify the install:
vet
You’ll get JSON describing the tool and available commands:
{
"ok": true,
"command": "vet",
"result": {
"name": "vet",
"version": "0.1.0",
"description": "CASA Tier 2 / OWASP ASVS security scanner"
}
}
2. Run a basic scan
Point vet at your app:
vet https://your-app.com
That’s shorthand for vet scan https://your-app.com. Vet will:
- Discover endpoints — probes common paths (
/api,/health,/oauth/token, etc.), well-known URIs,robots.txt, andsitemap.xml. - Run passive probes in parallel — security headers, HTTP method enforcement, error disclosure, TLS/SSL, CORS.
- Run active probes sequentially — SQL injection, XSS, path traversal, and other input validation checks.
- Output a JSON report to stdout.
The scan typically takes 5–30 seconds depending on endpoint count.
3. Read the JSON output
The output is a JSON envelope. Here’s the structure of a successful scan:
{
"ok": true,
"command": "scan",
"result": {
"target": "https://your-app.com",
"scanned_at": "2025-02-13T14:30:00.000Z",
"duration_ms": 12340,
"summary": {
"pass": 42,
"fail": 3,
"warn": 5,
"skip": 2
},
"categories": {
"security_headers": {
"status": "fail",
"checks": [ ... ]
},
"tls_ssl": {
"status": "pass",
"checks": [ ... ]
}
},
"endpoints_scanned": 12
},
"next_actions": [
{
"command": "vet scan https://your-app.com --only security_headers --verbose",
"description": "Re-scan 1 failing categories with verbose output"
}
]
}
Key fields:
| Field | Meaning |
|---|---|
ok | true if the scan completed, false if vet couldn’t run |
summary.fail | Number of checks that failed — these are what you fix |
summary.warn | Issues worth investigating but not hard failures |
summary.skip | Checks that couldn’t run (e.g., endpoint unreachable) |
categories | Results grouped by scan category |
next_actions | Suggested follow-up commands |
Each check inside a category looks like this:
{
"id": "V9.1.1",
"category": "security_headers",
"name": "HSTS present",
"status": "fail",
"evidence": "Header missing",
"remediation": "Add Strict-Transport-Security header with max-age>=31536000.",
"endpoint": "https://your-app.com",
"duration_ms": 45
}
The id maps to an OWASP ASVS control. The remediation field tells you exactly what to do.
4. Fix a finding: missing HSTS header
The most common finding on a first scan is a missing Strict-Transport-Security header (control V9.1.1).
Nginx — add to your server block:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Caddy — add to your Caddyfile:
header Strict-Transport-Security "max-age=31536000; includeSubDomains"
Express.js — use helmet:
import helmet from 'helmet';
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true }));
Cloudflare — Enable HSTS in SSL/TLS → Edge Certificates → HTTP Strict Transport Security.
Deploy the change.
5. Re-scan to verify
Re-scan just the headers category to check your fix:
vet https://your-app.com --only headers --verbose
The --verbose flag prints progress to stderr as each probe runs:
Discovering endpoints for https://your-app.com...
Found 8 endpoints
Running 1 passive probes in parallel...
[headers] starting...
[headers] done (64 checks)
In the JSON output, look for the V9.1.1 check:
{
"id": "V9.1.1",
"name": "HSTS present",
"status": "pass",
"evidence": "max-age=31536000; includeSubDomains"
}
Status is pass. Fix confirmed.
6. Next steps
- Run a full scan and work through each
failfinding:vet https://your-app.com - Add vet to your CI pipeline: see How-to: Add vet to your CI pipeline
- If you’re building an OAuth app for Google, add
--client-idand--client-secretfor auth flow testing: see How-to: Scan OAuth apps - Read the full CLI reference for every flag and output field