Skip to main content

Find the vulnerabilities in your code, and the path to each one.

Vulkro reads your whole application on your own machine: Python, JavaScript, TypeScript, Go, Java, C, C++ and PHP, plus the containers, infrastructure files and package lists next to the code. It follows what a user can type to where it does damage, and reports a vulnerability only with the path that proves it. Nothing is uploaded.

Ten minutes to the first result. Free needs no account; the 14-day trial of the full product needs no card.

console - findings4 findings
SeverityFindingCWERule
CRITSQL injection in order lookupcheckout.py:214CWE-89VULK-1042
Dataflow pathVULK-1042
  1. 01checkout.py:214request handlersource

    order_id read from request.args, no type or format check

  2. 02services/orders.py:88helper

    passed through lookup_order(order_id) unchanged

  3. 03db/query_builder.py:41query builder

    concatenated into the WHERE clause with an f-string

  4. 04db/session.py:57sinksink

    cursor.execute(sql) runs the assembled statement

4 hops resolved. No sanitiser between the source and the sink.

HIGHMissing authorization on invoice downloadroutes/invoice.ts:47CWE-639VULK-2117
HIGHSSRF via user-supplied URLproxy-handler.ts:23CWE-918VULK-1180
MEDHardcoded API token committed to the repositoryconfig/stripe.js:9CWE-798VULK-3304
A vulnerability as the console shows it: how serious, where, and the data path that proves it.

How it works, and how deep it goes.

Three steps, then the table that states the depth for every language, including the cells that are blank. Every check also reports which files it could not read, so a partial check is never mistaken for a clean one.

Maps every way in

Every route the frameworks declare, the handler behind it, and whether reaching it needs a session. Routes built from variables are resolved, not skipped.

Follows the data

Inside a function, between functions in a file, and across files by following which functions call which. Any check on the way is noted; a missing one is the vulnerability.

Proves it, then ranks it

A problem is reported as proven only when the path is complete. Everything else is shown as unproven or not checked, so what you see first is what you fix first.

analysis depth by language9 languages x 5 capabilities
Analysis depth for each supported language, by capability. Each cell is full, partial or none.
LanguageRoute mappingentry pointsTaint, same filesource to sinkTaint, across filesvia call graphFramework awarenessrouters, ORMsDedicated detectorslanguage rules
PythonDjango, Flask, FastAPIFullFullFullFullFull
JavaScriptExpress, Koa, Next.jsFullFullFullFullFull
TypeScriptExpress, NestJS, Next.jsFullFullFullFullFull
Gonet/http, Gin, Echo, chiFullFullFullFullFull
Javasame-file data flow onlyFullFullNoneFullFull
PHPsingle-function taint; Laravel, Symfony sourcesNonePartialNonePartialFull
Csingle-function taintNonePartialNoneNoneFull
C++the C rules run here tooNonePartialNoneNoneFull
Terraform, Dockerfileconfiguration, not data flowNoneNoneNoneFullFull
  • Fullruns on every scan of that language
  • Partiallimited to the cases named in the row
  • Nonenot analysed at this depth today

Across files means data that arrives in one file is followed into the risky operation in another, along the functions that call each other, up to four calls deep. Java is same-file: a value that leaves a method is not followed into the function it calls. C, C++ and PHP are single-function: input reaching a dangerous call inside the same function is proven, and a value that leaves the function is not followed.

Analysis depth per language. The blank cell is stated up front.

What it finds.

180+ security checks, all on one pass, all on your machine. The families that matter most:

Injection

Database queries, shell commands and templates built from something a user typed, including the ones a data layer or an ORM assembles.

Broken access control

Records, fields and pages anyone can reach by changing a number in the address, missing permission checks, and forged requests.

Every endpoint you ship

An inventory of every route, the handler behind it, and whether reaching it needs a session, including the ones you forgot.

Leaked secrets

124 kinds of key and password, recognised by the shape each provider issues, in the code and in git history.

Vulnerable packages

Known bugs in the packages you use, matched on your machine against a local bundle and ranked by whether your code can reach them. Five manifest formats are parsed. The default published bundle currently ships npm and PyPI; the wider signed bundle covers Go modules, crates.io and Maven.

Infrastructure and containers

Terraform, Kubernetes and compose files checked against a built-in AWS, Azure and GCP catalogue; Dockerfiles; and built images read from disk.

C, C++ and PHP

Dangerous C library calls, format strings, unsafe temporary files, unchecked privilege drops and use-after-free shapes; PHP injection, file inclusion, unsafe unserialize and upload, weak cryptography and login bypass, with Laravel and Symfony request objects recognised as input.

AI agent and MCP risks

Agent tools that execute code, unsafe model and data loading, and misconfigured MCP servers, in the code and in the configuration.

Evidence for auditors

What you ship (SBOM), which known bugs affect it (VEX), and control-by-control evidence for SOC 2, ISO 27001, HIPAA, PCI DSS and NIST 800-53.

Air-gapped SAST: nothing leaves your machine.

Every check runs on your own computer. Turn on the air-gap switch and it refuses every outbound connection, including its own update check. The vulnerability data arrives as a checksummed bundle you carry in, and the licence is a file. The same code always gets the same answer.

trust boundaryone switch closes it

Everything the check needs stays on your machine: the code it reads, the map it builds of your application, the problems it finds, and the optional local AI model it can ask without leaving the machine.

Stays on this machine
source code, call graph + taint, findings, account layer, and the local model on 127.0.0.1
One crossing
Without the air-gap switch, one line leaves: a daily licence check carrying a handful of usage counts and never your code. With the switch on, nothing leaves at all.
Never crosses
source code, file paths, file names, finding contents, project names, the hardware fingerprint
What leaves the machine, and what never does. The air-gap switch closes the one line.

Before a release, on a codebase with history.

Three controls for the first-run number, none of them a quiet way to ignore the report. The result is a pass or fail your pipeline can enforce.

Only new problems block

Compare against the branch you merge into, so ten years of old debt never blocks a pull request. A committed snapshot does the same for a release.

Set aside, in the code, with an expiry

A one-line note above the code names the rule and a date. It is reviewed in the diff and stops working on the day you set.

Turn the volume down, not off

Three confidence levels: stop a release on the highest, work the middle as a backlog, sweep the lowest. Nothing is deleted, only ranked.

vulkro

HIGHBroken authorization on invoice download

routes/invoice.ts:47VULK-1042CWE-639

The handler looks the invoice up by the id in the path and returns the file. Nothing scopes that lookup to req.org, and requireAuth only proves the caller is signed in, not that the invoice belongs to them. Any authenticated user can download invoices from another organization by changing the number in the URL. The check belongs in the query, not in the response.

Suggested change

Line beforeLine afterChangeSource
@@ -45,7 +45,11 @@ router.get('/invoices/:id/download')
4545router.get('/invoices/:id/download', requireAuth, async (req, res) => {
46Removed line. const invoice = await db.invoice.findUnique({
47Removed line. where: {id: req.params.id},
46Added line. const invoice = await db.invoice.findFirst({
47Added line. where: {id: req.params.id, orgId: req.org.id},
4848 });
4949 
50Added line. if (!invoice) {
51Added line. return res.status(404).send('not found');
52Added line. }
53Added line.  
5054 return res.download(invoice.path);
5155});

vulkro scan · exit 1 · 1 high, 0 criticalRan on your runner. The code never left it.

What lands on a pull request: one new problem, in plain words, and a patch the author can read.

One check, then the evidence other people ask you for.

26 report formats from a single run, including the ones your pipeline, your auditor and your customers already read.

What you ship. A package inventory in the CycloneDX and SPDX formats, and a separate inventory of the cryptography.
Which known bugs affect it. An exploitability statement (OpenVEX and CycloneDX-VEX) for every package advisory, citing the path behind the verdict.
Control by control. Evidence packs for SOC 2, ISO 27001, HIPAA, PCI DSS 4.0 and NIST 800-53, one file per control, plus a GDPR Article 30 template.

vulkro cra-bundle . --framework soc2-fullexit 1, findings present
  • cra-readiness.zip/built on this machine
    • index.htmlreadiness one-pager
    • compliance/soc2-full, 61 controls
      • manifest.jsonframework, scan id, control summary
      • summary.mdevery control with its status
      • findings.csvfinding to control mapping, flat
      • soc2-full.htmlper-control evidence table
      • controls/one file per control
        • CC6.1.jsonPass
        • CC6.6.jsonPartial, 2 findings
        • CC7.2.jsonFail, 1 finding
        • CC8.1.jsonPass
        • P4.1.jsonPass
        • ...56 more control files
      • README.mdwhat each file is, and what it is not
    • sbom/
      • cyclonedx.jsonCycloneDX 1.6
      • spdx.jsonSPDX 2.3
    • vex/
      • openvex.jsonOpenVEX 0.2.0, per CVE

$ vulkro compliance-pack . --framework soc2-full --output ./compliance/

vulkro cra-bundle runs the same pack and writes it into one zip with the SBOM and VEX documents beside it. Every file is generated locally: nothing is uploaded, and each control file cites the findings and endpoints it was built from.

A compliance pack as it lands on disk: one file per control, each citing what it was built from.

Where it falls short.

Stated here rather than discovered on day two.

  • Java is read one file at a time. A value that leaves a method is not followed into the function it calls, so a vulnerability that crosses files in Java is missed.
  • C, C++ and PHP are read one function at a time. Input reaching a dangerous call inside the same function is proven; a value that leaves the function is not followed, and there is no route map or access model for these languages yet.
  • C and C++ are not checked for memory safety in general. It catches the dangerous calls and patterns behind most published C vulnerabilities, not out-of-bounds proofs or value ranges.
  • C#, Ruby, Kotlin and Rust get no code analysis. Their package lists, containers and secrets are still read.
  • Package reachability is a ranking, not a proof. It comes from how the code looks, and it applies to npm and PyPI only.
  • Container scans read images saved on disk. Nothing is pulled from a registry, and jars inside jars are not opened.
  • The published test set is fixed. It flatters every tool, ours included. The number to trust is a check of your own code, which is why the trial is the pitch.