Skip to content
Zolt/Product 01 · PR review

The review comment that shows the attack path.

Zolt PR Review traces each change in a pull request from source to sink, decides whether it is exploitable, and posts the finding, the path, a confidence score and the patch as one comment. Nothing else.

14-day free trial · no credit card · free for open source
zolt-securitybotleft a reviewPR #12
Zolt security review · 1 issue introduced by this change: 1 high (1 with a one-click fix)
src/routes/invoices.js
13+router.get('/:id/export', auth, async (req, res) => {14+  const filename = (req.query.filename || `invoice-${invoice.id}`) + '.pdf'15+  const out = path.join(config.uploadDir, filename)
Security issue · high · 95% confidence
Path traversal in invoice export. req.query.filename flows into path.join without validation, so a name containing ../ writes the PDF outside the upload directory.
Suggested change · src/routes/invoices.js
15-  const out = path.join(config.uploadDir, filename)15+  const safeName = path.basename(filename)16+  const out = path.join(config.uploadDir, safeName)
Also available as a prompt for your coding agent
In your pull request

This is what lands on the PR

The summary, each finding on the lines that introduced it, and the fix as a suggestion you commit from the thread. Nothing to open, nothing to export.

feat(invoices): add PDF export · #12 · demo/invoice-pdf-export → main
zolt-securitybotsuggested changes · 3 days ago
Zolt security review · 2 issues introduced by this change: 2 high (2 with a one-click fix)
Each is commented inline on the changed lines below.
src/routes/invoices.js
17+const filename = (req.query.filename || `invoice-${invoice.id}`) + '.pdf'18+const out = path.join(config.uploadDir, filename)
High· 95% confidence
Path traversal in invoice export. req.query.filename reaches path.join unvalidated, so ../ writes PDFs anywhere on disk.
The summary, then the lines"2 issues introduced by this change", each commented inline on the exact lines with severity and confidence.
src/routes/invoices.js · line 14
zolt-securitybot3 days ago
High· 70% confidence
Missing invoice lookup in export endpoint. The route uses invoice.id but never fetches the invoice, so nothing checks who may export which invoice.
FixLook the invoice up by id, return 403 unless invoice.user_id === req.user.sub, then sanitise the filename.AgentIn src/routes/invoices.js around line 14, fix this…copy
ReplyResolve conversation
Severity, confidence, and a promptEvery finding carries a confidence score for the exploitability call, a suggested fix, and a ready-made prompt for a coding agent.
Suggested change · src/routes/invoices.js
45+const baseName = sanitizeFilename(req.query.filename || `invoice-${invoice.id}`)46+const filename = baseName + '.pdf'47+const out = path.join(config.uploadDir, filename)49+if (!out.startsWith(config.uploadDir)) {50+  return res.status(400).json({ error: 'invalid filename' })51+}
Apply suggestion ▾Add to batch
Apply suggestion✕
Update src/routes/invoices.js
Add an optional extended description…
Commit changes
One click to commit the fixThe fix is a suggestion on the PR. Apply it, name the commit, and it lands on the branch without leaving the thread.
01 / what it catches

The bugs that become incidents

Not lint. Not style. The classes of bug that show up in breach reports, checked against the change and everything it can reach.

Broken authorisation

Object references, tenant boundaries, role checks that a diff quietly removes.

IDOR · CWE-639 · CWE-284
Injection

User input that reaches a query, a shell, a template or a deserialiser.

SQLi · SSTI · command injection
Auth & sessions

Token comparison, JWT handling, password reset and session fixation.

timing leak · alg=none
Secrets in the diff

Keys and tokens, validated live against the provider before you are alerted.

AWS · Stripe · GitHub
Reachable dependencies

Only the CVEs whose vulnerable function your code actually calls.

SCA with reachability
Logic & races

Double-spend, coupon reuse, TOCTOU in the flows your tests never covered.

race · TOCTOU · replay
02 / it learns from you

Tell it once. It remembers.

A finding that matters in a payments service is noise in an internal health check. When you mark something accepted risk or not exploitable here, Zolt stores the decision with the repository and applies it to every review after.

zolt-bot · pull request #482api/internal/health.py:31
Medium: stack trace returned on /internal/health

Unhandled exception details are returned to the caller. Reachable only from the internal network according to the route table.

Where a Critical goes · acme/api
Slack · visibility
Linear · tracking
Jira · tracking
#security-alerts Zolt · Critical on acme/api #482 · report export not scoped to organisation · path + patch attached · merge blocked
SEC-218 · Urgent · assigned to priya-acme Scope report lookups by org (from Zolt finding #482-1). Closes when the finding closes.
03 / where findings go

Send findings to the tools that already own the work

Comments in the PR for the author; a message in Slack for the team; a ticket in Linear or Jira for the tracker. Choose per repository, and the ticket closes when the finding does.

04 / security review vs code review

A code reviewer will still let the authorisation bug through

General AI reviewers are good at what they do. Exploitability is a different job, and it needs a reviewer that thinks about the attacker rather than the author.

A security reviewer
■Follows data from the request to the sink across files, not just the diff
■Asks whether the change is exploitable, and shows the path
■Reports by impact: what an attacker gets, from where
■Attaches the path, a confidence score and the patch
■Quiet when there is nothing to prove
A general code reviewer
□Reads the diff and comments on what it sees
□Style, naming, structure, the occasional "consider validating"
□Security remarks mixed in with everything else
□Suggests, never traces
□Something on almost every PR
05 / merge gates

Decide once what blocks a merge. Zolt applies it to every PR.

Severity and confidence thresholds per repository, kept in a file you own. Change a setting on the left and watch the check on the right.

Gate settings · acme/api
Block on critical
Block on high
Block on medium
Block on leaked secrets
Minimum confidence to block
Findings under this confidence are still posted, but never block a merge.
.zolt.ymlin the repository
# .zolt.yml
gates:
  block_on: [critical, high]
  min_confidence: 80
  secrets: block
review:
  trigger: [pull_request, "@zolt review"]
The toggles write this file. Edit the file and the toggles follow. Either way it is reviewed like any other change.
PR #12 · checks
Zolt security review · failing
1 finding at or above your thresholds. The check fails until they are fixed or accepted.
Path traversal in invoice exportHigh · 95% confidenceblocking
Verbose error body on /internal/healthMedium · 70% confidenceposted only
Merge blocked

Put it on one repository

Free for open source. $30 per seat per month for teams.