{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://vibecheck.themeridianlab.com/scan-output-schema.json",
  "title": "vibecheck scan report",
  "description": "JSON Schema for the response body of POST /api/scan. Validates the full ScanReport shape returned by vibecheck (https://vibecheck.themeridianlab.com) when scanning a deployed URL. v1.3.0.",
  "type": "object",
  "required": [
    "targetUrl",
    "scannedAt",
    "durationMs",
    "fetcherErrors",
    "bundleCount",
    "supabase",
    "firebase",
    "secrets",
    "paths",
    "api",
    "headers",
    "storage",
    "framework",
    "prompts",
    "defaults",
    "leaks",
    "baas",
    "subdomains",
    "redirects",
    "jwts",
    "deepScan",
    "overallSeverity",
    "grade"
  ],
  "properties": {
    "targetUrl": { "type": "string", "format": "uri" },
    "scannedAt": { "type": "number", "description": "Unix seconds (float)" },
    "durationMs": { "type": "integer", "minimum": 0 },
    "fetcherErrors": { "type": "array", "items": { "type": "string" } },
    "bundleCount": { "type": "integer", "minimum": 0 },
    "deepScan": { "type": "boolean" },
    "overallSeverity": { "$ref": "#/$defs/Severity" },
    "grade": { "type": "string", "enum": ["A", "B", "C", "D", "F"] },
    "supabase": { "type": "array", "items": { "$ref": "#/$defs/SupabaseFinding" } },
    "firebase": { "type": "array", "items": { "$ref": "#/$defs/FirebaseFinding" } },
    "secrets": { "type": "array", "items": { "$ref": "#/$defs/SecretFinding" } },
    "paths": { "type": "array", "items": { "$ref": "#/$defs/PathFinding" } },
    "api": { "type": "array", "items": { "$ref": "#/$defs/ApiFinding" } },
    "headers": { "type": "array", "items": { "$ref": "#/$defs/HeaderFinding" } },
    "storage": { "type": "array", "items": { "$ref": "#/$defs/StorageFinding" } },
    "framework": { "type": "array", "items": { "$ref": "#/$defs/FrameworkFinding" } },
    "prompts": { "type": "array", "items": { "$ref": "#/$defs/PromptFinding" } },
    "defaults": { "type": "array", "items": { "$ref": "#/$defs/DefaultsFinding" } },
    "leaks": { "type": "array", "items": { "$ref": "#/$defs/LeakFinding" } },
    "baas": { "type": "array", "items": { "$ref": "#/$defs/BaasFinding" } },
    "subdomains": { "type": "array", "items": { "$ref": "#/$defs/SubdomainFinding" } },
    "redirects": { "type": "array", "items": { "$ref": "#/$defs/RedirectFinding" } },
    "jwts": { "type": "array", "items": { "$ref": "#/$defs/JwtFinding" } }
  },
  "$defs": {
    "Severity": {
      "type": "string",
      "enum": ["critical", "high", "medium", "low", "info"]
    },
    "SupabaseFinding": {
      "type": "object",
      "required": ["project", "rlsFindings", "notes", "severity"],
      "properties": {
        "project": {
          "type": "object",
          "required": ["projectRef", "url", "keys"],
          "properties": {
            "projectRef": { "type": "string" },
            "url": { "type": "string", "format": "uri" },
            "keys": {
              "type": "object",
              "additionalProperties": { "type": "string" },
              "description": "Map of role → JWT (e.g. {anon: '...', service_role: '...'})"
            }
          }
        },
        "rlsFindings": { "type": "array", "items": { "$ref": "#/$defs/TableFinding" } },
        "notes": { "type": "array", "items": { "type": "string" } },
        "severity": { "$ref": "#/$defs/Severity" }
      }
    },
    "TableFinding": {
      "type": "object",
      "required": ["table", "publiclyReadable", "rowCountSample", "piiColumns", "allColumns"],
      "properties": {
        "table": { "type": "string" },
        "publiclyReadable": { "type": "boolean" },
        "rowCountSample": { "type": "integer", "minimum": 0, "maximum": 1 },
        "piiColumns": { "type": "array", "items": { "type": "string" } },
        "allColumns": { "type": "array", "items": { "type": "string" } },
        "error": { "type": "string" }
      }
    },
    "FirebaseFinding": {
      "type": "object",
      "required": ["projectId", "rtdbOpen", "notes", "severity"],
      "properties": {
        "projectId": { "type": "string" },
        "rtdbOpen": { "type": ["boolean", "null"] },
        "notes": { "type": "array", "items": { "type": "string" } },
        "severity": { "$ref": "#/$defs/Severity" }
      }
    },
    "SecretFinding": {
      "type": "object",
      "required": ["rule", "severity", "description", "redactedMatch", "location"],
      "properties": {
        "rule": { "type": "string" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" },
        "redactedMatch": { "type": "string", "description": "first 6 + last 4 chars only" },
        "location": { "type": "string", "description": "<html> | <inline:N> | <bundle URL> | sourcemap:<path>" }
      }
    },
    "PathFinding": {
      "type": "object",
      "required": ["path", "severity", "description", "status"],
      "properties": {
        "path": { "type": "string" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" },
        "status": { "type": "integer" },
        "evidence": { "type": "string" }
      }
    },
    "ApiFinding": {
      "type": "object",
      "required": ["endpoint", "severity", "description"],
      "properties": {
        "endpoint": { "type": "string" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" },
        "evidence": { "type": "string" }
      }
    },
    "HeaderFinding": {
      "type": "object",
      "required": ["rule", "severity", "description"],
      "properties": {
        "rule": { "type": "string" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" },
        "evidence": { "type": "string" }
      }
    },
    "StorageFinding": {
      "type": "object",
      "required": ["provider", "bucket", "url", "severity", "description"],
      "properties": {
        "provider": { "type": "string", "enum": ["s3", "gcs", "azure", "r2"] },
        "bucket": { "type": "string" },
        "url": { "type": "string", "format": "uri" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" },
        "evidence": { "type": "string" }
      }
    },
    "FrameworkFinding": {
      "type": "object",
      "required": ["rule", "severity", "description"],
      "properties": {
        "rule": { "type": "string" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" },
        "evidence": { "type": "string" }
      }
    },
    "PromptFinding": {
      "type": "object",
      "required": ["rule", "severity", "description", "evidence", "location"],
      "properties": {
        "rule": { "type": "string" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" },
        "evidence": { "type": "string" },
        "location": { "type": "string" }
      }
    },
    "DefaultsFinding": {
      "type": "object",
      "required": ["platform", "endpoint", "severity", "description"],
      "properties": {
        "platform": { "type": "string" },
        "endpoint": { "type": "string", "format": "uri" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" }
      }
    },
    "LeakFinding": {
      "type": "object",
      "required": ["rule", "severity", "description", "evidence", "location"],
      "properties": {
        "rule": { "type": "string" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" },
        "evidence": { "type": "string" },
        "location": { "type": "string" }
      }
    },
    "BaasFinding": {
      "type": "object",
      "required": ["rule", "provider", "endpoint", "severity", "description"],
      "properties": {
        "rule": { "type": "string" },
        "provider": { "type": "string", "enum": ["convex", "appwrite", "pocketbase"] },
        "endpoint": { "type": "string" },
        "collection": { "type": "string" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" },
        "evidence": { "type": "string" }
      }
    },
    "SubdomainFinding": {
      "type": "object",
      "required": ["subdomain", "severity", "description"],
      "properties": {
        "subdomain": { "type": "string" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" },
        "evidence": { "type": "string" }
      }
    },
    "RedirectFinding": {
      "type": "object",
      "required": ["url", "param", "severity", "description"],
      "properties": {
        "url": { "type": "string", "format": "uri" },
        "param": { "type": "string" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" },
        "evidence": { "type": "string" }
      }
    },
    "JwtFinding": {
      "type": "object",
      "required": ["rule", "severity", "description", "redactedToken"],
      "properties": {
        "rule": { "type": "string" },
        "severity": { "$ref": "#/$defs/Severity" },
        "description": { "type": "string" },
        "redactedToken": { "type": "string" },
        "alg": { "type": "string" },
        "iss": { "type": "string" },
        "exp": { "type": ["number", "null"] },
        "evidence": { "type": "string" }
      }
    }
  }
}
