Complete reference for the vet command-line tool.
Synopsis
vet [command] <target> [options]
If no command is given and the first argument looks like a URL, vet treats it as vet scan <url>.
# These are equivalent
vet https://example.com
vet scan https://example.com
Commands
vet (no subcommand)
Prints tool info and available commands as JSON.
vet
{
"ok": true,
"command": "vet",
"result": {
"name": "vet",
"version": "0.1.0",
"description": "CASA Tier 2 / OWASP ASVS security scanner"
}
}
vet scan <url>
Run a security scan against the target URL.
vet scan https://example.com [options]
Arguments
| Argument | Required | Description |
|---|---|---|
target | Yes | Target URL to scan. Must start with http:// or https://. |
Options
| Flag | Type | Default | Description |
|---|---|---|---|
--openapi <url> | string | — | OpenAPI spec URL. Vet parses all paths from the spec for endpoint discovery. |
--client-id <id> | string | — | OAuth client ID. Enables OAuth-specific probes (V2 authentication checks). |
--client-secret <secret> | string | — | OAuth client secret. Used with --client-id for token exchange probes. |
--only <categories> | string | — | Comma-separated list of categories to scan. See Categories. |
--verbose | boolean | false | Print probe progress to stderr. JSON output still goes to stdout. |
--format <fmt> | json | summary | json | Output format. json includes full check details. summary includes only counts and category statuses. |
--timeout <ms> | integer | 10000 | Per-probe timeout in milliseconds. |
Output format
All output is JSON. No ANSI colors. No plain text.
Success envelope
{
ok: true,
command: "scan",
result: ScanReport,
next_actions: Array<{ command: string, description: string }>
}
Error envelope
{
ok: false,
command: "scan",
error: { message: string, code: string },
fix: string,
next_actions: Array<{ command: string, description: string }>
}
Error codes
| Code | Meaning |
|---|---|
INVALID_URL | The target URL is malformed. |
TARGET_UNREACHABLE | Vet could not connect to the target. |
SCAN_FAILED | The scan started but encountered an unrecoverable error. |
JSON schema: ScanReport
interface ScanReport {
target: string; // The URL that was scanned
scanned_at: string; // ISO 8601 timestamp
duration_ms: number; // Total scan wall time
summary: {
pass: number; // Checks that passed
fail: number; // Checks that failed
warn: number; // Checks with warnings
skip: number; // Checks that couldn't run
};
categories: Record<string, {
status: "pass" | "fail" | "warn"; // Worst status in category
checks: ProbeResult[]; // Individual check results
}>;
endpoints_scanned: number; // Unique endpoints probed
}
Category status is the worst status among its checks: fail > warn > pass. If all checks in a category are skip, the category status is warn.
JSON schema: ProbeResult
interface ProbeResult {
id: string; // ASVS control ID (e.g., "V9.1.1")
category: string; // Category key (e.g., "security_headers")
name: string; // Human-readable check name
status: "pass" | "fail" | "warn" | "skip";
evidence?: string; // What was observed (header value, status code, matched pattern)
remediation?: string; // How to fix (present on fail/warn)
endpoint?: string; // Which URL was probed
duration_ms?: number; // Probe round-trip time
}
Status values
| Status | Meaning |
|---|---|
pass | Check passed. No action needed. |
fail | Check failed. Fix required for CASA compliance. |
warn | Potential issue. Investigate but may be acceptable. |
skip | Check couldn’t run (endpoint unreachable, prerequisite missing). |
Summary format
With --format summary, the result object is simplified:
{
"ok": true,
"command": "scan",
"result": {
"target": "https://example.com",
"scanned_at": "2025-02-13T14:30:00.000Z",
"duration_ms": 12340,
"summary": { "pass": 42, "fail": 3, "warn": 5, "skip": 2 },
"endpoints_scanned": 12,
"category_statuses": {
"security_headers": "fail",
"method_enforcement": "pass",
"error_disclosure": "pass",
"tls_ssl": "pass",
"cors": "warn",
"input_validation": "pass"
}
},
"next_actions": []
}
No individual check details are included.
Categories
Use these names with the --only flag. Short name or full category name both work.
headers / security_headers — ASVS V9.1.x
Security header checks on every discovered endpoint.
| Check ID | Name | Pass condition |
|---|---|---|
| V9.1.1 | HSTS present | Strict-Transport-Security header with max-age ≥ 31536000 |
| V9.1.2 | X-Content-Type-Options | Header is nosniff |
| V9.1.3 | X-Frame-Options | DENY or SAMEORIGIN, or CSP frame-ancestors 'none'/'self' |
| V9.1.4 | Content-Security-Policy present | Header present, no unsafe-eval, no unsafe-inline in script-src/default-src on API routes |
| V9.1.5 | Referrer-Policy | One of: strict-origin-when-cross-origin, no-referrer, same-origin, strict-origin |
| V9.1.6 | Permissions-Policy restricts sensitive features | Restricts camera=(), microphone=(), geolocation=() |
| V9.1.7 | No server version disclosure | No version numbers in Server header, no X-Powered-By header |
| V9.1.8 | Cache-Control on auth endpoints | Cache-Control: no-store on endpoints matching /auth|token|oauth|login/ |
methods / method_enforcement — ASVS V9.2.x
HTTP method enforcement on every discovered endpoint.
| Check ID | Name | Pass condition |
|---|---|---|
| V9.2.1 | TRACE method blocked | TRACE returns 405 (or non-2xx) |
| V9.2.2 | TRACK method blocked | TRACK returns 405 (or non-2xx without echo). CDN pass-through flagged as warn. |
| V9.2.3 | Unsupported methods return 405 | PROPFIND returns 405 (or non-2xx) |
| V9.2.4 | OPTIONS returns proper response | Returns 405, or 2xx with Access-Control-Allow-Methods header |
errors / error_disclosure — ASVS V7.4.x
Error response inspection. Sends malformed requests and inspects error bodies.
| Check ID | Name | Pass condition |
|---|---|---|
| V7.4.1 | No stack traces in errors | No V8/Python/Java stack trace patterns in error responses |
| V7.4.2 | No version strings in errors | No Express/, PHP/, nginx/, etc. in error response bodies |
| V7.4.3 | No internal paths in errors | No /home/, /var/, C:\, node_modules in 4xx/5xx responses |
| V7.4.4 | No database errors in responses | No SQL syntax errors, driver names, or leaked SQL in 4xx/5xx responses |
| V7.4.5 | No detailed auth error messages | No “user not found”, “password incorrect”, email addresses in 401/403 responses |
| V7.4.6 | Server errors use short generic body | 5xx responses are <2000 chars, no exception class names (TypeError, NullPointerException, etc.) |
Probes sent: junk query param with path traversal, malformed JSON POST, path traversal in URL, non-existent path, invalid Authorization: Bearer header.
tls / tls_ssl — ASVS V9.3.x
TLS/SSL configuration checks.
| Check ID | Name | Pass condition |
|---|---|---|
| V9.3.1 | HTTPS enforced | Target URL uses https:// |
| V9.3.2 | TLS certificate valid | TLS handshake succeeds (valid, non-expired, non-self-signed cert) |
| V9.3.3 | HSTS enforced | Strict-Transport-Security with max-age ≥ 31536000 on HTTPS root |
| V9.3.4 | No mixed content indicators | No http:// in src, href, or action attributes in HTML responses |
| V9.3.5 | HTTP redirects to HTTPS | http:// version returns 301/302/307/308 redirect to https://. Connection refused on port 80 also passes. |
| V9.3.6 | TLS 1.0 rejected | Server rejects TLS 1.0 handshake (tested via openssl s_client) |
| V9.3.7 | TLS 1.1 rejected | Server rejects TLS 1.1 handshake (tested via openssl s_client) |
cors / cors — ASVS V9.4.x
CORS configuration checks on every discovered endpoint.
| Check ID | Name | Pass condition |
|---|---|---|
| V9.4.1 | Evil origin not reflected | Origin: https://evil.com not reflected in Access-Control-Allow-Origin with credentials |
| V9.4.2 | Null origin not allowed | Origin: null not reflected in Access-Control-Allow-Origin |
| V9.4.3 | Credentials not allowed with wildcard | Access-Control-Allow-Origin: * not combined with Access-Control-Allow-Credentials: true |
| V9.4.4 | CORS preflight validates methods | OPTIONS with Access-Control-Request-Method: DELETE from evil origin doesn’t allow DELETE |
| V9.4.5 | Subdomain suffix origin not reflected | Origin: https://example.com.evil.com not reflected in ACAO with credentials |
injection / input_validation — ASVS V5.x
Active input validation probes. Sends attack payloads to endpoints.
| Check ID | Name | Pass condition |
|---|---|---|
| V5.3.4 | SQL injection protection | No SQL error patterns in responses to SQLi payloads (' OR '1'='1, UNION SELECT, etc.) |
| V5.3.3 | XSS protection (reflected) | XSS payloads (<script>alert(1)</script>, etc.) not reflected unescaped in HTML responses |
| V5.3.8 | Path traversal protection | No file content patterns (root:, /bin/bash) in responses to traversal payloads |
| V5.1.1 | Request size limit | 1MB POST body returns 413 or 400 (not 200) |
| V5.1.2 | Malformed JSON handling | Invalid JSON POST returns 400 (not 500) |
| V5.3.10 | Null byte injection protection | Null bytes (\x00) not passed through in response bodies |
| V5.1.3 | Missing required fields handled | Empty JSON body {} POST returns 4xx (not 5xx) |
| V5.1.4 | Wrong field types handled | Type confusion ({"id": "not_a_number"}) returns 4xx (not 5xx) |
Endpoint discovery
Vet discovers endpoints in this order:
- Base URL — always included
- Common paths —
/,/health,/api,/api/v1,/login,/oauth/token,/oauth/authorize,/webhooks - Well-known endpoints —
/.well-known/openid-configuration,/.well-known/oauth-authorization-server(also parses their JSON for endpoint URLs) - OpenAPI spec — all paths from the spec (if
--openapiprovided) - robots.txt —
Disallowentries andSitemapreferences - Sitemap XML —
<loc>URLs from/sitemap.xmland sitemaps referenced in robots.txt
Endpoints are included if they respond with 2xx, 3xx, or 405.
Execution order
- Connectivity pre-check (single
fetchto target) - Endpoint discovery
- Passive probes in parallel (headers, methods, errors, TLS, CORS)
- Active probes sequentially (injection)
- Result aggregation and report generation
Exit codes
Vet always exits 0. Scan results (pass/fail) are in the JSON envelope, not the exit code. Check ok and summary.fail in the output to determine pass/fail status.
# Check scan success
vet https://example.com | jq '.ok'
# Check for failures
vet https://example.com | jq '.result.summary.fail'
Complete ASVS control ID map
| Control ID | Category | Check name |
|---|---|---|
| V5.1.1 | input_validation | Request size limit |
| V5.1.2 | input_validation | Malformed JSON handling |
| V5.1.3 | input_validation | Missing required fields handled |
| V5.1.4 | input_validation | Wrong field types handled |
| V5.3.3 | input_validation | XSS protection (reflected) |
| V5.3.4 | input_validation | SQL injection protection |
| V5.3.8 | input_validation | Path traversal protection |
| V5.3.10 | input_validation | Null byte injection protection |
| V7.4.1 | error_disclosure | No stack traces in errors |
| V7.4.2 | error_disclosure | No version strings in errors |
| V7.4.3 | error_disclosure | No internal paths in errors |
| V7.4.4 | error_disclosure | No database errors in responses |
| V7.4.5 | error_disclosure | No detailed auth error messages |
| V7.4.6 | error_disclosure | Server errors use short generic body |
| V9.1.1 | security_headers | HSTS present |
| V9.1.2 | security_headers | X-Content-Type-Options |
| V9.1.3 | security_headers | X-Frame-Options |
| V9.1.4 | security_headers | Content-Security-Policy present |
| V9.1.5 | security_headers | Referrer-Policy |
| V9.1.6 | security_headers | Permissions-Policy restricts sensitive features |
| V9.1.7 | security_headers | No server version disclosure |
| V9.1.8 | security_headers | Cache-Control on auth endpoints |
| V9.2.1 | method_enforcement | TRACE method blocked |
| V9.2.2 | method_enforcement | TRACK method blocked |
| V9.2.3 | method_enforcement | Unsupported methods return 405 |
| V9.2.4 | method_enforcement | OPTIONS returns proper response |
| V9.3.1 | tls_ssl | HTTPS enforced |
| V9.3.2 | tls_ssl | TLS certificate valid |
| V9.3.3 | tls_ssl | HSTS enforced |
| V9.3.4 | tls_ssl | No mixed content indicators |
| V9.3.5 | tls_ssl | HTTP redirects to HTTPS |
| V9.3.6 | tls_ssl | TLS 1.0 rejected |
| V9.3.7 | tls_ssl | TLS 1.1 rejected |
| V9.4.1 | cors | Evil origin not reflected |
| V9.4.2 | cors | Null origin not allowed |
| V9.4.3 | cors | Credentials not allowed with wildcard |
| V9.4.4 | cors | CORS preflight validates methods |
| V9.4.5 | cors | Subdomain suffix origin not reflected |
Build and install
git clone https://github.com/acoyfellow/vet.git
cd vet
bun install
bun build src/cli.ts --compile --outfile vet
cp vet ~/.bun/bin/vet
Requires Bun v1.0+. The compiled binary has zero runtime dependencies.