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

ArgumentRequiredDescription
targetYesTarget URL to scan. Must start with http:// or https://.

Options

FlagTypeDefaultDescription
--openapi <url>stringOpenAPI spec URL. Vet parses all paths from the spec for endpoint discovery.
--client-id <id>stringOAuth client ID. Enables OAuth-specific probes (V2 authentication checks).
--client-secret <secret>stringOAuth client secret. Used with --client-id for token exchange probes.
--only <categories>stringComma-separated list of categories to scan. See Categories.
--verbosebooleanfalsePrint probe progress to stderr. JSON output still goes to stdout.
--format <fmt>json | summaryjsonOutput format. json includes full check details. summary includes only counts and category statuses.
--timeout <ms>integer10000Per-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

CodeMeaning
INVALID_URLThe target URL is malformed.
TARGET_UNREACHABLEVet could not connect to the target.
SCAN_FAILEDThe 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

StatusMeaning
passCheck passed. No action needed.
failCheck failed. Fix required for CASA compliance.
warnPotential issue. Investigate but may be acceptable.
skipCheck 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 IDNamePass condition
V9.1.1HSTS presentStrict-Transport-Security header with max-age ≥ 31536000
V9.1.2X-Content-Type-OptionsHeader is nosniff
V9.1.3X-Frame-OptionsDENY or SAMEORIGIN, or CSP frame-ancestors 'none'/'self'
V9.1.4Content-Security-Policy presentHeader present, no unsafe-eval, no unsafe-inline in script-src/default-src on API routes
V9.1.5Referrer-PolicyOne of: strict-origin-when-cross-origin, no-referrer, same-origin, strict-origin
V9.1.6Permissions-Policy restricts sensitive featuresRestricts camera=(), microphone=(), geolocation=()
V9.1.7No server version disclosureNo version numbers in Server header, no X-Powered-By header
V9.1.8Cache-Control on auth endpointsCache-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 IDNamePass condition
V9.2.1TRACE method blockedTRACE returns 405 (or non-2xx)
V9.2.2TRACK method blockedTRACK returns 405 (or non-2xx without echo). CDN pass-through flagged as warn.
V9.2.3Unsupported methods return 405PROPFIND returns 405 (or non-2xx)
V9.2.4OPTIONS returns proper responseReturns 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 IDNamePass condition
V7.4.1No stack traces in errorsNo V8/Python/Java stack trace patterns in error responses
V7.4.2No version strings in errorsNo Express/, PHP/, nginx/, etc. in error response bodies
V7.4.3No internal paths in errorsNo /home/, /var/, C:\, node_modules in 4xx/5xx responses
V7.4.4No database errors in responsesNo SQL syntax errors, driver names, or leaked SQL in 4xx/5xx responses
V7.4.5No detailed auth error messagesNo “user not found”, “password incorrect”, email addresses in 401/403 responses
V7.4.6Server errors use short generic body5xx 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 IDNamePass condition
V9.3.1HTTPS enforcedTarget URL uses https://
V9.3.2TLS certificate validTLS handshake succeeds (valid, non-expired, non-self-signed cert)
V9.3.3HSTS enforcedStrict-Transport-Security with max-age ≥ 31536000 on HTTPS root
V9.3.4No mixed content indicatorsNo http:// in src, href, or action attributes in HTML responses
V9.3.5HTTP redirects to HTTPShttp:// version returns 301/302/307/308 redirect to https://. Connection refused on port 80 also passes.
V9.3.6TLS 1.0 rejectedServer rejects TLS 1.0 handshake (tested via openssl s_client)
V9.3.7TLS 1.1 rejectedServer rejects TLS 1.1 handshake (tested via openssl s_client)

cors / cors — ASVS V9.4.x

CORS configuration checks on every discovered endpoint.

Check IDNamePass condition
V9.4.1Evil origin not reflectedOrigin: https://evil.com not reflected in Access-Control-Allow-Origin with credentials
V9.4.2Null origin not allowedOrigin: null not reflected in Access-Control-Allow-Origin
V9.4.3Credentials not allowed with wildcardAccess-Control-Allow-Origin: * not combined with Access-Control-Allow-Credentials: true
V9.4.4CORS preflight validates methodsOPTIONS with Access-Control-Request-Method: DELETE from evil origin doesn’t allow DELETE
V9.4.5Subdomain suffix origin not reflectedOrigin: 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 IDNamePass condition
V5.3.4SQL injection protectionNo SQL error patterns in responses to SQLi payloads (' OR '1'='1, UNION SELECT, etc.)
V5.3.3XSS protection (reflected)XSS payloads (<script>alert(1)</script>, etc.) not reflected unescaped in HTML responses
V5.3.8Path traversal protectionNo file content patterns (root:, /bin/bash) in responses to traversal payloads
V5.1.1Request size limit1MB POST body returns 413 or 400 (not 200)
V5.1.2Malformed JSON handlingInvalid JSON POST returns 400 (not 500)
V5.3.10Null byte injection protectionNull bytes (\x00) not passed through in response bodies
V5.1.3Missing required fields handledEmpty JSON body {} POST returns 4xx (not 5xx)
V5.1.4Wrong field types handledType confusion ({"id": "not_a_number"}) returns 4xx (not 5xx)

Endpoint discovery

Vet discovers endpoints in this order:

  1. Base URL — always included
  2. Common paths/, /health, /api, /api/v1, /login, /oauth/token, /oauth/authorize, /webhooks
  3. Well-known endpoints/.well-known/openid-configuration, /.well-known/oauth-authorization-server (also parses their JSON for endpoint URLs)
  4. OpenAPI spec — all paths from the spec (if --openapi provided)
  5. robots.txtDisallow entries and Sitemap references
  6. Sitemap XML<loc> URLs from /sitemap.xml and sitemaps referenced in robots.txt

Endpoints are included if they respond with 2xx, 3xx, or 405.

Execution order

  1. Connectivity pre-check (single fetch to target)
  2. Endpoint discovery
  3. Passive probes in parallel (headers, methods, errors, TLS, CORS)
  4. Active probes sequentially (injection)
  5. 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 IDCategoryCheck name
V5.1.1input_validationRequest size limit
V5.1.2input_validationMalformed JSON handling
V5.1.3input_validationMissing required fields handled
V5.1.4input_validationWrong field types handled
V5.3.3input_validationXSS protection (reflected)
V5.3.4input_validationSQL injection protection
V5.3.8input_validationPath traversal protection
V5.3.10input_validationNull byte injection protection
V7.4.1error_disclosureNo stack traces in errors
V7.4.2error_disclosureNo version strings in errors
V7.4.3error_disclosureNo internal paths in errors
V7.4.4error_disclosureNo database errors in responses
V7.4.5error_disclosureNo detailed auth error messages
V7.4.6error_disclosureServer errors use short generic body
V9.1.1security_headersHSTS present
V9.1.2security_headersX-Content-Type-Options
V9.1.3security_headersX-Frame-Options
V9.1.4security_headersContent-Security-Policy present
V9.1.5security_headersReferrer-Policy
V9.1.6security_headersPermissions-Policy restricts sensitive features
V9.1.7security_headersNo server version disclosure
V9.1.8security_headersCache-Control on auth endpoints
V9.2.1method_enforcementTRACE method blocked
V9.2.2method_enforcementTRACK method blocked
V9.2.3method_enforcementUnsupported methods return 405
V9.2.4method_enforcementOPTIONS returns proper response
V9.3.1tls_sslHTTPS enforced
V9.3.2tls_sslTLS certificate valid
V9.3.3tls_sslHSTS enforced
V9.3.4tls_sslNo mixed content indicators
V9.3.5tls_sslHTTP redirects to HTTPS
V9.3.6tls_sslTLS 1.0 rejected
V9.3.7tls_sslTLS 1.1 rejected
V9.4.1corsEvil origin not reflected
V9.4.2corsNull origin not allowed
V9.4.3corsCredentials not allowed with wildcard
V9.4.4corsCORS preflight validates methods
V9.4.5corsSubdomain 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.