vyql

module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 12, 2026 License: Apache-2.0

README

VyQL

ci detection data security Go Reference License

A security scanner for 22 languages. It follows untrusted input through your code and tells you why it thinks each finding is real.

There are two parts. A small Go engine builds one graph of your program and answers questions about it. Everything the engine knows about frameworks, sinks, sanitizers and vulnerability classes lives in vyql/ as plain text you can read and edit.

pattern  →  concept  →  binding  →  rule

A pattern is a shape in the code. A binding attaches a concept (code.SqlExecution, core.HtmlEscape) to it. A rule says which combinations add up to a vulnerability. To cover a new framework you usually write a binding, not Go.

Install

curl -fsSL https://dl.vyprsec.ai/vyql/install.sh | sh

Picks the right build for your platform, verifies its published SHA-256, unpacks it under ~/.local/share/vyql, and puts vyql in ~/.local/bin. Set VYQL_VERSION, VYQL_INSTALL_DIR or VYQL_BIN_DIR to change any of that.

Release assets come from the GitHub release, which is where the checksums and build provenance live. VYQL_INSTALL_BASE_URL points the download at a mirror serving the same <base>/<version>/<asset> layout.

The checksum is served from the same host as the archive, so it catches a corrupted download rather than a compromised release. Pin VYQL_VERSION and check the sum against a second source if you need more than that.

Linux and macOS, amd64 and arm64. On Windows it says so rather than failing obscurely. Use WSL, or the GitHub Action.

Download a release manually

The scanner loads its security knowledge from a vyql/ directory at startup. The release archive carries both halves, so it works on a machine that has never seen VyQL:

# platforms: linux_amd64, linux_arm64, darwin_arm64, darwin_amd64
V=v0.2.5; P=darwin_arm64
curl -fsSLO https://github.com/vyprai/vyql/releases/download/$V/vyql_${V}_${P}.tar.gz
curl -fsSLO https://github.com/vyprai/vyql/releases/download/$V/vyql_${V}_${P}.tar.gz.sha256
shasum -a 256 -c vyql_${V}_${P}.tar.gz.sha256

tar -xzf vyql_${V}_${P}.tar.gz
cd vyql_${V}_${P}
./bin/vyql scan .
go install

Requires Go 1.26+ and a C toolchain (the parsers are C).

go install github.com/vyprai/vyql/cmd/vyql@latest

The binary finds its data in the module cache, so no further setup is needed.

Container
docker run --rm -v "$PWD:/work" ghcr.io/vyprai/vyql scan .

About 270 MB, most of it the security knowledge base. Runs as a non-root user, so it will not leave root-owned files in a mounted tree. debian:stable-slim rather than Alpine: the parsers are C linked against glibc.

From source
git clone https://github.com/vyprai/vyql
cd vyql
make build          # -> bin/vyql
As an agent skill

For Claude Code, VyQL ships as a plugin that teaches the agent to run a scan and triage what comes back:

/plugin marketplace add vyprai/claude-plugins
/plugin install vyql@vypr

Then ask for a security scan in the ordinary way. The skill installs the vyql binary if it is missing, but asks first. A security tool that downloads and runs binaries unprompted has the wrong instincts.

It lives in vyprai/claude-plugins rather than here, so installing it copies 32 KB instead of cloning a repository whose knowledge base is several hundred megabytes.

skills/vyql-security-scan/ there follows the Agent Skills format, which is not Claude-specific: any tool that reads a SKILL.md can use it directly, without the plugin manifests.

Check what you have
vyql version
vyql v0.2.4
commit: 6906231d71f36f0f7f93f6301870efff4ae333ad
built:  2026-08-07T01:43:38Z
go:     go1.26.4
platform: darwin/arm64

Worth quoting in a bug report: findings depend on the version of the security knowledge as much as on the engine.

How you use it

The same shape whether you drive the CLI yourself or ask an agent.

scope  →  scan  →  coverage  →  list  →  verify  →  reproduce  →  fix

Scope. Whole repo, or just what a change introduced. These are different questions: on a codebase with a backlog, scanning everything at review time buries the two findings your branch added under two hundred it did not. For a diff, scan both sides and use vyql diff.

Scan. vyql scan . with no configuration. There is nothing to pick.

Coverage. Read the scanned line before the findings. A clean report over a tree that was mostly skipped looks exactly like a clean report over a tree that was fully read, and only that line tells them apart.

List. Everything it found, by severity, each with a location.

Verify. Pick one, or all the high ones. Findings are grouped by rule family and verified an agent at a time, four families at most, because a systematic false positive arrives as a whole family. This is static verification: it establishes the path holds up, not that the bug is exploitable.

Reproduce. Optionally, boot the app from a clean worktree and exploit that. If it will not boot cold, a failing test instead. Local only, never the instance you are already running.

Fix. Optionally, and only if you ask. The default is to name the control and leave the edit to you.

Stop wherever you have your answer. A list is a complete answer to "what is wrong here", and most scans end there.

Driving it with an agent

The same flow, without remembering the commands:

/plugin marketplace add vyprai/claude-plugins
/plugin install vyql@vypr

Then it is a conversation, and each turn builds on the last:

you  audit this repo for security problems

     Scanning the whole tree. vendor/, node_modules/ and testdata/ are
     skipped already. On top of those I would skip:

       **/*_templ.go    generated templates, one huge function each
       examples/        sample code, not shipped

     Anything else to leave out, or shall I go?

you  go

     scanned python:14 javascript:3, 2 files unread (.kt)
     7 findings. 2 critical, 3 high, 2 medium.
     1. VYQL-INJ-002  critical  api/users.py:88  request body reaches a shell
     2. VYQL-PATH-001 high      api/files.py:23  query param reaches open()
     ...

you  verify the high ones

     4 families: INJ (2), PATH (3), CRY (1), CFG (1). Verifying all four,
     one agent each. Nothing deferred.

     INJ   1 real, 1 false positive
     PATH  3 false positives, all through the same startswith() guard
     CRY   1 real
     CFG   1 unresolved, needs to know whether debug mode ships

     The three PATH findings share a cause. That guard is a containment
     check VyQL does not model, so it is a binding gap, not three bugs.

you  what about the INJ one

     Real. api/users.py:88 is a request handler and the path carries the
     value with no escaping. Fix is core.ShellEscape: subprocess with a
     list rather than shell=True.

you  prove it

     Booting from a clean worktree at the scanned commit.
     POST /users/import with a payload of "; id" returns uid=0(root).
     Torn down.

you  fix it then

     One file, api/users.py, introducing subprocess with an argument list.
     Shall I?

You never restate which finding you mean, because the list is still in the conversation. A single question works too, but the flow is what it is built for.

A reproduction runs against your own machine and nothing else, and the skill asks before executing one. Writing it is the deliverable; running it is your call.

The skill runs the right commands, reports coverage before findings, and holds the line on what a verdict means. It asks before installing anything, before scanning, and before writing a reproduction.

It asks about scope because scope decides what the scan cannot find: a directory skipped there is one the report calls clean without having read it. And it never shortens a list of findings without saying so — past about 25 it shows the critical and high ones, states how many of each severity are held back, and offers the rest.

skills/vyql-security-scan/ in that repository is a plain SKILL.md, which is an open format. Any agent that reads it can follow the same flow; Claude Code is just the one with an installer.

Scan something

vyql scan ./my-project
vyql scan                  # no path: scans the working directory, and says so
analysis profile: HTTP API server (api)

1 finding(s):

[P3] [HIGH] VYQL-PATH-001  (conf=medium, fp=fd2b76a631e27d16)
    source: code.HttpInput @ app.js:5   <- code.HttpInput by javascript.input@resolved
    sink: code.FilePathAccess @ app.js:6
    taint path: app.jsAttr#85 -> app.jsArg#86
    unless path coveredBy core.PathCanonicalization: not satisfied
    unless endpoint coveredBy core.PathAccessCheck: not satisfied

Every finding shows the source, the sink, the path between them, and the controls that would have stopped it. If you already have one of those controls in place, explain will tell you why it didn't apply here.

vyql scan -format sarif ./my-project > results.sarif
vyql scan -format json  ./my-project | jq '.[].rule'
Failing a build

scan exits 3 when it finds anything HIGH or CRITICAL. Dropping it into a pipeline gates that pipeline, with no extra configuration.

vyql scan .                        # exit 3 on HIGH or CRITICAL  (the default)
vyql scan -fail-on critical .      # exit 3 only on CRITICAL
vyql scan -fail-on none .          # report everything, always exit 0

Severities, lowest to highest: info low medium high critical. -fail-on takes any of them, or none.

Every command reports the same four codes, so a pipeline can act on the status alone without parsing output:

code meaning
0 the command run successfully
1 VyQL could not complete — bad path, unreadable file, rules that do not compile
2 usage error — unknown command or flag, missing path, a value outside its set
3 the check ran and did not pass — findings at or above -fail-on, a corpus that does not validate, a finding set that changed
Adopting it on a codebase that already has findings

A baseline records what you have already triaged, keyed on the finding fingerprint. The fingerprint is anchored to rule and location rather than line number, so your verdict survives edits elsewhere in the file.

vyql scan -baseline-write .vyql-baseline.json .   # take the backlog as given
vyql scan -baseline .vyql-baseline.json .         # fail only on what is new

Applying a baseline lowers the gate to any new finding. Without one, scan asks "is anything in this code wrong", and -fail-on high is how a team declines to fix the long tail. With one it asks "did this change add anything", and every addition counts — a new medium is a regression this branch introduced, not part of a backlog somebody accepted. Keeping the plain default there would pass the build on exactly those findings while the report above it listed them.

The run says so on stderr rather than leaving you to infer it, and names the way back:

vyql: warning: baseline applied; gating on any new finding
         pass -fail-on high to keep the usual threshold, or -fail-on none to report only

Naming a threshold still wins, so a pipeline that only wants new criticals says so:

vyql scan -baseline .vyql-baseline.json -fail-on critical .

Entries are written as accepted with an empty reason; fill them in as you triage, and change the verdict to false-positive where the finding is wrong:

{ "fp": "cfb54bfb4024aa90", "verdict": "false-positive",
  "reason": "source is a build-time constant, not request data",
  "rule": "VYQL-INJ-002", "loc": "server.js:5" }

A recording run still reports. -baseline-write writes the file and then prints the findings it recorded, in whatever -format you asked for, so the run that adopts the scanner can still publish its SARIF and you can see what was accepted on your behalf:

vyql scan -baseline-write .vyql-baseline.json -format sarif . > results.sarif

That run does not fail the build. Everything it reported was just accepted, and gating on the backlog the flag exists to absorb would leave you unable to adopt the scanner in the pipeline that needs it. It does fail, before printing anything, if the baseline cannot be written.

Rolling a baseline forward. Give both flags, with different paths, and the run applies the old baseline and records the next one:

vyql scan -baseline .vyql-baseline.json -baseline-write next.json .

This run reports and gates on what the old baseline does not cover, because that is what is new. The file it writes carries three things: every finding the old baseline covered, keeping its verdict and reason, so triage survives the roll; every new finding below -fail-on, as accepted; and nothing else. A new finding that meets -fail-on is deliberately left out — recording what just failed the build would leave the next run green with the finding absorbed and nobody told. Entries in the old baseline that match no current finding are dropped, so a rolled baseline sheds suppressions whose code is gone.

Pointing both flags at the same file is refused. Recording writes every finding as accepted with an empty reason, so it would overwrite the verdicts you triaged. Record to a different path and diff it.

Baselined findings are kept out of both the report and the gate. Anything not in the file is new and reported normally.

Entries that stop matching are reported, not forgotten:

warning: 4 baseline entries match nothing in this scan
         the code they excused may have changed; re-triage or remove them:
           27d5f6e6503511f5  VYQL-INJ-004  app.py:12

Otherwise a suppression outlives the code it excused: the code moves, the excuse stays, and nobody looks again.

A malformed baseline, an unknown verdict or a missing file is an error, not an empty baseline. Failing loudly beats silently suppressing everything or nothing.

Skipping files

-exclude takes one pattern and may be repeated. One rule decides what a pattern means:

vyql scan -exclude node_modules .              # that directory, at any depth
vyql scan -exclude '*_templ.go' .              # that file, at any depth
vyql scan -exclude 'src/gen/**' .              # rooted, because it has a slash
vyql scan -exclude '**/*.{test,spec}.ts' .     # brace alternation
vyql scan -exclude node_modules -exclude vendor .   # repeat for more

A value with no slash and no glob character names a directory. A value with no slash but a glob character names a file at any depth — * does not cross a slash, so a bare suffix pattern would otherwise match only at the scan root. A value containing a slash is anchored at the scan root and matches as written.

Excluded directories are never descended, so their files are not read or even listed. A malformed pattern is rejected before the scan starts, and -coverage reports how much each pattern excluded.

One pattern per flag: a comma is rejected, because it would be ambiguous with a valid glob pattern.

What was actually read

"No findings" only means something if you know what was read. Anything left unanalysed gets reported whether you ask or not:

scanned python:1 textpattern:1 — 9 finding(s)
warning: 15 file(s) matched no frontend and were not analysed (.zig 12, .cob 3)
         run with -coverage for the breakdown

-coverage gives the full account:

vyql scan -coverage .
coverage
  parsed    python 1 · textpattern 1
  excluded  5 file(s) and 1 director(ies) dropped by -exclude
              node_modules   1 dir(s)
              **/*_templ.go  5 file(s)
              *.min.js       0 file(s)  ← matched nothing
  oversized 2 file(s) skipped over the -max-file-size ceiling;
            raise it or pass 0 to scan them
  unread    15 file(s) matched no frontend: .zig 12, .cob 3
  depth     java, python, javascript are the reference frontends;
            other languages range down to call-and-concat coverage
  note      a parse that partially fails still counts as parsed;
            this does not yet report that

Source files above 2 MiB are skipped during tree walks by default — a file that size is almost never hand-written, and parsing one costs far more than the rest of the tree combined. -max-file-size 8MB raises the ceiling, 0 disables it, and naming a file directly always scans it regardless of size.

A clean report over a tree that was mostly skipped reads exactly like a clean report over a tree that was fully read. The warning is the difference, which is why it is not optional. The note is there because the gap is real: tree-sitter recovers from syntax errors, so a file that parsed badly still counts as parsed.

Understand a finding

The diagnostic commands are the point of the design: you interrogate the analysis instead of guessing at it. Every one takes paths just as scan does, so . scans the current directory.

explain prints each finding's full proof tree, including the negation evidence: every unless clause the rule carries and whether it was satisfied. Usually the fastest answer to "why did this fire".

vyql explain .
vyql explain -rules vyql/packs/injection .   # narrow to one pack or file

match lists every node a binding attached a concept to. If your source or sink is not listed, no rule can fire, because rules match concepts.

vyql match .

resolve shows which calls resolved to a body and which did not. Taint stops at an unresolved call, so this is where a missing cross-function flow shows up.

vyql resolve .

trace follows taint from source to sink, or shows where it stops. Both filters match on a substring of the concept name.

vyql trace .
vyql trace -from HttpInput -to SqlExecution .
vyql trace -to FilePathAccess .              # every path into file access

query searches the graph by predicate, for when you want to look rather than be told.

vyql query -type code.Call .                 # by node type
vyql query -concept HttpInput .              # by concept label
vyql query -call db.Query .                  # by callee path or method
vyql query -loc handlers.go .                # by location substring
vyql query -concept SqlExecution -edges .    # include outgoing edges
vyql query -concept HttpInput -count .       # just how many

graph dumps the whole graph, or taint reachability per source. Verbose, and definitive when the others all look right.

vyql graph .

Two more worth knowing:

vyql definitions -kind all                   # what concepts, rules and bindings loaded
vyql definitions explain code.SqlExecution   # which binding produced a label
vyql definitions -kind bindings -lang python  # one language's source/sink/check vocabulary

A filter that matches no known concept is an error rather than an empty result, because "0 sources reach a sink" and "you typed the name wrong" should not look the same:

$ vyql trace -from HttpInpt .
vyql: -from "HttpInpt" matches no concept
  did you mean: code.HttpInput, code.HttpHeader, code.HttpPersistentAuthReuse?
  list them with: vyql definitions -kind concepts
Comparing two scans

diff compares two -format json runs by finding fingerprint. Fingerprints are anchored to rule and location rather than line number, so the comparison survives edits elsewhere in the file:

vyql scan -format json . > before.json
# ... change something ...
vyql scan -format json . > after.json
vyql diff before.json after.json
before: 9 findings   after: 7 findings
- removed: 2   + added: 0   (= 7 unchanged)
  - [VYQL-INJ-004] app.py:18 → app.py:19
  - [VYQL-INJ-202]  → app.py:19

This is how to ask "did this branch introduce anything new" without failing on a backlog that was already there.

A missed finding is nearly always one of three things, and match, resolve and explain distinguish them in that order: nothing was labelled, the call did not resolve, or an unless clause was satisfied.

Every command and flag

vyql help lists the commands; vyql help <command> prints that command's flags. This section is the same information with the reason for each one.

Four flags are on every command. -data points at the vyql/ data directory when it is not where the binary would look, -profile picks the analysis profile, and -cpuprofile / -memprofile write pprof files:

vyql scan -data /opt/vyql .                  # data directory somewhere else
vyql definitions -data /opt/vyql -kind concepts
vyql scan -profile api .                     # skip auto-detection
vyql scan -cpuprofile cpu.prof -memprofile heap.prof .

$VYQL_HOME, $VYQL_CPUPROFILE and $VYQL_MEMPROFILE do the same and are the fallback when the flag is not given.

scan
flag default what it does
-rules vyql/packs load rules from a .vyql file or directory
-bindings a repo-local binding overlay directory
-format text text, sarif, json or graph-json
-fail-on high, or any new finding with -baseline exit 3 at or above this severity, or none
-exclude skip paths matching this pattern; repeatable
-baseline apply triaged findings, and report only what is new
-baseline-write record the current findings to this path
-coverage off what was parsed, excluded and left unread
-stats off graph counts, taint hubs, per-phase timing
-flags off review flags: off, with (findings and flags), only
-flag-category all filter review flags by category
-flag-kind all all, attention, target or check
-flag-loc filter review flags by location substring
-cache auto auto, off, or a directory
-cache-incremental off also cache per-file parses, for an edit loop
-max-ram 80% of RAM soft ceiling, e.g. 8GB or 16GiB
-max-file-size 2MiB skip larger source files; 0 disables

Combinations worth knowing:

# CI: machine output, gated, with the coverage account on stderr
vyql scan -format sarif -fail-on high -coverage . > results.sarif

# CI on a codebase with a backlog: fail only on what this branch added
vyql scan -baseline .vyql-baseline.json -format sarif . > results.sarif

# roll the baseline forward while still gating on what it does not cover
vyql scan -baseline .vyql-baseline.json -baseline-write next.json .

# adopt: record everything, gate on nothing
vyql scan -fail-on none -baseline-write .vyql-baseline.json .

# review flags instead of findings, narrowed to one category and location
vyql scan -flags only -flag-kind attention -flag-loc handlers/ .

# findings and flags together, as one JSON document
vyql scan -flags with -format json .

# a big or slow tree: bound the memory, skip generated files, keep the cache warm
vyql scan -max-ram 8GB -exclude '**/*_templ.go' -exclude node_modules .
vyql scan -cache-incremental .               # second run after an edit is faster

# one pack, one profile, no cache — what you want when a rule misbehaves
vyql scan -rules vyql/packs/injection -profile api -cache off -stats .

-flags selects the mode and the three -flag-* flags filter it. Setting a filter while -flags is off is a usage error, because the filter could not reach the output.

trace
flag what it does
-from only sources whose concept contains this substring
-to only sinks whose concept contains this substring
-brief one line per connected pair, with a hop count
-count the number of connected pairs, and nothing else
vyql trace .                                     # every source
vyql trace -from HttpInput -to SqlExecution .    # one question
vyql trace -to FilePathAccess .                  # every path into file access
vyql trace -from HttpInput -to SqlExecution -brief .
vyql trace -from HttpInput -count .

-from and -to are checked against the ontology before the scan runs, so a typo is an error rather than a report of zero. Every mode reports how many sources dead-ended: a list of only the sources that reached a sink reads exactly like a clean result.

query
flag what it does
-type match node type substring
-concept match concept label substring
-call match callee path or method
-loc match location substring
-edges also print each match's outgoing edges
-count print only how many matched
vyql query -type code.Call .
vyql query -concept HttpInput .
vyql query -call db.Query .
vyql query -loc handlers.go .
vyql query -concept SqlExecution -edges .        # with outgoing edges
vyql query -concept HttpInput -count .           # just the number
vyql query -type code.Call -loc handlers/ -call exec .   # filters combine

Filters combine with AND. Reachability is trace, not query.

explain, match, resolve, graph
vyql explain .
vyql explain -rules vyql/packs/injection .   # narrow to one pack or file
vyql match .
vyql resolve .
vyql graph .                                 # the whole USG, nodes and edges
definitions
flag default what it does
-kind all all, concepts, rules, bindings, reviews, packs
-lang binding language filter
-query case-insensitive substring across names, patterns, packages, CWE
-limit 80 maximum rows per section
-format text text or json
vyql definitions                                  # everything that loaded
vyql definitions -kind concepts                   # the concept vocabulary
vyql definitions -kind rules -query injection     # rules mentioning injection
vyql definitions -kind bindings -lang python      # one language's vocabulary
vyql definitions -kind bindings -lang go -format json
vyql definitions -kind concepts -limit 500        # raise the per-section cap

Subcommands:

vyql definitions search sql injection             # search every kind at once
vyql definitions explain code.SqlExecution        # which binding produced a label
vyql definitions refs core.SqlParameterization    # what references a definition
vyql definitions show-policy resultIdentity.default
vyql definitions show-mechanic ruleVerb.taint
vyql definitions validate                         # validate the loaded corpus
vyql definitions validate vyql/packs/injection    # or one path
vyql definitions validate -unstable               # report quarantined uses
vyql definitions validate-binding vyql/bindings/python/python/558.vyql

validate exits 3 when the corpus does not validate.

refs and explain take the path as -in <path> rather than positionally, because their positional argument is the definition being asked about:

vyql definitions refs -in vyql/ontology/concepts core.SqlParameterization
vyql definitions explain -in vyql/bindings/python code.SqlExecution
diff, cache, version, help
vyql diff before.json after.json     # two `scan -format json` outputs
vyql cache path                      # the directory this build would use
vyql cache clear                     # remove it, reporting the space freed
vyql cache clear -cache /tmp/mine    # a cache somewhere else
vyql version
vyql help
vyql help scan

diff exits 3 when the finding set changed, so a pipeline can gate on "this branch changed the findings" without parsing the output.

cache clear removes the directory rather than emptying it, and says how much it freed. The next scan recreates it.

scan -max-ram puts the graph in a store under the system temporary directory, removed when the scan ends — including when you interrupt it. A kill -9 cannot be caught, so that one case leaves the directory behind; remove it with rm -rf $TMPDIR/vyql-graph-*.

Languages

Java, Python, JavaScript/TypeScript, C#, PHP, Ruby, Go, Rust, Kotlin, Scala, Swift, Dart, Groovy, Elixir, Lua, Perl, C, C++, Objective-C, Solidity, Bash, PowerShell.

Depth is not uniform. Java, Python and JavaScript are the reference frontends and carry the most complete modelling; the rest range from full taint tracking to call-and-concat coverage. vyql definitions -kind all reports what is actually loaded.

Where the knowledge lives

vyql/
  ontology/   concepts and threat kinds -- the vocabulary
  bindings/   what in each language means which concept
  packs/      rules: which concept combinations are vulnerabilities
  taxonomy/   CWE and CAPEC
  tests/      executable specs, one per rule

None of this is compiled in. The binary loads it at startup from the directory VYQL_HOME names, or by finding vyql/ above the working directory. Point VYQL_HOME at your own copy and your edits take effect on the next run.

Adding coverage

A binding says "this shape in this language is this concept":

binding requestJson {
  requires { language("python") }
  query pattern callExpr where callee.path ~= "request.json"
  emit source code.HttpInput at call.result
}

Write it, add a spec in vyql/tests/, run go test -count=1 ./.... See docs/07-adapters-and-patterns.md for the binding language.

Check a binding file parses, and what it emits, without running a scan. Bindings live under vyql/bindings/<language>/, one module per file:

vyql definitions validate-binding vyql/bindings/python/python/558.vyql
{
  "ok": true,
  "bindings": [
    { "name": "python", "mapping_count": 1, "mappings": [ ... ] }
  ]
}

Then confirm it attaches what you expect on real code. match lists what was labelled, and definitions explain names the binding responsible:

vyql match ./some-project
vyql definitions explain code.HttpInput

Documentation

docs/README.md is the index. The design series (docs/03 through docs/20) is the reference for how the engine works; read it before changing the engine or the language.

Stability

The CLI is stable. Commands, flags and output formats follow semantic versioning: scan, its -format values, the JSON and SARIF shapes, and the finding fingerprint will not change incompatibly within a major version. Build tooling against them.

The rule and binding language is not. It is still moving, and a future version changes parts of the syntax. Concepts get renamed and clauses get added, so a rule pack written today may need edits to keep working. The specs in vyql/tests/ are what tell you when something breaks.

The knowledge base evolves. A newer release can report findings an older one did not -- that is the point of it -- but it means pinning a version is the only way to get identical output twice.

Some documents describe design rather than behaviour. The reference series in docs/ includes work that is not implemented; those documents say so at the top, and the index lists them separately. What a scan actually does is whatever vyql definitions -kind all reports as loaded.

Contributing

See CONTRIBUTING.md. Two things worth knowing before your first change:

  • go test must always be go test -count=1. The Go test cache keys on source and does not track the .vyql data files, so a cached pass can hide a change to the security knowledge base entirely.
  • CGO_ENABLED=1 is required. The parsers are C; without cgo the build constraints exclude them silently rather than failing.

make ci runs exactly what the pipeline runs.

Accuracy

Measured on the public OWASP Benchmark suites, scored by Youden index (J = TPR − FPR, macro-averaged over categories):

Suite Cases Score
BenchmarkJava 2,740 +1.00
BenchmarkPython 1,230 +0.90

Run them yourself: benchmarks/fetch-corpora.sh pulls the corpora, which are GPL and not vendored here. benchmarks/RESULTS.md has the method, per-category numbers and known corpus defects. Scores from our own language ports live there too, and are not comparable to these.

License

Apache-2.0. See LICENSE, NOTICE and THIRD_PARTY_NOTICES.md. The last one covers the vendored tree-sitter grammars, the MITRE CWE/CAPEC taxonomies, and the ecosyste.ms package snapshot, which is CC-BY-SA 4.0.

Directories

Path Synopsis
cmd
gen-ontology-json command
Command gen-ontology-json dumps the ontology content (concepts + threat kinds) to JSON, the source-of-truth format the ontology package loads.
Command gen-ontology-json dumps the ontology content (concepts + threat kinds) to JSON, the source-of-truth format the ontology package loads.
vyql command
Command vyql is a multi-language security scanner.
Command vyql is a multi-language security scanner.
internal
attackpath
Package attackpath composes typed step relations into cross-domain attack Paths (docs/13).
Package attackpath composes typed step relations into cross-domain attack Paths (docs/13).
bindings
Package bindings applies compiled v2 binding labelers to graph nodes.
Package bindings applies compiled v2 binding labelers to graph nodes.
datadir
Package datadir resolves the VyQL data root — the standalone `vyql/` directory (rule packs + ontology + taxonomy) that lives OUTSIDE the Go source tree and is loaded from disk at runtime (no go:embed).
Package datadir resolves the VyQL data root — the standalone `vyql/` directory (rule packs + ontology + taxonomy) that lives OUTSIDE the Go source tree and is loaded from disk at runtime (no go:embed).
definitions
Package definitions inspects the shipped VyQL definition corpus -- concepts, bindings, rule packs and reviews -- and reports what it contains.
Package definitions inspects the shipped VyQL definition corpus -- concepts, bindings, rule packs and reviews -- and reports what it contains.
engine
Package engine compiles and evaluates VyQL rules (docs/03, /05).
Package engine compiles and evaluates VyQL rules (docs/03, /05).
extract
Package extract is the scan pipeline: source paths in, an analysis graph out.
Package extract is the scan pipeline: source paths in, an analysis graph out.
extract/frontend
Package frontend is the extraction layer: the registry of which technologies exist and what files each claims, and the loading of frontend-facing binding data.
Package frontend is the extraction layer: the registry of which technologies exist and what files each claims, and the loading of frontend-facing binding data.
extract/frontend/config
Package config is a non-tree-sitter frontend for declarative project files.
Package config is a non-tree-sitter frontend for declarative project files.
extract/frontend/golang
Package golang is a NATIVE Go-source extraction frontend: it parses real .go files with the standard go/parser + go/ast and lowers them to the shared NIR (docs/20).
Package golang is a NATIVE Go-source extraction frontend: it parses real .go files with the standard go/parser + go/ast and lowers them to the shared NIR (docs/20).
extract/frontend/textpattern
Package textpattern runs a data-defined text-pattern profile over files that do not need a language parser.
Package textpattern runs a data-defined text-pattern profile over files that do not need a language parser.
extract/frontend/treesitter
Package treesitter holds REAL parser frontends built on tree-sitter (docs/20: the recommended production parser — a uniform node API across 100+ languages, error recovery, build-free).
Package treesitter holds REAL parser frontends built on tree-sitter (docs/20: the recommended production parser — a uniform node API across 100+ languages, error recovery, build-free).
extract/frontend/treesitter/grammars/dart
Package dart provides the tree-sitter Dart language.
Package dart provides the tree-sitter Dart language.
extract/frontend/treesitter/grammars/elixir
Package elixir provides the tree-sitter Elixir language.
Package elixir provides the tree-sitter Elixir language.
extract/frontend/treesitter/grammars/groovy
Package groovy provides the tree-sitter Groovy language.
Package groovy provides the tree-sitter Groovy language.
extract/frontend/treesitter/grammars/objc
Package objc provides the tree-sitter Objective-C language (vendored from tree-sitter-grammars/tree-sitter-objc, committed parser.c, no Go binding).
Package objc provides the tree-sitter Objective-C language (vendored from tree-sitter-grammars/tree-sitter-objc, committed parser.c, no Go binding).
extract/frontend/treesitter/grammars/perl
Package perl provides the tree-sitter Perl language.
Package perl provides the tree-sitter Perl language.
extract/frontend/treesitter/grammars/powershell
Package powershell provides the tree-sitter PowerShell language.
Package powershell provides the tree-sitter PowerShell language.
extract/frontend/treesitter/grammars/solidity
Package solidity provides the tree-sitter Solidity language.
Package solidity provides the tree-sitter Solidity language.
extract/frontend/treesitter/grammars/swift
Package swift provides the tree-sitter Swift language.
Package swift provides the tree-sitter Swift language.
extract/lowering
Package lowering is the shared, language-AGNOSTIC tier (docs/20): it lowers NIR into the shared graph, owning the function/class registries, per-file import tables, the type map (self, constructors, class/static receivers), call resolution (import -> type -> guarded unique-name fallback), and dataflow construction (scopes, assignments, FLOWS edges).
Package lowering is the shared, language-AGNOSTIC tier (docs/20): it lowers NIR into the shared graph, owning the function/class registries, per-file import tables, the type map (self, constructors, class/static receivers), call resolution (import -> type -> guarded unique-name fallback), and dataflow construction (scopes, assignments, FLOWS edges).
extract/nir
Package nir is the Normalized IR (docs/20) — the coherent representation Every language frontend targets.
Package nir is the Normalized IR (docs/20) — the coherent representation Every language frontend targets.
extract/parsecache
Package parsecache is a content-addressed, BadgerDB-backed cache of per-file NIR parse results.
Package parsecache is a content-addressed, BadgerDB-backed cache of per-file NIR parse results.
extract/regexambig
Package regexambig decides whether a regular expression can backtrack catastrophically.
Package regexambig decides whether a regular expression can backtrack catastrophically.
extract/sca
Package sca implements the dependency/SBOM path and the vulnerable-library Entrypoint projection (docs/20, docs/11).
Package sca implements the dependency/SBOM path and the vulnerable-library Entrypoint projection (docs/20, docs/11).
findings
Package findings is the Finding + proof tree (docs/14).
Package findings is the Finding + proof tree (docs/14).
graphjson
Package graphjson renders a scan as the graph-json export document: the functions and call edges the scan resolved, each finding's source, sink and witness path, and the concept legend that explains the labels.
Package graphjson renders a scan as the graph-json export document: the functions and call edges the scan resolved, each finding's source, sink and witness path, and the concept legend that explains the labels.
graphsync
Package graphsync turns a VyQL analysis graph into a stable, content-addressed change-feed for syncing to an external database.
Package graphsync turns a VyQL analysis graph into a stable, content-addressed change-feed for syncing to an external database.
nexus
Package nexus emits full-fidelity finding JSON for the Nexus API (docs/14) — finding + proof tree + node refs, so UIs can pivot from a finding to its graph neighborhood.
Package nexus emits full-fidelity finding JSON for the Nexus API (docs/14) — finding + proof tree + node refs, so UIs can pivot from a finding to its graph neighborhood.
ontology
Package ontology is the VyQL security ontology + type system (docs/06).
Package ontology is the VyQL security ontology + type system (docs/06).
pack
Package pack is the rule-pack manifest + version pinning (docs/05 §module System, docs/15).
Package pack is the rule-pack manifest + version pinning (docs/05 §module System, docs/15).
parser
Package parser implements the VyQL v2 definition parser and compiles authored definitions into the scanner IR consumed by the engine and graph labeler.
Package parser implements the VyQL v2 definition parser and compiles authored definitions into the scanner IR consumed by the engine and graph labeler.
profile
Package profile implements application-archetype analysis profiles.
Package profile implements application-archetype analysis profiles.
report
Package report renders findings for a terminal.
Package report renders findings for a terminal.
resultpolicy
Package resultpolicy loads v2 result policies and applies them to scanner outputs.
Package resultpolicy loads v2 result policies and applies them to scanner outputs.
review
Package review derives the review flags a scan reports alongside its findings: the conditions worth a human's attention, deduplicated, ordered, and paired with the checks that relate to them.
Package review derives the review flags a scan reports alongside its findings: the conditions worth a human's attention, deduplicated, ordered, and paired with the checks that relate to them.
risk
Package risk is the derived prioritization layer over findings + graph context.
Package risk is the derived prioritization layer over findings + graph context.
sarif
Package sarif emits VyQL findings as SARIF 2.1.0 (docs/14, /16) — the Table-stakes output for code findings and CI.
Package sarif emits VyQL findings as SARIF 2.1.0 (docs/14, /16) — the Table-stakes output for code findings and CI.
solvers
Package solvers implements the flow solvers (docs/08).
Package solvers implements the flow solvers (docs/08).
taxonomy
Package taxonomy embeds the full MITRE CWE and CAPEC catalogs as reference data (docs/16).
Package taxonomy embeds the full MITRE CWE and CAPEC catalogs as reference data (docs/16).
timing
Package timing is the per-phase wall-clock log used for local performance work.
Package timing is the per-phase wall-clock log used for local performance work.
usg
Package usg implements the Universal Security Graph (docs/04) for the Go production build of VyQL.
Package usg implements the Universal Security Graph (docs/04) for the Go production build of VyQL.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL