Offline and air-gapped
If the code cannot leave the building, neither should the scanner.
You already know the constraint, so this page is about whether this tool actually meets it. Static application security testing, dependency matching and vulnerability matching all run on the machine that holds the code. One switch hard-refuses every network call at the process boundary. Entitlement is online-first with roughly 14 days of offline grace, and a machine that will never see the network runs from an air-gap licence file we issue on request. The vulnerability archive is checksummed and can be carried in on a disk.
- No source upload, ever
- One switch closes the boundary
- Air-gap licence file on request
- Vulnerability data delivered offline
01 / What offline means here
Four claims, each one specific enough to be wrong
Every scanner in this category calls itself offline. These are the four things we mean by it, written so that a reader can catch us out if any of them is untrue.
- No source upload, ever
- The detection runs locally. There is no optional cloud feature that quietly sends code away for deeper analysis, and no tier that unlocks one. If there were, the rest of this page would be worthless.
- One usage call, on by default, and you can read all of it
- We will not write “no telemetry” here, because it would be untrue. A usage heartbeat is on by default and posts to our servers at most once a day. It carries eight fields at most: which product ran, an install id, the version, the operating system family, a timestamp, a count of how many scans ran, a device token while you are signed in, and a short list of the language names seen in the last scan. It never carries a path, a project name, a finding or a line of source. Setting VULKRO_OFFLINE or VULKRO_NO_HEARTBEAT reduces it to zero egress, and the next section prints the body field by field.
- No account to scan, and Pro verified without your code
- No account is needed to scan: one repository at a time, every language, every finding with its proof, for as long as you like. An account holds Pro, and the first verified device login starts a 14-day term of the full product. Entitlement is online-first: the signed lease refreshes on that same daily call and keeps working for roughly 14 days without one, and when it lapses the machine returns to Free rather than stopping. A machine that will never reach the network holds Pro through an air-gap licence file instead, bound to that machine and verified locally. That file is the escape hatch rather than the default, and we issue it on request at license@vulkro.com.
- An enforceable air gap
- One switch makes the process refuse every outbound call at the boundary rather than merely declining to make one. With an air-gap licence file installed, the review runs fully disconnected, and the vulnerability archive arrives by disk, internal mirror or your own feed.
Everything the scan needs stays on your machine: the code it reads, the map it builds of how your product fits together, the findings it writes, and the optional local model it can consult without leaving the machine. One line leaves.
- Stays on this machine
- source code, call graph + taint, findings, account layer, and the local model on 127.0.0.1
- One crossing
- The usage and entitlement call: eight fields at most, at most once a day, and nothing derived from the contents of your code. Turn it off, or run from an air-gap licence file, and this line does not exist at all.
- Never crosses
- source code, file paths, file names, finding contents, project names, the hardware fingerprint
The vulnerability archive is distributed with a checksum and verified against it when it lands. That is a check on integrity in transit, and it is deliberately not described as a signature check, because those are different guarantees and only one of them is on offer. How the archive is built, how often it is refreshed and how to move it into a network with no route out are in the offline vulnerability matching guide, and the outbound inventory is itemised in the network reference.
02 / The one crossing
If one line leaves, you should be able to read it
A privacy claim that cannot be checked is a policy. This call is on by default. Here is the entire body of it, field by field, with what each field carries.
The eight fields are: the product that ran, an install id, the version, the operating system family, a timestamp, a count of scans since the last call, a device token while you are signed in, and a short sorted list of the language names seen in the last scan. Six of those are present on an anonymous run. The install id is minted at install and is not a hardware fingerprint, so it cannot be correlated with a machine you own elsewhere. The scan counter is how a per-seat licence is enforced without anybody looking at what was scanned. The language list is capped in length and is never a path or a filename.
What is not in the body: no source, no file paths, no file names, no project names, no findings, no repository or branch, and no hardware fingerprint. There is no field one of those could travel in.
Setting VULKRO_OFFLINE or VULKRO_NO_HEARTBEAT reduces this to zero egress. VULKRO_OFFLINE goes further and makes the process refuse every outbound call rather than skipping this one. On a machine running from an air-gap licence file there is no cadence, no call and no body at all, because the entitlement was verified locally from the file.
| Field and example value | What it carries |
|---|---|
| "product": "vulkro", | which binary made the call |
| "install_id": "3f9c1a7e-5b40-4c8d-9a21-7ce4d0b28f16", | a random id minted at install, not a hardware fingerprint |
| "version": "0.21.0", | the build that is running |
| "os": "macos", | operating system family, not a machine name |
| "scans_since_last_heartbeat": 4, | how many scans ran since the last call |
| "timestamp": "2026-08-15T09:41:02Z" | when the call was made |
03 / The position
Paid, supported, and still runs on your machine
Plot this category on two axes, whether it runs where your code already is and whether anyone is accountable for it, and one of the four cells is nearly empty.
| Who is behind it | Runs on your machine, one binary | Runs in somebody else’s cloud |
|---|---|---|
| Free, community supported | Several genuinely good command-line scanners live here. You get the tool, a forum, and whatever accuracy claim the project makes about itself. | The free tiers of the hosted platforms, which is the same trade as the paid cell to the right with a lower ceiling. |
| Paid, with a vendor accountable | Vulkro. A supported product that runs entirely on your machine as one binary, with a published measurement method behind the accuracy claim. | Almost everything else that a company pays for in this category. You connect a repository, code or snippets leave the building, and telemetry comes with it. |
The free offline tools are real and some of them are good. The trade is that you get community support and an accuracy claim the project makes about itself. The paid products are almost all hosted, which means connecting a repository and accepting that code, or fragments of it, leaves the building. If your constraint is that it must not, then the paid cloud column is not a decision you get to make.
What the middle cell is supposed to give you is both: somebody accountable for the product, and a scan that never leaves the machine. That is the position this product occupies, and the only way to make it credible is to publish how the accuracy is measured rather than asking you to take the architecture as evidence of quality.
04 / One binary
The surface that usually takes three or four tools
A disconnected environment is the worst place to be running four separate tools, each with its own update path and its own data feed.
The application code itself
Secrets, including the ones you removed
Infrastructure, containers and built images
Dependencies, matched against data on your disk
Values are followed across file boundaries in Python, JavaScript, TypeScript and Go. In Java the trace stops at the first file boundary, so a Java result on the same codebase is narrower than a Python or Go one. C, C++ and PHP are read one function at a time. Those, plus Salesforce Apex in the Salesforce build, are the whole list: there is no Ruby, C# or Kotlin parser. The per-language depth is set out on what the scanner covers and in the supported detection reference, because a language you rely on being absent is something you should learn here rather than after installing.
05 / Measured, not asserted
Offline is the architecture. This is the accuracy
The result below comes from a test built out of real applications with real, publicly documented vulnerabilities in them, at the strict setting we recommend for a gate.
Measured againstvulkro 0.25.3, measured 2026-09-08
| What was measured | Result |
|---|---|
| Real vulnerabilities found | 33 of 83 |
| Real vulnerabilities missed | 50 |
| False alarms raised | 20 |
The detection engine is closed, and that is the licensed product. What is published instead is the thing that decides whether the claim is worth anything: the way accuracy is measured, the provenance of every vulnerability in the test, and the ones the review fails to find. You can check the catch rate and the false-alarm rate without anybody handing over the rule engine, which is the honest version of this trade rather than an invitation to read the source.
Note what the middle row costs us to publish. A vulnerability the review cannot find stays in the test rather than being removed from it, so every figure above is lower than it would be if we curated the test to flatter the result.
06 / Honest scope
What running offline does not buy you
Offline solves exactly one problem, and it is worth being precise about which one.
- Offline is an architecture, not an accuracy claim
- Running on your machine says nothing about how much the review finds. That is a separate argument, made with a measurement, and it is made further up this page rather than implied by the word offline.
- Nothing is executed or probed
- This is a read of your code and your configuration. It does not attack a running system, and it is not a substitute for someone paid to try.
- Coverage is uneven by language, and published as such
- Code analysis covers Python, JavaScript, TypeScript, Go and Java with cross-file data flow (Java stops at the first file boundary), plus C, C++ and PHP read one function at a time, plus Salesforce Apex in the Salesforce build. There is no Ruby, no C# and no Kotlin parser, so a project in any of those gets dependency matching from its manifest and nothing else. That list is written here rather than left to be discovered after installing.
- The data is as current as your last delivery
- A disconnected machine cannot refresh anything on its own. The vulnerability archive is as current as the copy somebody carried in, which is a property of the constraint rather than a shortcoming of the tool.
5 manifest and lockfile formats are read (npm, PyPI, Go modules, crates.io, Maven), but the published archive does not currently carry all 5: the default one ships npm and Python packages, and the wider archive adds Go modules, crates.io and Maven. The archive is distributed with a checksum and verified against it on arrival, which is an integrity check and not a signature check. On a disconnected machine, confirm which ecosystems the copy you carried in actually covers before reading an empty result as a clean one.
Everything here runs on the same engine
Vulkro reviews your codebase and Vulkro for Salesforce reviews your Salesforce build. Vulkro Red is the other half of the team: it takes what the review found and works out what an attacker would actually do with it.
Reviews your code
Goes through every line of your codebase before a release, the way a senior engineer would if they had the time, and tells you what a customer could exploit.
Reviews your Salesforce build
The same review for the part of your business that runs on Salesforce, including the settings in the org itself, and what the AppExchange security review will ask you for.
Attacks what they found
Takes the review and works out what an attacker would actually do with it: which small problems chain together into a real break-in, and which ones are noise.
One engine behind all three, so the red team works from what the review already foundIt all runs on your machine. Your code never leaves it.