Open source · Apache-2.0
No hidden gaps. No hand-waving.
VyQL follows untrusted input through your code to the point it turns dangerous, and names the controls that would have stopped it. If a path exists, it shows you every hop. If a control covers it, it stays quiet.
$ curl -fsSL https://dl.vyprsec.ai/vyql/install.sh | sh
macOS and Linux, amd64 and arm64. Verifies its checksum, installs to
~/.local/bin. No signup, no API key, nothing gets sent
anywhere.
Other Ways to Install
| Homebrew | brew install vyprai/tap/vyql |
| Container | docker run --rm -v "$PWD:/work" ghcr.io/vyprai/vyql scan . |
| Go | go install github.com/vyprai/vyql/cmd/vyql@latest |
| GitHub Actions | vyql-security-scan |
| Claude Code | /plugin marketplace add vyprai/claude-plugins |
Every one carries the security knowledge with it. No second setup step, and nothing to point at a server.
Quickstart
From the Terminal
Four commands cover almost everything:
vyql scan . # everything in this tree vyql explain . # why each finding fired vyql scan -fail-on none . # report without failing the shell vyql scan -baseline .vyql-baseline.json . # only what is new
Reviewing a change rather than a repo is a different question. Scan both sides and diff, which keys on a fingerprint so moving a function doesn't read as a new finding:
git worktree add -q /tmp/base $(git merge-base HEAD origin/main) vyql scan --format json /tmp/base > before.json vyql scan --format json . > after.json vyql diff before.json after.json
Through an Agent
VyQL ships as an agent skill, so you can ask instead of remembering flags. In Claude Code:
/plugin marketplace add vyprai/claude-plugins /plugin install vyql@vypr
Then it is a conversation. Each turn builds on the last, so you never re-state which finding you mean:
you audit this repo for security problems
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?
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.
It runs the commands, reports what was read before what it found, and asks before installing anything or writing a reproduction. The skill is a plain SKILL.md, an open format, so any agent that reads it can follow the same steps. Claude Code is just the one with an installer.
Either Way, the Same Shape
Scope what you are looking at, scan, read the coverage line, look at the list, verify the ones that matter, and reproduce if you need proof. Stop wherever you have your answer. Most scans end at the list.
What a Finding Looks Like
An Express app that takes a filename from the query string and passes it to the filesystem:
// app.js
const express = require("express");
const fs = require("fs");
const app = express();
app.get("/download", (req, res) => {
const name = req.query.name;
res.send(fs.readFileSync("/var/data/" + name));
});
No config file, no picking which rules to run:
$ vyql scan .
analysis profile: HTTP API server (api)
2 finding(s):
[P3] [HIGH] VYQL-PATH-001 (conf=high, fp=c61843fd71a2284b)
source: code.HttpInput @ app.js:6
sink: code.FilePathAccess @ app.js:7
taint path: app.jsAttr#80 -> app.jsFormat#81 -> app.jsArg#83
unless path coveredBy core.PathCanonicalization: not satisfied, no neutralizing
control dominates the path; none found anywhere on flows
unless endpoint coveredBy core.PathAccessCheck: not satisfied, no guard on sink
Look at the last two lines. Most scanners tell you something is wrong and leave the rest to you. VyQL names the control that would have fixed it. If you already have that control, it tells you why it didn't apply.
What You Get
Knowledge as Data
A small Go engine plus 12,400 .vyql files you can open and edit.
Adding a framework usually means writing a binding, not patching Go.
One Graph, 22 Languages
Java, Python and JavaScript are the most complete. Coverage thins out from there, and the scan tells you what you actually got.
Built for CI
scan exits 1 on anything high or critical. SARIF and JSON output.
Baseline files if you're adopting it on a codebase that already has findings.
It Admits What It Missed
Files it couldn't parse get reported whether you ask or not. Otherwise a clean run over code nothing read looks the same as a genuinely clean run.