Scan an unreleased managed package offline
An unreleased managed package is not just a repository. For most Salesforce ISVs it is the company. The Apex, the Lightning components, the flows and the metadata are the whole asset, and they are worth something precisely because nobody outside the team has read them yet.
That makes the standard security-scanning arrangement awkward. The category is built on the assumption that you will hand your source to someone else's pipeline: push the branch, let a hosted analyser clone it, wait for a dashboard. For a finished, released, public codebase, that trade is unremarkable. For a package that has not shipped, it means the first outside party to hold a complete copy of your unreleased product is a vendor you evaluated in an afternoon.
Vulkro for Salesforce is built the other way around. The analysis happens on the machine holding the code. There is no upload step to opt out of, because there is no upload step.
What "offline" has to mean before it means anything
"Runs locally" is easy to say and easy to fudge. A tool can run a local agent that streams your syntax tree to a server for the expensive part. A tool can keep the code local and post every finding, with its file path and code excerpt, to a hosted issue tracker. Both of those are described as local scanning somewhere on the internet today.
So the claim worth making is narrower and checkable: no command uploads your source, and a scan transmits nothing about your code at all. The one outbound call a plain scan makes is a usage heartbeat that carries no code, no paths and no findings, and the next section prints its whole payload. Anything that would send a finding somewhere else is a separate flag you type on purpose. The scanner parses Apex, Lightning Web Components, Aura, Visualforce, flows and metadata XML from disk, runs its dataflow analysis in process, and writes the report to disk or to your terminal. Nothing in that path opens a socket.
The command
Point it at the package directory. That is the whole invocation.
VULKRO_OFFLINE=1 vulkro-sf scan ./force-app
scan takes a path to an SFDX project or to a retrieved metadata folder,
and defaults to the current directory when you omit it. If your package
lives as retrieved metadata with no sfdx-project.json beside it, add
--metadata-only and it will scan the folder as it stands. --format json or --format sarif gives you a machine-readable result for a
pipeline. The exit code is the contract: 0 for a clean scan, 1 when
findings were reported, 2 for an error.
VULKRO_OFFLINE=1 is the part that matters here, and the next section
explains exactly what it changes.
What leaves the machine, stated precisely
Being exact about this is the entire point of the page, so here is the whole list rather than a slogan.
By default, signed in and online, the CLI sends one thing: a usage
heartbeat to api.vulkro.com, roughly once a day. It carries the product
name, an install identifier, the binary version, the operating system, a
timestamp, a scan counter, a device token while you are signed in, and
the set of language slugs the last scan touched. It does not carry file
paths, project names, namespace prefixes, findings, rule IDs or source.
That is the entire payload, which is why it is short enough to print.
The same connection is what refreshes your entitlement. Vulkro is online-first: the CLI holds a short-lived signed lease and renews it on that daily call, with roughly fourteen days of cached grace so a disconnected week never interrupts you.
Everything else is opt-in and named. A live dependency lookup only
happens if you ask for it. Posting a finding summary to a chat channel
only happens if you pass the flag that does it. The report generators
write files. None of them are on by default, and none of them run during
a plain scan.
Zero egress, and what it costs you
Setting VULKRO_OFFLINE=1 refuses every outbound call, including the
heartbeat and the entitlement refresh. Nothing is sent, nothing is
attempted, and the scan runs exactly as it does online because the
analysis never needed the network in the first place.
You can verify that rather than trust it. On Linux, run the scan inside a network namespace with no route out:
unshare -rn /bin/bash -c 'VULKRO_OFFLINE=1 vulkro-sf scan ./force-app'
The scan completes. If it were quietly phoning home for anything load bearing, it could not.
The honest cost: with all egress refused, the CLI cannot renew its lease, so a machine that is permanently disconnected needs an air-gap licence file installed instead. That is the escape hatch for genuinely isolated build environments and it is issued on request at license@vulkro.com. It is not the default path, and it is not something you need for the common case, which is a laptop or a build agent that has a network but is not allowed to put your package on someone else's server.
Full detail on both, including the per-call inventory, is in offline mode.
What the scan actually reads in a package
A managed package is not just Apex, and a review does not treat it that way. The scan walks the whole source directory:
- Apex classes and triggers, with cross-method dataflow, so a tainted value that enters a controller and reaches a dynamic SOQL call three methods later is followed rather than guessed at.
- Authorization enforcement, which is the single most common reason a
package comes back with findings: object and field permission checks,
sharing declarations, and the API version fork. From v67 (Summer '26),
SOQL, SOSL, DML and
Databasemethods default to user mode, an undeclared class defaults towith sharing, andWITH SECURITY_ENFORCEDis removed and will not compile. A package that still carries it needs migrating, and the scan tells you where. - Lightning Web Components and Aura, for the DOM sinks and the unescaped-render paths that turn a component property into script.
- Visualforce, for the same class of problem in the older surface.
- Flows and metadata XML, including the permission sets and profiles your package ships, which is where over-broad access usually gets granted by accident rather than by decision.
- Secrets and named credentials, because a hardcoded key in a package ships to every subscriber org that installs it.
Before you submit
Two commands turn a scan into a submission decision.
vulkro-sf presubmit . runs a focused checklist of ten checks and prints
each one as PASS or FAIL, which is the right shape for a developer loop
or a CI gate: the question is not "audit everything" but "is this change
safe to submit?" The path argument defaults to the current directory, and
--format json gives a machine-readable result for a gate that blocks on
any FAIL. The details are in the
presubmit reference.
vulkro-sf appexchange-report . renders the readiness report structured
the way a reviewer reads it, with PASS, FAIL and NOT EVALUATED sections
kept distinct so you can see what the scanner covered and what it did
not. That command requires Pro. The fourteen-day trial of the full
product covers a submission cycle, so you can run it against the real
package before deciding anything.
Both run on the same local pass over the same source, with the same egress behaviour described above.
Why this ordering saves you the round trip
The scanners that gate a listing run inside the review pipeline, after you have submitted. That is a reasonable design for the platform and a bad one for your calendar, because the first time you see the output is the first time you have already spent a submission attempt. A failed round costs two to three weeks of reviewer turnaround, and for a paid listing each attempt carries a submission fee.
There is no official first-pass failure rate. The figure partners cite most often is about half, and that is an industry estimate rather than a published number. Either way, the arithmetic only points one direction: the cheapest place to fail the review is on your own machine, before the package has ever left it.
Next steps
- Vulkro for Salesforce - the full picture: what it scans, what it reports, and how the AppExchange readiness workflow fits together.
vulkro-sf presubmit- the ten-check submission gate, its flags and its exit codes.- Offline mode - the per-call egress
inventory, the
VULKRO_OFFLINEbehaviour table, and how to verify the contract yourself.
Start with a 14-day trial of the full product. Licenses are issued directly by our team at license@vulkro.com.