Skip to main content
Extensions let you add custom scanning logic to Vigolium without modifying the core scanner. You can write them in JavaScript for full flexibility, in YAML for declarative pattern matching, or use lightweight quick checks and snippets for fast iteration.

Table of Contents


Overview

Extensions plug into the scanner pipeline at four points: All four types are supported in JS, YAML, and (for active/passive) as quick checks or snippets. YAML is simpler for straightforward pattern matching. JS gives you full access to HTTP requests, regex, encoding utilities, the database API, OAST (out-of-band) testing, and optional AI-augmented analysis. Quick checks and snippets are even lighter, ideal for agent-generated or ad-hoc checks.

Setup

1. Enable extensions in your config

Add or uncomment the extensions block under dynamic-assessment in your vigolium-configs.yaml:

2. Place your extension file

Drop any .js or .vgm.yaml file into your extension_dir. Vigolium discovers them automatically on the next scan.

3. Verify it loaded


Extension Types

Module export contract (JS)

Every JS extension must export a module.exports object. Required fields:

Writing a JavaScript Extension

JS extensions run inside an embedded Sobek (ES2015+) VM. The global vigolium object provides all APIs.

Active Module (JS)

Active modules send modified requests to probe for vulnerabilities. Declare which scan granularity you need in scanTypes:
  • per_insertion_point: called once per parameter (query, body, header, cookie)
  • per_request: called once per request/response pair
  • per_host: called once per unique hostname
per_insertion_point example: detect reflected input:
per_request example: detect error messages in existing responses:
Return value for active/passive: an array of finding objects, or null if nothing found. Each finding object:

Passive Module (JS)

Passive modules analyze existing request/response pairs without making new requests. Add a scope field to limit to "request", "response", or "both" (default).

Pre-Hook (JS)

Pre-hooks run before each request is sent to a module. Return the modified request, a headers-only patch, or null to skip the request entirely.
Return value options: Skip static assets example:

Post-Hook (JS)

Post-hooks receive each emitted finding. Return the (possibly modified) result, or null to suppress the finding.

Writing a YAML Extension

YAML extensions (.vgm.yaml) are a declarative alternative for common patterns. They require no programming knowledge and are compiled to the same internal module interface as JS extensions.

Active Module (YAML)

Use rules to define match-then-emit pairs. Each rule specifies a match condition and the finding to emit when it matches.
Top-level active fields: Use rules when different patterns should emit different findings. Use matchers + finding when all conditions must be true together. Matcher types:

Passive Module (YAML)

Passive YAML modules use the same rules structure but do not send new requests:
Rule match fields for rules[].match:

Pre-Hook (YAML)

Pre-hooks in YAML support header injection, extension skipping, and conditional skipping. Inject headers:
Skip static files:
Pre-hook YAML fields: Template functions available in header values:

Post-Hook (YAML)

Post-hooks in YAML can escalate severity or drop findings based on URL patterns. Escalate severity for critical paths:
Drop low-severity findings on certain paths:

Quick Checks

Quick checks are the lightest extension format, declarative JSON objects that define “send payload, check response” patterns with zero JavaScript. They’re ideal for agent-generated checks and rapid iteration.

Per Insertion Point

Inject payloads into each parameter and check the response:

Per Request / Per Host

Send specific requests and check responses:

Match Fields

Match conditions use OR logic:

Rules

  • id must be lowercase with hyphens (e.g. "ssti-jinja2")
  • scan is one of: per_insertion_point, per_request, per_host
  • severity is one of: critical, high, medium, low, info
  • Quick checks are automatically wrapped into full extension modules at runtime

Snippets

Snippets are a middle ground between quick checks and full extensions, you write just the function body (no boilerplate), and it gets wrapped in a module scaffold automatically. Use snippets when you need custom logic or vigolium.* API access.

Format

Available Variables

Inside the snippet body, you have access to:

Rules

  • id must be lowercase with hyphens
  • scan is one of: per_insertion_point, per_request, per_host
  • body contains the function body as a string (newlines escaped as \n)
  • The return value follows the same convention as full extensions: array of findings or null

Context Objects Reference

ctx: passed to all active/passive module functions

insertion: second arg to scanPerInsertionPoint

ctx.record: current HTTP record context


API Reference

vigolium.log

vigolium.utils

Encoding: Hashing: Random: Regex: File I/O: URL utilities: Parameter utilities: Diff and similarity: HTML: Token extraction: JWT: Multipart: Anomaly detection: Other:

vigolium.parse

vigolium.http

Basic requests: Sessions: Session objects expose: get(), post(), request(), send(), setHeader(), removeHeader(), getHeaders(), getCookies(), setCookie(), cloneAs(), onRequest(), onResponse(), setAutoRefresh(). Batch and replay: Multi-step workflows: Auth testing: Retry and caching: GraphQL:

vigolium.scan

vigolium.ingest

vigolium.source

vigolium.agent (AI-augmented)

vigolium.oast (Out-of-Band Testing)

vigolium.db

Records: Findings: Comparison:

vigolium.payloads(type)

Returns built-in payload wordlists by vulnerability type. Types: "xss", "sqli", "ssti", "ssrf", "lfi", "path_traversal", "xxe", "cmdi", "open_redirect", "crlf".

vigolium.config

Read-only config values from the variables block in vigolium-configs.yaml:

Testing Your Extension

Run only the extension phase

The fastest way to test your extension against already-ingested traffic without running a full scan:
This skips discovery, spidering, and standard audit modules, only your extensions run against traffic already in the database.

Test against a live target

To ingest fresh traffic and immediately run only extensions:

Use a one-off config with a custom extension path

You don’t need to copy files to ~/.vigolium/extensions/. Use custom_dir to point directly at your file:
With my-test-config.yaml:

Verify your extension loads

Before running a scan, check your extension is discovered and parsed correctly:

Browse the built-in API reference

Install preset examples to learn from


Configuration Reference

Full extensions block options in vigolium-configs.yaml:

Tips and Best Practices

Return null, not []: returning an empty array is treated the same as null, but null is the conventional no-finding signal. Check for nil before accessing properties:
Use vigolium.utils.randomString for canaries to avoid collisions between concurrent extension invocations. Keep pre-hooks fast: they run on every request before any module sees it. Avoid HTTP calls inside pre-hooks. YAML vs JS vs quick check decision guide:
  • Use quick checks when you need simple payload-and-match patterns with no logic
  • Use snippets when you need vigolium.* API access but don’t want full boilerplate
  • Use YAML when you need regex/header/status matching with a fixed finding output
  • Use JS when you need: conditional logic, multiple HTTP requests, encoding/decoding, database lookups, session management, or AI-augmented analysis
Scope your passive module: set scope: "response" if you only need response data. This avoids unnecessary invocations. Use vigolium.config.* for secrets and environment-specific values instead of hardcoding them:
Use built-in payloads instead of hardcoding wordlists:
Enable response caching for extensions that make repeated baseline requests:
Use sessions for multi-request flows: sessions persist cookies and headers:
Avoid hardcoding the extension id if you plan to distribute extensions, the filename without extension is used as the default ID, which is usually fine. Test incrementally: start with --only extension and a small known dataset so your module’s console.log output is easy to read.