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:

  1. Discover endpoints — probes common paths (/api, /health, /oauth/token, etc.), well-known URIs, robots.txt, and sitemap.xml.
  2. Run passive probes in parallel — security headers, HTTP method enforcement, error disclosure, TLS/SSL, CORS.
  3. Run active probes sequentially — SQL injection, XSS, path traversal, and other input validation checks.
  4. 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:

FieldMeaning
oktrue if the scan completed, false if vet couldn’t run
summary.failNumber of checks that failed — these are what you fix
summary.warnIssues worth investigating but not hard failures
summary.skipChecks that couldn’t run (e.g., endpoint unreachable)
categoriesResults grouped by scan category
next_actionsSuggested 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