Changelog
Release notes for every Vulkro version
Plain-language release notes for Vulkro for Salesforce and Vulkro Core, newest first. Read one product or both together.
Release artifacts on dist.vulkro.com ship with SHA256SUMS and a CycloneDX SBOM.
Added
- Entry points for every route, in JavaScript/TypeScript, Java, Python and Go. Each endpoint now names the function that handles it, its full path (with router, group, blueprint and mount prefixes applied) and the guards that protect it. Covered: Express, Fastify, Koa, Hono, NestJS (global prefixes, global guards and opt-out decorators), Next.js (route handlers, pages, server actions and middleware matchers), tRPC, GraphQL resolvers, Spring MVC and WebFlux, JAX-RS, Quarkus, Micronaut, servlets, Django and Django REST Framework, FastAPI, Flask, Starlette, aiohttp, gin, echo, fiber, chi, gorilla/mux, net/http, gRPC and connect-go. MCP server tools and LLM agent tools are entry points too, so a tool that passes its input to a shell, a query or a file is reported.
- One severity model. Severity now comes from who can reach the code (anonymous, any signed-up user, an internal user, an administrator), what is at stake (code execution, file access, account takeover, other users' data, internal network reach), whether the path is proven, and whether a guard stands in the way. Every placed finding says why in its reason.
- Authorization checks. One check decides whether a route is
authenticated, whether a record is scoped to the caller and whether a role is
required. New findings: lookups by id with no ownership check, role changes
with no role check, Next.js server actions without authorization,
middleware-only authentication, unguarded route groups, over-wide
permitAllrules, unsigned webhooks and request bodies written straight to the database. - One issue per root cause. When several analysis passes report the same flaw, the scan shows it once and lists the other reports under it. A suppression or triage decision on any of them applies to the issue.
vulkro verify <results.sarif>. Checks another tool's SARIF findings against Vulkro's own proof and marks each one proven, not provable (with the reason) or not examined.vulkro why-not <path[:line]>. Explains why nothing was reported at a location: not read, no entry point reaches it, a guard or sanitiser applies, suppressed, folded into another issue, or below the confidence floor.- Verified fixes.
vulkro fix --verifyapplies each proposed patch to a copy of the project and scans it again, keeping only patches that remove the finding without adding a new one.--bundlewrites the verified patches and--applyapplies them. SQL fixes use the bound-parameter form of the driver you use. The MCP server gainsverify_fix. - Risk category and origin on every finding. Each finding carries one of ten categories and where its file comes from (first-party, test, example, generated, vendored or browser code). Findings in test, example and vendored code are reported at Low at most, and server-side rules no longer fire on browser code.
- Go dependency reachability per advisory. Each Go vulnerability is checked against the exact function the advisory names: reachable (with the call path), imported but not reached, or not imported.
- Malicious package behaviour. Install scripts that fetch or run code,
setup.pyfiles with network or exec at import, executable.pthfiles, and packages resolved from the public registry under a private-looking name. - AI bill of materials.
--format aibomlists LLM SDKs, model identifiers, model files and MCP servers with file and line evidence. - Compliance packs for the OWASP Top 10 for LLM Applications 2025, the
OWASP Top 10 for Agentic Applications and the CERT-In SBOM guideline, plus
vulkro sbom --check FILE --profile cert-in. - Exposure report.
vulkro report exposuresummarises one application's entry points by authentication state, unauthenticated reach to sensitive code, proven Critical and High findings and authorization verdicts, as HTML, Markdown or JSON. - Notifications to several destinations at once with
--post-to, saved destinations, and Jira issues updated instead of duplicated on re-runs. - New sinks and sources across the four languages, including Java XXE,
deserialization, Kryo, ScriptEngine and SpEL, MyBatis XML mappers and Spring
Data queries, Thymeleaf view names, JavaScript prototype pollution, ReDoS
and import-aware file and database sinks, Python HTTP client objects and
Django
.extra, and Go GORM, sqlx,http.ServeFileand text/template.
Changed
- Editor and console triage share
.vulkro/triage.toml. Marking a finding as a false positive or accepted risk in the editor or the console writes the same file the CLI reads. - Quieter editor. Diagnostics start with the proof tier in words, show the risk category and the proof hops on hover, hide the lower-confidence tier by default, and re-analyse only the saved file.
- Go same-package calls and parameter-type call resolution are on by default, so more Go and TypeScript flows carry a full proof path.
Fixed
- Scans no longer crash with a stack overflow on Go code where a function literal calls itself through its own variable.
- Fewer false positives: a writer argument is no longer treated as written data (server-sent events, CSV and zip writers are not HTML), test and example keys are no longer High or Critical, sanitisers, allowlists, parse pipes and validate-then-reject checks are recognised, bound query arguments are not SQL text, and missing-authentication findings on public-by-design routes are no longer Critical.
Added
- New detections for JavaScript and TypeScript. A
Math.random()UUID reused as a security token, a request value used to look up an object property (prototype lookup), and react-admin record data rendered as raw HTML are now reported with the traced path behind them. Command and request-forgery sinks are recognised by the shape of the call, not only by its name. - Deeper Go analysis. Go struct fields are tracked one by one, so a request value stored in one field no longer taints the whole struct. A returned error no longer carries taint it never held, and a redirect to a configured URL is not an open redirect.
- Scan coverage report. JSON and NDJSON output now say what a scan did not read: files present versus analysed, per-language counts, skipped files with the reason, and any pass that did not complete. An empty result can no longer be mistaken for a clean one.
vulkro env. Prints every environment variable the scanner reads, grouped by topic (vulkro env network,vulkro env account, ...), from the binary itself.- ISO 27001:2022, DORA and NIS2 compliance packs. Every compliance pack now judges a control by what each finding is about, so an injection flaw no longer fails an unrelated logging or MFA control, and a control whose checks ran clean now reads Pass.
--timings. Records how long each analysis pass took.
Changed
- Higher-precision default view. A Critical or High finding keeps that severity only when it is backed by proof (a traced data flow, a verified secret, a matched advisory or a decisive code shape). Unproven rows are still reported, at Medium. Reachability is computed for framework route handlers by default.
- Development-only dependency CVEs are held back. A vulnerable package
used only for development and testing, never shipped, leaves the default
view and the default failure policy. It stays visible with
--all-confidenceand in JSON and SARIF, marked as a dev dependency. - The desktop console requires an access token. Each start prints a link carrying a fresh token; requests without it, or sent from another web page, are refused.
- Faster scans. Independent analysis passes run in parallel, with byte-identical findings.
Fixed
- Fewer false positives across log injection (now Medium), cleartext
HTTP, Dockerfiles, CI secret references, placeholder secret values, PII
words in log text, Helm
secretName, file uploads and WebSocket checks. - The unused-code check no longer hides findings in live code. Lazy
import(), Viteimport.meta.globand TypeScript./x.jsimports all count as imports. - Review and diff never modify your repository. Reviewing the working
tree used to refresh
.git/indexas a side effect; every git command the scanner runs is now read-only. vulkro logoutrevokes the device and frees it from your seat, so you can sign in on your next machine straight away. It previously signed out locally only and reported the server as unreachable.- The GitLab CI, CircleCI and Jenkins templates run. They called flags and an output format the CLI does not have, so the first scan step failed. They now write their reports through standard output, write every report before the job exits with the scan's result, and run the merge gate only when the runner is signed in.
Fixed
vulkro-sf cloud connectsays when a connection is waiting for approval. On a workspace that requires an admin to approve new connections, the command now says the connection is on hold and where an admin approves it, instead of reporting success. A command refused because the connection is still pending, or because this network is not on the workspace's allowlist, now gives that reason instead of blaming your workspace role.vulkro-sf logoutrevokes the device and frees it from your seat, so you can sign in on your next machine straight away. It previously signed out locally only and reported the server as unreachable.
Added
- Checked against your live org. A new
vulkro-sf org contextcommand reads who actually holds each profile and permission set, which objects they can open, which add-ons the org has, and which components Salesforce itself ships. Scans use it to decide whether a finding can really be reached, so a permission nobody holds, or a field grant on an object the profile cannot open, is no longer reported as a live risk. - Salesforce's own components are recognised. Standard profiles and permission sets nobody uses, the Sites starter pages, default flows and platform users are grouped as Salesforce defaults instead of being reported as your issues.
- Every finding has a risk category, such as public exposure, injection, access control, identity and permissions, secrets, data protection, integrations, org configuration, automation or code quality, in the JSON and SARIF output.
- New checks: a guest lookup that finds a record by a guessable number, CSV and spreadsheet formula injection, sensitive data sent on platform events, permission set groups that bundle full-org admin powers, secrets inside static resources, a portal user able to edit the decision on their own record, record data written to the browser console, a password reset for a caller-chosen user, encryption keys written in code, passwords left in comments, unescaped Aura output, old JavaScript libraries in static resources, integration users with no login IP restriction, and browser protections (content sniffing, XSS filter, referrer policy, redirect warning) turned off.
Changed
- One issue per root cause. A permission set that grants access to hundreds of objects is one finding, not one per object, and one Apex method reports each kind of problem once, with the related checks as evidence.
- Severity follows who can reach the code and what data it touches. A proven path from an unauthenticated guest to sensitive data is Critical; the same flaw that only an administrator can reach, or that touches no sensitive data, is rated lower. Grants nobody holds are Low.
- Correct code stays quiet. Checks now recognise ownership checks, user mode (including the default for Apex API version 67 and later), field and object access checks, allowlisted values, bind variables and correct escaping, and only count a check that runs before the risky line.
- Guest access is judged the way Salesforce enforces it. A guest sees no record without a guest sharing rule, so a guest read on such an object is reported as a latent risk; a guest sharing rule that opens every record of a sensitive object is Critical.
- Add-on advice only where it applies. Field Audit Trail and Shield encryption recommendations appear once, and only when they are relevant to the org, instead of once per field.
- Inventory is information, not an issue: active sites, Experience Cloud bundles, message channels and exposed-surface lists.
Fixed
- The number of users on a profile is counted correctly.
- A young org is no longer told its setup audit trail is too short.
- Checks that could not read a setting report that they were not evaluated instead of reporting a finding.
- Salesforce's own packages are no longer called third-party, and a Full scope connected app is no longer described as limited.
- A connected app's public consumer key is no longer reported as a secret.
- Many false alarms on correct code, including bound dynamic queries, validated redirects, constant-returning guest methods, count queries, batch query locators and callouts that set a timeout.
Fixed
- The same org gives the same results, scan after scan. Two scans of an unchanged org could list different personal-data fields, attach a finding to a different Visualforce page, or give a finding a new identity, because results depended on the order Salesforce returned data and files were read. Results are now in a fixed order, and a finding keeps its identity when code above it moves.
- A check that could not read the org says so. Sharing rules, integration users, object and field permissions, profiles and permission sets, installed packages, event monitoring, personal data and the audit-trail cross-walk used to report a failed or cut-short read as "no findings". They now report that the check could not run, or ran on part of the org, and why.
- Complete reads instead of a random subset. Sharing is read as totals per grantee, integration-user logins as totals per user, login history over the last 30 days and the setup audit trail over 180 days, event logs per event type over 30 days. The audit-trail cross-walk now reads the newest events (it read the oldest), and audit-trail retention is judged from the org's oldest entry.
- Login policy, trusted admin IP ranges and service-account IP locks are checked again. On current Salesforce API versions these checks could not read profile login IP ranges and silently did not run. They now read them from profile metadata.
- Personal data lists every field. Each object lists all of its personal data fields, not a sample of five.
- Vulkro Cloud for Salesforce issues stay put. An issue is closed only when the part of the scan that finds it ran, keeps its key and assignee when its code moves, and every automatic close, reopen or move shows why.
Added
vulkro-sf org <audit> --format json-reportprints the findings together with how complete the read was.- JSON findings carry a stable
entity: what the finding is about, unchanged when counts or wording change.
Added
- Vulkro Cloud from the terminal (
vulkro-sf cloud). Connectvulkro-sfto your team's Vulkro Cloud for Salesforce workspace once, approving it in the browser, then list what to fix, triage, assign, review proposals, make a rule advisory or turn it off, start a cloud scan, plan a fix against your local SFDX project and verify it is gone, all from the terminal. The same actions are available to AI assistants assf_cloud_*MCP tools, andvulkro-sf cloud triage-aiasks your configured model for verdicts that wait for a person to approve them. Nothing in these commands changes a Salesforce org. - Every check in one list (
vulkro-sf checks). Lists all 659 checks Vulkro for Salesforce can report, 96 on a live org and 563 on code and metadata, including the Well-Architected anti-patterns and the release readiness items, each with its id, category, default severity, what it looks for and why it matters. The list is verified against the engine on every build. Also available to AI assistants as thesf_list_checksMCP tool.
Added
- Attack paths as one graph. Every scan now joins who can reach your code
(guest, community member, internal user), the door they come through (Aura /
LWC endpoint, Apex REST, Visualforce, Flow), the grant that lets them in, the
access mode the code runs in, and the data it touches. Every step cites a file
and line. The JSON output carries the full graph (
attack_graph), and a Critical finding is now always explained by a path from a person to your data. - Findings written in Salesforce terms. Each finding leads with a sentence
like "an unauthenticated guest user can write Contact.Email through
BookingController.updateContact because the update runs in system mode with
no field-level security check", followed by who can trigger it, the door, the
access mode, the data flow, the fix in Salesforce idiom (
WITH USER_MODE,Security.stripInaccessible,queryWithBinds,with sharing) and how to verify it. The same card appears in the CLI, SARIF, the editor and the console. - Effective sharing and access mode. Vulkro now works out how each Apex class and method really runs (user mode, system mode with sharing, system mode without sharing) along the call stack, following the Apex Developer Guide rules per API version, inherited and omitted sharing, triggers and async entry points. Record-access findings use it, and the editor shows it as a lens on every class and entry method.
- Metadata change review.
vulkro-sf reviewreports what a metadata change grants and to whom: a guest profile gaining read on a sensitive field, a permission set gaining Modify All Data, a connected or External Client App losing PKCE, a prompt template reading a web-writable field. It works on a branch, a commit range or two retrieved folders, and posts to pull requests. - One AppExchange command.
vulkro-sf appexchange-reportnow produces the readiness checklist and the full submission packet: the review-blocking fixes ordered by effort, imported Salesforce Code Analyzer results with drafted false-positive justifications built from Vulkro's own evidence, an API 67.0 readiness section, and a False Positives Whitelist file.presubmitandasrrstill work and point to it. - API 67.0 readiness. Lists what changes when a class is bumped to 67.0:
queries and DML that move to user mode,
WITH SECURITY_ENFORCEDthat stops compiling, and classes whose omitted sharing flips. - Repository triage file. Decisions (false positive, accepted risk with an
optional expiry, confirmed) live in
.vulkro/triage.toml, reviewed in pull requests like code, and are honoured by the CLI, the console, the editor and the AppExchange whitelist. - Precise guest and community exposure. New checks for guest-readable sensitive fields, guest sharing rules on sensitive data, open external org-wide defaults, Apex that returns User contact details to guests or members, guest-run system-mode flows whose record comes from user input, and public site API settings.
- Latent public exposure. An LWC that can be placed on an Experience Cloud or site page and calls Apex that runs without sharing on sensitive data is now reported as "reachable by any visitor if published", even before a site exists.
- Public custom settings holding secrets or personal data are reported, with who can read them.
- Agentforce review requirements. Checks for agent actions that take record ids or field names as user input, data-changing or email actions without a confirmation step, private service-agent actions not scoped to the verified customer, flow actions running in system mode, and model output used in SOQL or DML. A new rule reports the full prompt-injection exfiltration chain.
vulkro-sf scan --org <alias>adds live org facts to the attack graph: who actually holds each dangerous permission and permission set, connected app token use, and guest-owned records (counts only, read-only).- Console. Projects open on four views: Exposure, Review readiness, Changes and Findings, with a data access matrix (objects and fields by principal, with how each access is granted), an Apex run-mode view, client workspaces with a client-ready report, and plain-language titles for every compliance control.
- Editor. The Salesforce edition shows the same default view as the CLI, re- analyses only the file you save, and shows how each class runs and which profiles can call it.
- Coverage honesty. Every output says what was not analysed and why, and counts the Apex entry points analysed and degraded.
Fixed
- CRUD/FLS on custom settings and custom metadata is no longer reported: these are admin configuration, not user data. This applies to settings from managed packages too.
- Your own security layer is recognised. Permission-check helpers in other
classes, fflib security utilities and
Security.stripInaccessiblecount as enforcement when they run before the operation. - Fewer false findings from SOQL built only from bind variables or schema
names, schema-validated object names,
JSON.deserializeUntyped, constant queries,Type.forNameused without instantiation, records already selected under sharing, and cache or loggerputcalls. - Call resolution. Apex calls now resolve to the methods the declared types can reach instead of every method with the same name, so paths follow the code that can really run.
- Project layouts. Metadata API layouts (
src/withpackage.xml), flat folders and SFDX projects nested inside a repository are now scanned in full. vulkro-sf entrypointsno longer counts class grants marked disabled, so guest-reachable counts on full org retrieves are correct.- Live org.
org guest-livechecks public Sites on API 67.0 and treats an org without Experience Cloud as a fact rather than a failed check;org effective-permsnames every object and permission set and weights grants by active holders; the posture list and org status counters are filled in; the personal-data map includes custom objects. - SSN, passport and national ID fields (and other identity document numbers) are classified as government identifiers.
- Scans make no network call. The Agentforce rules bundle is refreshed only by
vulkro-sf update. - Faster scans on large projects, with lower memory use.
Changed
- Quieter default view.
vulkro-sf scanshows Medium confidence and above by default, the same asvulkro scan;--min-confidence lowshows everything. - Visualforce XSS with a proven request source reaching an unescaped sink is High.
- The local console requires the access token printed at startup and refuses cross-origin requests.
Fixed
- Fewer false SSRF alerts on constant URLs. A request handler that calls
fetch()or a similar HTTP client on a hardcoded URL is no longer flagged as server-side request forgery when no attacker-controlled input can reach that URL. Deep scans in particular could report one fabricated Critical SSRF per route that fetched a fixed endpoint; those are gone. Genuine flows, where user input actually reaches the outbound request, are still caught.
Fixed
- Monorepo scans no longer crash. In a workspace layout where a package's
node_modulescontains symlinks that point outside the package (pnpm, npm and yarn workspaces all do this), a scan could abort partway through. In the editor this showed up as no findings at all on the whole project. These projects now scan through to the end and report their findings normally.
Fixed
- Workspace scans no longer crash. In an SFDX or JavaScript workspace where a
package's
node_modulescontains symlinks that point outside the package (pnpm, npm and yarn workspaces all do this), a scan could abort partway through and, in the editor, show no findings for the whole project. These projects now scan to completion and report their findings normally.
Added
C, C++ and PHP support. Vulkro now scans C, C++ and PHP alongside JavaScript, TypeScript, Python, Go and Java. Each ships with proven-tier taint tracking (an attacker-controlled input followed through the code to a dangerous call) and a full set of detectors: command and SQL injection, path traversal, uncontrolled format strings, XML external entities, LDAP and XPath injection, PHP phar deserialization, disabled TLS certificate verification and predictable randomness for security values. Vendored and bundled third-party code is skipped by default, so findings land on the code you own.
Change intelligence for AI-assisted coding. Three tools, in the terminal, the editor and over MCP, give an assistant the map of your application before it changes anything.
code_graphbuilds a ranked, scan-free map of your symbols and what depends on what.assess_changereports the blast radius of an edit, the tests that cover it, and whether a diff is safe or introduced a new finding.from_tracetakes a production stack trace and resolves each frame to the function it names, with the problems already on that line. All three are deterministic, offline and read-only, and every count says what it could not resolve, so a number is never mistaken for the whole picture.More weak-cryptography and secret coverage. New detectors flag broken symmetric ciphers (DES, 3DES, RC4, Blowfish) in Go, JavaScript, Python and PHP; MD5 and SHA-1 built from a string algorithm name; secret comparison by reference in Java; and the secret scanner now recognizes more private-key formats and Azure Storage keys.
vulkro demoandvulkro glossary.vulkro demowrites a small, deliberately vulnerable Express project so you can watch the scanner find real issues on a known target.vulkro glossary [term](andvulkro-sf glossary) explains the security terms the output uses, in plain language.
Changed
Plain-language output everywhere. Finding titles, categories, menus, help text and console copy (in the terminal and in both desktop consoles) now read in plain English instead of rule-id shorthand, and every subcommand's
--helpexplains what it does.Scans lead with what is proven. The default output leads with the findings Vulkro can back with evidence. SARIF output now carries the specific detector rule id and honours your confidence filters, and the CWE a finding maps to was corrected across the findings, the compliance pack and the CRA / SBOM output.
Honest coverage. When a scan cannot read a language in your project, or skips a file, it says so instead of implying a clean result, and a project written in several languages is swept for each one.
Salesforce support has moved into its own VS Code extension. Apex, LWC, Aura, Visualforce, Flow, metadata and the org audit now live in Vulkro for Salesforce, a separate extension from Vulkro. Install it by running
vulkro-sf install-extensionin a terminal, or pick thevulkro-sf-<version>.vsixwhen installing from a file. If you are working in a Salesforce project, the Vulkro extension offers to install it for you the first time it sees one.The two are built to sit side by side. Install both in a repository that has both kinds of code and each takes its own files: everything under a folder containing
sfdx-project.jsongoes to Vulkro for Salesforce, including the JavaScript and HTML insidelwc/andaura/bundles, and everything else goes to Vulkro. Nothing is reported twice, and each has its own sidebar, status bar item, commands and settings.If you install only Vulkro, a Salesforce project is no longer scanned for Apex: you get a one-time notice pointing at the Salesforce extension instead. If you install only Vulkro for Salesforce, it stays out of the way entirely in a project with no Salesforce code, with no entry in the activity bar.
Fixed
vulkro fix --writeno longer breaks working code. Some automatic rewrites changed what the code did; those cases are corrected.Fewer false positives across every language. Measured false-positive clusters were removed in PHP (WordPress core), C and C++ (preprocessor and brace-less branches, alias tracking), Python log handling, NoSQL, GraphQL and infrastructure-as-code, and a large Java precision pass lifted accuracy on the OWASP Benchmark with no real findings lost.
Salesforce audit corrections. Several
vulkro-sforg audits queried fields that do not exist and quietly passed; they now run correctly, the compliance pack no longer reports "Not evaluated" as "Pass", and scanning a path that does not exist no longer returns a perfect score.Both VS Code extensions can now be installed at the same time. They used to declare the same command and view names, so whichever loaded second lost its commands and the two fought over one sidebar. Every command, view and setting now carries the name of the extension it belongs to.
Vulkro for Salesforce reads its own settings. Two settings it used to read from the
vulkro.namespace are nowvulkro-sf.lsp.debounceMsandvulkro-sf.showAdvancedViews. The old names keep working for one release, and the extension tells you once which name to move to.
Added
- Install the VS Code extension with one command.
vulkro install-extension(andvulkro-sf install-extension) finds your editor - VS Code, Cursor, Windsurf, or VSCodium, even when itscodecommand is not on your PATH - and installs the Vulkro extension into it, after asking first. The extension shows findings as you type, with one-click fixes, and signs in the same way the CLI does. - Recent scans across every project in the console.
vulkro servenow has a "Recent scans" list that spans all of your projects, newest first, so a scan you just ran from the command line is one click away instead of buried under a project you have to hunt for.
Fixed
- Salesforce command-line scans now show up in the console. A scan run with
vulkro-sf scanis now recorded invulkro-sf servethe same way a scan started from the console is, so your command-line scans appear in the scan history.
Added
- Review only what changed. Both consoles gain a Changes view that scans the findings on your current git diff or last commit (and, for Salesforce, the metadata that changed), so you can review a pull request's new risk without re-reading the whole repository. Findings that are new since your baseline are separated precisely from ones that were already there.
- See what was filtered, and why. The findings view now leads with a
breakdown of how many findings were admitted, demoted, or not examined, with a
proportion bar and one-click filtering to each group. On the command line the
same view is available with
--disposition admitted|demoted|not-examined. - Scan-coverage reporting. The console shows how many files were analyzed versus how many are present, so a partial scan is never mistaken for a clean one, and flags the specific files that could not be parsed.
- Exploit-probability and known-exploited context on findings. Findings now carry EPSS (exploit-probability) and CISA KEV (known-exploited) signals, so you can rank by real-world exploitation likelihood, not just severity.
- Workspace-aware project picker. In a monorepo, the sub-project picker groups projects by their workspace folder.
- Verifier tools for AI agents. The built-in MCP server exposes a scan-diff tool (compare two scans) and a prove tool (confirm a finding's reachability), so an assistant can check results against the deterministic engine.
- Salesforce: risk-adjusted coverage by business process in the console.
Updated
- Deeper data-layer SQL analysis. SQL sink recognition now spans the common data layers - Prisma, knex, Sequelize, the Django ORM, and gorm - with a parameterization-aware core, so a genuinely bound query is not mistaken for injection while a raw-string query still fires.
- Cross-file and cross-package taint. Data flow now follows values across workspace-package boundaries and Java cross-file call graphs, and a dependency CVE is checked for whether your code actually imports the vulnerable package before it is called reachable.
- Go request-body binding is tracked. Injection that flows from a bound Go request body is now followed to the sink.
Fixed
- Parameterized store-then-execute is no longer flagged as SQL injection. A statement that is prepared with bound parameters and executed later is recognized as safe.
- A SQL string built only from configuration values is no longer flagged. When the only dynamic part of a raw query is a config or settings value (not request input), it is demoted out of the injection surface.
- Apex dynamic-SOQL severity now tracks confidence instead of always ranking Critical, and Apex CRUD/FLS posture respects sharing inheritance.
- Fewer Apex mass-assignment false positives on case-insensitive collection filters.
Added
- Click through to any finding from anywhere in the console. Clicking a finding or a finding-bearing item in any tab (dependencies, secrets, the module and dependency-graph panels, the compare view, and the summary cards) now opens its full detail, instead of some rows being dead ends.
- Infrastructure-as-code scanning expanded. Terraform, CloudFormation, Kubernetes, Docker Compose, Dockerfiles, Ansible, and the Serverless Framework are now checked for common misconfigurations - public storage, disabled encryption, over-broad RBAC, secrets in environment, and more.
- About 100 more secret formats and committed-credential files are detected, including additional AI/LLM provider tokens, cloud-CLI credential files, GCP service-account and Docker registry auth files, and secrets left in Terraform state.
- Stronger supply-chain checks: lockfile-integrity tamper detection for
poetry / uv / cargo / composer / gemfile, a catalog of known compromised
package releases, and
bun.locksupport for exact npm dependency resolution. - New detectors for code-executing LLM agent tools, insecure ML/AI deserialization, and additional MCP-server misconfigurations, plus an OWASP Top 10:2021 (Web) compliance crosswalk.
Changed
- Fewer false positives on real code. Generic-engine open-redirect findings are now Medium rather than a noisy High; Python request-object attributes that are not attacker input no longer taint; and several measured false-positive clusters across JavaScript, Go, and templates were removed, with no loss of true findings.
- Next.js / Remix
'use server'server actions are now treated as untrusted input sources, so injection that reaches them is caught.
Fixed
- Robustness. Both scanners isolate internal panics and bound memory on large dependency manifests and container images, so a single malformed input can no longer abort a scan.
Also ships Vulkro for Salesforce 0.13.0:
- New Impact view. See each component's blast radius (how many other components depend on it) alongside its findings, with a per-component panel listing what depends on it, what it depends on, and the findings in its file, so you can tell what breaks if a class changes.
- Click through to any finding from anywhere, the same as the core console: the org posture, identity, Agentforce, CRUD/FLS, PII-map, secrets, and Overview surfaces now open the finding you click.
- Fixed: a scan in progress no longer shows as a finished, healthy result. While a scan runs, the console now shows it as analysing instead of a misleading "0 findings / healthy" briefing.
- Stronger guest and org-posture detection. Toxic permission combinations and single all-powerful profiles that enable org takeover, guest mass-exposure on Experience / LWR sites, and connected-app / OAuth misconfigurations are now flagged and ranked by real-world impact.
- Deeper Apex analysis. Interprocedural (local-variable) taint, cross-method
REST / Visualforce request-body sources, and Apex XSS via
addError(..., escape=false)are now traced; CRUD/FLS posture findings are weighted by whether the class is actually reachable.
Added
- The proof behind a finding now shows in the terminal.
vulkro explain <id>prints the full data-flow ladder (from the attacker-controlled source to the dangerous sink, with the code at each step), and the scan list marks proof-carrying findings so you can tell a traced result from a pattern match at a glance. Previously this evidence chain only appeared in the SARIF and JSON output. - Secret detection recognises about 100 more credential formats. Anthropic, GitLab runner, DigitalOcean, Google Cloud, Slack webhooks, and many other provider tokens are now caught wherever a credential can appear, not just in agent and MCP configuration files.
- AWS CloudFormation templates are now scanned for misconfiguration. Public
S3 buckets, publicly accessible RDS instances, disabled storage encryption, and
security-group rules open to the whole internet are flagged in
.yaml/.yml/.json/.templateCloudFormation, mapped to the same controls as the Terraform scan. - Modern Python dependency files are covered. Dependency scanning now reads
uv.lockandpdm.lock, and inline[project].dependenciesinpyproject.toml, so uv and PDM projects have their dependencies checked for known CVEs end to end. - A scan now tells you when it has no CVE data for an ecosystem you use (for example a Go or Rust project when the bundle only covers npm / PyPI / Maven), instead of silently reporting zero and reading as a clean result.
- New detectors. Server-side template injection in Node template engines;
XXE via libxml2-wasm; Python command injection through
subprocess.getoutputand SSRF throughurllib; deserialization RCE in Apache Fory and LangChain; Kubernetes host-namespace sharing (hostPID/hostIPC), host Docker-socket bind-mounts, and container securityContext hardening; Spring Cloud Gateway actuator exposure; and over-permissive Next.js image-optimizer configuration.
Fixed
- Fewer false positives.
re.escape,html.unescape, JavaScriptunescape(), Gofilepath.Clean, andint(...)are no longer mistaken for sanitizers;Database.executeBatchis no longer flagged as a SOQL-injection sink; and a SnakeYAML deserialization sink that was keyed on the wrong method now fires correctly. - More accurate software bill of materials. Package identifiers (purls) are now canonical for scoped npm, PyPI, Go, and Composer packages; compound SPDX licenses are placed in the correct CycloneDX field; and known-exploited / exploit-likelihood scoring is applied to container-image CVEs too.
- More robust on hostile input. The deserialization, request-smuggling, argument-injection, and cleartext scans now stay fast on crafted files, and the software-bill-of-materials output is byte-for-byte reproducible.
Also ships Vulkro for Salesforce 0.12.0:
- New: the guest exposure report (
vulkro-sf exposure). One command shows everything an unauthenticated Experience Cloud visitor can reach, ranked most-severe first, each with its mechanism (guest LWR API, guest-granted@AuraEnabledApex, Aura guest endpoint), the exposed object and fields, the granting profile, and the full data-flow proof chain. @RemoteAction(JS Remoting) parameters are now traced as attacker input, so guest and remoting entry points reach the injection detectors.- Fixed:
Database.executeBatchis no longer flagged as a SOQL-injection sink.
Added
- New: the guest exposure report (
vulkro-sf exposure). One command shows everything an unauthenticated Experience Cloud visitor can reach, ranked most-severe first. Each finding carries its mechanism (guest LWR API, guest-granted@AuraEnabledApex, Aura guest endpoint), the exposed object and fields, the granting profile, and the full data-flow proof chain, so you can see in one place exactly what an anonymous visitor can read and why. @RemoteAction(JS Remoting) parameters are now traced as attacker input, so guest and remoting entry points reach the injection detectors.
Fixed
Database.executeBatchis no longer flagged as a SOQL-injection sink.
Removed
- The desktop console is now focused on deterministic security analysis. The experimental AI-assisted and adversarial panels - AI-code audit, AI Hunt, Red Team, Bruteforce, and Attack paths - have been removed from the console. The core Findings table, triage, suppression, compliance, dependencies, secrets, and every deterministic surface are unchanged.
Added
Cross-site scripting is now traced end to end, including in the browser. Reflected XSS (a request value written into an HTML response) and DOM-based XSS (a value read from the URL, cookie, or a
postMessageand written intoinnerHTML, React'sdangerouslySetInnerHTML, or jQuery.html()) are followed from where the data enters to where it lands, in JavaScript and TypeScript, with the escaper or sanitizer on the path recognized so clean code stays quiet.More injection classes are now proven rather than guessed, across four languages. Log injection, open redirect, LDAP and XPath injection, NoSQL injection, path traversal, unsafe deserialization, and server-side template injection now report an actual source-to-sink data flow in JavaScript, TypeScript, Python, Go, and Java (whichever apply), each cleared when a validator or escaper sits on the path.
Go gRPC and Connect services are now analyzed. A request message reaching a database query, a command, or a redirect is traced, where before only net/http, gin, echo, and fiber handlers were followed.
Prototype pollution now covers the recursive-merge family - lodash
merge/defaultsDeep, jQuery deepextend, and hand-rolled deep-merge helpers - when an attacker-controlled key can reach__proto__.Salesforce: code reachable by a guest is ranked first. An Apex SOQL injection or a missing object- and field-level access check that an unauthenticated Experience Cloud or Sites visitor can reach now outranks the same issue sitting behind a login. Flow is now followed from screen and record-triggered input into the database operations it drives, and Aura and Visualforce cross-site scripting is traced from source to sink.
Every finding now carries a disposition that tells you, at a glance, whether it is backed by a proven data flow, a heuristic pattern, or a check that cannot be decided from source alone - so the findings you can act on immediately sort to the top.
Fixed
Critical is now reserved for findings backed by a proven data flow. A finding that only matched a name or a shape, with no traceable path, no longer claims Critical severity; it reports as High instead. This sharply reduces the number of Critical rows on real code, so the top of the list is worth reading first.
A value you have already validated no longer fires. A request value checked against an inline allowlist on the guarded branch (
if col in {"name", "email"}: ...), or one field of an object when a different field of that object carried the untrusted value, is no longer reported as an injection.The same defect is no longer reported twice. When two internal analyses both flagged one sink but disagreed on how to categorize it (an open redirect filed once as a request-forgery and once as a misconfiguration), you now get a single finding with the more precise label instead of a duplicate.
A false positive on a clean neighbouring field is fixed (Python code like
user.nameno longer fires just because an unrelated local variable namednamewas tainted).Scanning a large Salesforce codebase twice could report slightly different results. On a big org (around a thousand Apex classes) two runs over identical, unchanged code could disagree by roughly fifteen findings, and a finding that appeared in both runs could describe a different database operation each time. Smaller codebases were unaffected, which is why this went unnoticed: the effect only appeared once the call graph was deep enough for a helper method to be reachable by both a short and a long path.
The cause was in the cross-method Apex analysis. When it followed calls out from a request entry point, the order it happened to visit methods in could change which helpers it considered reachable, and therefore whether it saw the field-level access check that makes a query safe. It now visits methods in a fixed order and always reaches a method by its shortest path, so the result depends only on your code.
Two consequences worth knowing about. A scan is now reproducible: the same commit produces the same findings on every run, so a build gate cannot pass and then fail on unchanged code. And because the analysis now follows shorter paths it did not previously explore, it finds access checks it used to miss, which removes a small number of false CRUD and field-level-security reports on code that was already correctly guarded. If you track finding counts over time, expect a one-off step down at this release.
The module dependency list in JSON output came out in a random order. The dependencies and their weights were always right, but two scans of the same project listed them differently, so diffing two reports showed churn that was not there. It is now sorted.
Fixed
Endpoints went missing in projects with a
samples,demoorexamplefolder. Those words are ordinary parts of a package name (Spring's own PetClinic sample lives inorg.springframework.samples.petclinic), but the scanner treated them as a signal that the code was demo material and quietly dropped every route in them. Anything that depends on knowing your routes went with it: unauthenticated endpoints, broken object level authorization, missing rate limits. On PetClinic that was all 16 endpoints and 19 of its 24 findings. Test files and specs are still excluded, which was the original intent.Java findings pointed at the wrong line when a method signature spans several lines. The reported line was short by the distance between the first line of the signature and its opening brace, so a finding on a wrapped handler could land a few lines above the code it was describing. Findings themselves are unchanged: only the line numbers move, and they now point at the real sink.
The installer now says why a download failed, and works behind corporate proxies. A failed download printed one generic line and sent you to a page that does not exist, so a blocked proxy, an interrupted connection and a genuinely missing file all looked identical. The installer now reports the HTTP status and the underlying error and suggests a specific next step. On Windows it also forces TLS 1.2, which some managed builds do not use by default, and authenticates through a configured system proxy. Reported by a user whose install failed on a network that required both.
Added
AI assistance for Apex fixes, with the detector as the judge.
fix --aiproposes a patch and then re-runs the deterministic detector against it. A patch that does not make the finding go away is refused rather than shown to you. On a real package it declined 6 of 15 suggestions. The AI layer is advisory throughout: it never adds, removes or reclassifies a finding, and it runs against a model on your own machine, so no Apex leaves it.Plain-language explanations of why a finding matters for a Security Review.
scan --ai-explainandtriagenarrate the deterministic findings in Salesforce terms. Opt in, and stdout and the exit code are unchanged.Editor support for Apex. The Salesforce language server now shares the full command surface with the core server, so findings appear as you work. Scans run on a background worker, so typing stays responsive on large orgs, and
// vulkro:disablenow means the same thing in the editor as it does on the command line.scan --min-severityto filter output to the severities you act on.
Fixed
Findings no longer appear on your test classes. Endpoint-style findings were being reported against test and spec files, which inflated the count and raised authentication warnings on code that never runs in production.
The language server starts for signed-out users, so the editor is usable before you have connected an org, and a finding keeps a stable identity between scans instead of flickering as you edit.
Honest language coverage. The extended-language list previously named languages the engine did not actually parse. It now names only what is genuinely supported.
Also ships Vulkro for Salesforce 0.8.0.
Added
- Check a repository for planted malicious code before you run it. A new
vulkro inspect <path>command reads just-cloned source and reports what the code is capable of doing: reading credential stores, opening a reverse shell, exfiltrating secrets over the network, self-propagating through publish tokens, draining crypto wallets, fetching and executing a second stage, installing persistence, running work from an install or git hook, hiding behind obfuscation or a date/hostname trigger, shipping a committed binary blob, pulling a dependency from outside the registry, or planting prompt-injection text in files an AI agent will read. It also catches trojan-source unicode tricks. Use--jsonfor a machine-readable manifest, and aninspect_repotool onvulkro mcp servelets an AI coding agent run the same check before it executes unfamiliar code. It never certifies code as safe or clean: static analysis of unrun code cannot do that, and every report says so. - Taint tracing now follows data across files in Java and Go. A request value that travels controller to service to repository (Java), or handler to helper to data layer (Go), is now traced through the whole chain rather than stopping at the file boundary. Resolution is deliberately conservative: a callee in the same file wins, then a uniquely-named one elsewhere in the workspace, and an ambiguous call is skipped rather than guessed, so a missed step is preferred over a trace pinned to the wrong function.
- Java remote-code-execution classes are now confirmed by data flow, not just pattern-matched. When attacker-controlled input actually reaches the sink, Vulkro reports expression-language injection (the Spring / Struts class), JNDI lookup injection (the Log4Shell class), and template-source injection in FreeMarker / Velocity, each with a source-to-sink trace in SARIF.
- New detectors for gaps found in a coverage audit. Django / Flask / Express
debug and misconfiguration (DEBUG on, hardcoded secret key, wildcard allowed
hosts, allow-all CORS, stack traces to the client); gadget-aware Java
deserialization; Go zip/tar-slip and
text/templaterendered as HTML; Go relative-binary execution and the ignored-error auth bypass; Spring data-binding without an allowlist (the Spring4Shell shape); decompression without a size cap and Go servers without timeouts or a body limit; HTTP request smuggling; Terraform instances still allowing IMDSv1; MongoDB operator injection; and origin reflection combined with credentialed CORS. - More detectors from a historical vulnerability sweep. Python and ML
deserialization (pickle, torch, unsafe YAML); an LLM completion flowing into
eval / exec / SQL; command-line option injection into git / tar / curl;
secrets and personal data written to logs; XXE parser configuration for Python
and JavaScript; a
postMessagehandler with no origin check; OAuth flows missing state or PKCE; secrets stored in cleartext orlocalStorage; and over-broad GitHub Actions OIDC trust policies. - Wider language coverage for existing checks. Object-level access-control
(IDOR) checks now have Go and Java arms, as do session fixation, CSRF, and
rate-limit recognition (bucket4j / resilience4j for Java,
x/time/rateand tollbooth for Go), plus JWT algorithm-confusion for Go. Trust-boundary violations (CWE-501) are now confirmed by data flow across all four deep-tier languages instead of by text matching alone. - Five new Salesforce checks. A
@RemoteActionmethod running SOQL or DML with no CRUD/FLS enforcement; a global@RestResourceorwebservicemethod reading or writing records with no record-level gate; an Apex managed-sharing row inserted with an over-broad access level or a hardcoded row cause; a Visualforce page that runs a controller action on the initial page load, before the platform's anti-CSRF token applies; and author-planted debug backdoors in production Apex (a test-mode check widened by an||, a hardcoded user-identity comparison, or a magic request-parameter password). vulkro-sf org report. One command produces a shareable live-org security posture report as HTML or Markdown, the same findings the console's org posture tab shows. An unreachable org produces a report that says so rather than an empty pass.- Name the client on a Salesforce readiness report.
vulkro-sf appexchange-reportgains--prepared-forand--prepared-byso a consultant can hand over a client-named deliverable. - The AI-agent guard stops calling malicious code "clean".
vulkro guard checkgained a Review outcome: a finding that carries a malicious-capability signal but sits below the block threshold is now surfaced for human review instead of being waved through with a "clean" or "proceed". Ordinary low-severity findings still pass as clean. - The desktop console shows the engine's ranking. The unified Findings table gained a Risk column and sorts worst-first by default, with the exploitability and reachability pills hoisted into the row rather than hidden in the drawer. The Overview "Top concern" now names the finding the engine ranks most urgent, so an exploitable, reachable High no longer hides behind a theoretical Critical, and the "Priority only" filter now includes anything graded exploitable instead of being a plain High-plus-Critical cut.
Changed
The scanner no longer checks for updates while it works. Every run used to start a background check against the release feed and print an "update available" line at the end. A scan should not reach the network to do its job, so that check is gone:
vulkro scanandvulkro-sf scannow make no release-feed request at all, and no upgrade notice can appear. You still get the same check when you ask for it:vulkro updatereads the feed and offers to install a newer binary, and--no-version-check(orVULKRO_NO_UPDATE_CHECK=1) skips that. Installing and upgrading are otherwise unchanged.A scan no longer sends your dependency list anywhere. When a scan found a dependency manifest it used to query the public OSV vulnerability API, sending each package's name, version, and ecosystem, to catch CVEs published since your local database. That lookup is now off unless you ask for it with
VULKRO_CVE_LIVE=1. By default your dependencies are matched against the CVE database on your own machine, so your dependency tree never leaves it. Nothing else changes: the local match is the same one that always ran, andvulkro updaterefreshes that database whenever you want it. If you prefer the fresher view, set the variable and the lookup works exactly as before.The install page now states all three of the installer's network calls (binary, CVE bundle, and an anonymous install-completion ping) and documents the
VULKRO_NO_ANALYTICSopt-out. The benchmark comparison on the site was refreshed from the current committed scorecard.
Fixed
The health score no longer reads as a danger score. A clean repository scored 100 out of 100, but four surfaces labelled it "Risk score", so a perfect result looked like maximum danger in GitHub, GitLab, and Bitbucket pull-request comments, in the executive report and
vulkro cimarkdown, and in thevulkro-sfscan summary. Every surface now reads "Health score: N/100 (higher is safer)". The Salesforce compliance report had the same problem in reverse: it called a rising health score "worsened" and a falling one "improved". Both are corrected. No output field changed, only the labels and the trend direction.One score, everywhere. The desktop console's project header computed its own score with different weights than the engine, so the same scan showed one number in the header and a different one in the Overview tab a screen away, and neither matched the terminal. The header now reads the engine's score directly. The Salesforce console's score weights were reconciled the same way.
Cross-file and named-helper taint tracing was silently doing nothing in JavaScript / TypeScript and Python. Two defects in how functions were recognized meant exported functions were invisible to the JavaScript engine (and an exported function is exactly the cross-file shape), and in Python any function preceded by a blank line was read as having an empty body, which is nearly every real helper.
async defnever matched at all. Both are fixed, so data flowing through a named helper is now actually traced.Deeper Java data-flow tracing. Three real misses are closed: a plain, un-annotated parameter on a Spring handler is now recognized as request-controlled (Spring binds it from the request regardless); a tainted local variable now reaches a sink (
String q = "..." + name; executeQuery(q)), as does a value passed into a helper method; and a reassignment following a try-with-resources header is no longer swallowed.The malware install-hook check no longer fires on comments. The check no longer fires on risky words that appear only inside comments ("no eval, no curl | bash").
Closed evasions in the malicious-code checks. Obfuscation detection now catches decoded content fed to more execution sinks (
child_process,vm.runInThisContext, a string passed tosetTimeout, aWorker), decoding through an intermediate variable, and theconst e = evalalias. Credential exfiltration now covers WebSockets,sendBeacon, UDP, DNS-subdomain smuggling, more HTTP clients, and egress through a spawnedcurl/nc/wget, and recognizes a harvested value that reaches egress via an alias.Session-fixation checks now recognize two more real idioms: the Go gorilla/sessions
session.Values["user_id"] = ...shape and the Java servletsession.setAttribute("user_id", ...)shape.PDF export no longer points you at an abandoned tool. When PDF rendering failed, Vulkro told you to install
wkhtmltopdf: a binary archived in 2023, last released in 2020, carrying a critical SSRF advisory. Worse, when it was installed it silently produced a degraded report, because its rendering engine cannot draw the report's layout or score gauge. The error now points at--format htmlplus your browser's print-to-PDF, and the shell-out warns before it runs.--format pdfstill works.The command palette no longer jumps to a blank screen. Four entries in the desktop console's Cmd-K list pointed at views that no longer exist; they are retargeted or removed, and any unknown destination is now ignored rather than blanking the pane.
A failing deploy gate now says why. The project header shows which thresholds were breached (for example "2 critical (max 0)") instead of a bare "fail".
Piping scan or gate output no longer crashes.
vulkro gate | head,vulkro scan | grep -m1, or quitting a pager early used to abort the command with an internal error. It now behaves like any Unix tool when the reader closes early. Same fix invulkro-sf.Signing in is more resilient.
vulkro login(andvulkro-sf login) no longer gives up if a single status check to the account server times out or hiccups: it retries until you approve in the browser or the request window ends. A slow first response right after a deploy no longer interrupts the login.The desktop console no longer says "ALL CLEAR" when there are High findings. The green all-clear banner now appears only when there are zero critical and zero high findings; otherwise it reports the real count.
Correct install command in the CI and open-source guides. The copy-paste snippets pointed at a host that does not resolve, so the pipeline failed at the install step. They now use
https://dist.vulkro.com/install.sh.Clearer, more honest console copy. Removed an internal codename that leaked into two Settings and lineage screens; reworded a few "fully offline" lines to state precisely what stays on your machine; a Salesforce compliance tooltip no longer says a finding "proves" a control fails (findings are posture signals, not proof); and several error and empty states now tell you what to do next instead of only what went wrong.
Dead links and stale references fixed across the docs, the desktop consoles, and CLI help text (broken benchmark and docs links, error messages that named commands that do not exist, and help text pointing at an old path).
Updated
- Easier to find where to start.
vulkro --helpnow opens with a "Common workflows" block naming the handful of commands that matter (scan, serve, report, summary), andvulkro-sf --helpcarries the same kind of block for its own spine (scan, appexchange-report, org report, health, serve), instead of presenting dozens of ungrouped subcommands. - Clearer navigation in both desktop consoles. Duplicate and near-synonym tab labels are gone: two tabs both named "Trends" are now "Security trends" and "Quality trends", "Data flow" versus "Data flow map" is resolved, and the Salesforce console merged four duplicate tab pairs into one each (AppExchange readiness and checklist into one tab, Apex and live-org test coverage into one tab with a toggle), dropping Compliance from seven sub-tabs to five. Old deep-links still resolve. The client-facing executive report moved out of a developer-artefact submenu into its own "Report" group, and the two sidebar entries that both read "Portfolio" are now distinguishable.
- The Salesforce console surfaces ranking too. Reachability,
exploitability, and risk rank were computed by the engine but discarded before
reaching the Salesforce console; findings there now sort by risk within a
severity band and show the pills.
vulkro-sf scan --format tableno longer dead-ends at a bare count: it prints the top findings by risk and points at the console and the machine-readable output. - The Salesforce client report is on-brand. The AppExchange readiness report now reads "Vulkro for Salesforce" in the product's own colours instead of a lowercase, off-key variant.
Changes since 0.5.0, consolidated.
Added
- Shareable live-org security posture report.
vulkro-sf org reportproduces a one-command, client-ready report of an org's security posture (permissions, packages, session, MFA, sharing, trust status), as HTML or Markdown. The org is read through your own authenticatedsfCLI login; nothing about your org is sent to Vulkro. - Name the client on a readiness report.
vulkro-sf appexchange-reportgains--prepared-forand--prepared-byso a consultant or partner can hand over a client-branded AppExchange readiness deliverable. - New Apex and org checks, including a
@RemoteActionauthorization check, REST endpoint authorization, sharing access-level review, Visualforce action CSRF, and a backdoor / debug-bypass check for production Apex.
Fixed
- Dead links and wrong commands in the console. The benchmark trust link
and the in-product docs links now resolve; an error hint that named a command
that does not exist now points at
vulkro-sf update; and the AI-coding-agent setup card no longer points at a skill that does not apply to Salesforce. - Honest compliance and status copy. A rule tooltip no longer claims a finding "proves" a control fails (findings are posture signals, not proof), and live-org error banners now tell you what to do next.
Changed
- Clearer console navigation. Duplicate and near-synonym tabs were merged (AppExchange readiness and checklist into one, Apex and live-org test coverage into one, the data-flow views into one), and findings surface the engine's risk ranking.
Also ships Vulkro for Salesforce 0.7.0, and Vulkro for Salesforce 0.6.0, released in the same window.
Added
- Java and Spring are now scanned by the general scanner. Vulkro parses Java
source, recognizes a Maven or Gradle project as Spring Boot, and extracts Spring
MVC and JAX-RS endpoints along with their per-method authorization annotations
(
@PreAuthorize,@Secured,@RolesAllowed) and the application-level security filter chain. That lights up the language-agnostic API endpoint checks and dependency-CVE scanning of Maven manifests for Java projects. - Nineteen dedicated Java and Spring detectors. Data-flow-backed checks for
SQL injection, command injection, unsafe deserialization, XXE, path traversal,
open redirect, expression-language injection, LDAP and XPath injection, JNDI
lookup injection, unsafe reflection, script-engine evaluation, reflected
cross-site scripting, and CRLF injection, each tracing a Spring or servlet
request value into the sink. Plus configuration checks for weak cryptography,
a wildcard CORS origin with credentials, disabled CSRF or a blanket
permitAllin Spring Security, trust-all TLS (an accept-everything trust manager or hostname verifier), and cookies set without Secure or HttpOnly. - Nine new Go detectors. Disabled TLS verification, session cookies without
Secure or HttpOnly,
net/http/pproforexpvarexposed on a public mux, a wildcard CORS origin with credentials, internal error text written to the HTTP response, server-side request forgery from a request-derived URL, cross-site scripting from a raw request value ortext/templatemisuse, log injection from an unneutralized request value, and NoSQL injection through a$whereor a formatted Mongo filter. - Sixteen more detectors closing per-language gaps. For Java: insecure
randomness in a security context, server-side request forgery, NoSQL injection,
JWT handling (unsigned parse, alg-none, hardcoded key), regular-expression
denial of service, mass assignment, non-constant-time secret comparison,
world-readable file permissions, verbose error responses, exposed Spring
Actuator endpoints, and time-of-check-to-time-of-use races. For Go: LDAP and
XPath injection, request text parsed as a template, weak security headers
(
unsafe-inlineCSP,X-Frame-Options: ALLOWALL), and JavaScript evaluated through an embedded engine. - Six more JavaScript / TypeScript and Python detectors. Disabled TLS
verification in Node (
rejectUnauthorized: false,NODE_TLS_REJECT_UNAUTHORIZED=0, a no-opcheckServerIdentity), archive-entry path traversal, unserializing request input withnode-serialize/funcster/cryo, disabled SAML signature validation, PyMongo$whereor whole-request-as-filter NoSQL injection, and disabled TLS verification in Python (verify=False,ssl._create_unverified_context,CERT_NONE). - A cross-language trust-boundary check. A request-derived value written straight into a server-side session store, with no cast or validation in between, is now reported for Java, Python, JavaScript / TypeScript, and Go. A value that is parsed or cast first does not fire. This closed the last vulnerability class that had no coverage in any language.
- Java and Go now trace data across method calls. A request value handed from one method to another within a file is followed to its sink, so a controller that passes user input to a helper no longer breaks the chain.
- Findings are now ranked by risk, and the report calls out what to fix
first. Every finding carries a 0-100 risk score computed from severity,
exploitability, reachability, confidence, and known-exploited / EPSS data.
On an interactive terminal
vulkro scancloses with a "Fix these first" list of the top issues,--top Nsets the count (--top 0turns it off), the score and rank appear in JSON and SARIF, andvulkro explain --risk-modelprints the exact formula. A newvulkro summarycommand renders a shareable wrap-up (severity rollup, exploitable and known-exploited counts, coverage, and the top-ranked issues with a plain-language note on why each ranks where it does) as text, Markdown, or JSON. The ordering never changes what is detected. - A first scan now tells you what to do next. The first successful scan on a
machine prints a one-time note pointing at the console,
vulkro explain, and diff-only scanning; a scan with no findings suggests widening the scan instead of leaving you wondering whether anything ran. It never prints twice and stays out of piped, CI, and machine-readable output. - Expired suppressions now warn. An entry in
vulkro.tomlpast itsexpiresdate already stopped suppressing, but silently. The scan now prints which entries lapsed and when. Unparseable dates warn separately and keep suppressing, so a typo cannot silently unhide a triaged finding.
Changed
Java SQL injection and cross-site scripting catch more real sinks. The SQL check now covers the full JDBC and Spring execution family (
prepareCall,batchUpdate,queryForRowSet,queryForStream,executeBatch,addBatch) and recognizes the JDBC callable escape{call ...}, and the reflected cross-site scripting check now treatsgetWriter().printfand.formatas write sinks. Both measurably find more real issues without adding noise.A scan's progress log now stays out of machine-readable and CI output. The step lines a scan prints while it works (
▸/✓/·) are now suppressed when the output format is machine-readable, when stderr is not a terminal, or when running in CI, so piped and CI runs keep a clean stderr. An interactive run is unchanged, warnings and errors are never suppressed, and scan results are identical either way.
Fixed
- A Spring authorization annotation no longer covers the wrong handler. The
scanner's look-back could be confused by a path pattern such as
@DeleteMapping("/{id}")and let one method's@PreAuthorizebleed onto the next one, marking an unauthenticated route as protected and hiding a genuine missing-authorization finding. It now reads only each method's own annotation block. - Fewer false positives. The unguarded-mutating-route check was silently
being promoted to High and firing on route tables and deliberately-open
surfaces; it is now capped per file and graded correctly. Cross-site-scripting
findings where an attacker only selects which row or file comes back (rather
than authoring its contents) are demoted to a review lead instead of being
claimed as confirmed High.
jwt.decode(token, verify=False)is no longer misreported as disabled TLS certificate verification (it is signature verification, and the JWT detector already covers it). Go log-injection no longer fires on structured key-value loggers, Go verbose-error no longer fires on a bare sentinel constant, and Go insecure-cookie no longer fires on outboundreq.AddCookie. Java mass-assignment, NoSQL injection with a static$where, timing-comparison in test files, and a Spring@RequestParamwith a default value no longer produce spurious access-control findings. - Genuine Java findings no longer vanish at the strictest setting. An exact-line, in-method flow from a request value straight into a command execution or a redirect is now graded High, so it survives high-confidence filtering. Multi-hop and visibly-validated flows keep their previous grade.
Also ships Vulkro for Salesforce 0.5.1.
Added
- Prove a finding is real. A new
vulkro provecommand turns a finding into a runnable test harness you can execute to confirm the vulnerability before you fix it. It generates the harness in the finding's own language (JavaScript / TypeScript, Python, or Apex) for injection and access-control findings, and never runs anything itself: you run the test, watch it fail, apply the fix, and watch it pass. - Exploitability grading. Findings now carry an exploitability grade (exploitable, likely, or theoretical) so you can triage the ones that matter first. It appears in the JSON and SARIF output and in the desktop console.
- Cross-repo and AI-agent workflows. A new evidence-graph output and a
vulkro aggregatecommand connect findings, endpoints, and data flows across several repositories so an AI agent can reason about issues that span services. Two matching tools are exposed onvulkro mcp serve. - Cross-site scripting detection. The taint engine now traces request-controlled data reaching an HTML or raw-response sink, catching reflected and same-file stored cross-site scripting (CWE-79).
- Optional advisory triage. A new
vulkro triagecommand runs an optional, bring-your-own-key pass that asks an AI model to weigh in on the findings a deterministic scan cannot judge on intent alone (for example, whether an endpoint is meant to be public, or whether a returned field was meant to be exposed). It is off unless you run it, it never changes the scan's own results, and nothing is sent to any model unless you opt in with your own key.
Fixed
- Fewer false positives on safe database code. Prisma
$executeRaw/$queryRawtagged-template queries are parameterized by Prisma and are no longer flagged as SQL injection. The genuinely-unsafe$executeRawUnsafe/$queryRawUnsafestring forms still fire. - Correct CI exit code.
vulkro scannow exits non-zero when critical or high findings are present even when no quality gate is configured, so a CI job cannot silently pass a project that has critical issues. - Less access-control noise. The cross-route authorization check no longer fires on isolated public routes and framework-guarded handlers it could not attribute, so it flags a forgotten guard without the surrounding noise.
Also ships Vulkro for Salesforce 0.5.0.
Added
- Catch the security holes AI-built apps ship with. Three new checks,
run as part of a normal scan and grouped under a new "AI-code risk"
filter in the desktop console, target the ways quickly-built apps leak:
- Backend access control. Flags Firebase security rules left open to
anyone (
if true), a Supabaseservice_rolekey shipped to the browser, and client-side database access where Row Level Security was never turned on. This is the class of mistake behind the well-known "the whole database was readable" incidents. - Databases exposed in your infrastructure. Finds a database, bucket, or cache deployed public, with no password, or open to the whole internet in your Terraform, Docker Compose, or Kubernetes files, before it ever reaches production.
- Supply-chain and CI hardening. Flags risky install-time package scripts, over-privileged or unpinned GitHub Actions, and remote scripts piped straight into a shell, the pattern behind recent npm worm outbreaks.
- Backend access control. Flags Firebase security rules left open to
anyone (
Added
- Catch guest users reading data they should not. A new check flags an
@AuraEnabledmethod reachable by the unauthenticated Experience Cloud guest user that returns record data with no field-level-security enforcement, so a public site visitor could read every field and row the query returns. It complements the existing guest-write check.
Also ships Vulkro for Salesforce 0.4.0.
Added
- Slopsquatting / hallucinated-package check. A new
vulkro slopcheckcommand flags dependencies that look invented or impersonated: names an AI assistant likely hallucinated, typosquats of popular packages, known-malicious package names, and lookalike suffixes. Point it at a manifest (package.json, requirements.txt, and the other supported ecosystems) or paste a list of package names. It runs fully offline by default; an opt-in--onlineflag can confirm whether a name actually exists in the registry before you install it. - In-loop guard for AI coding agents. A new
vulkro guardcommand scans each file as your AI coding agent writes it and blocks on high or critical findings so the agent regenerates the code until it is clean.vulkro guard installwires it into Claude Code and Cursor (Windsurf best-effort); it runs locally on every file write and composes withvulkro mcp serve.
Changed
- AppExchange Security Review readiness is easier to read. The
asrrandpresubmitcommands now surface the overall verdict (READY / GAPS for the readiness report, pass / fail for the pre-submit checklist) prominently at the top of the output, so you can tell at a glance whether a package is ready to submit.
Also ships Vulkro for Salesforce 0.3.0.
Added
- Data flow map. A new
vulkro dataflowcommand, and a "Data flow map" tab in the desktop console, document how untrusted input moves through your codebase: every input (REST endpoints plus GraphQL / gRPC / WebSocket events), the functions and classes the data passes through, the sinks and data stores it reaches, the sanitisers that protect it, and the PII it carries. Each flow gets a 0-100 risk score and a protection-gap flag, and you can export the graph as Mermaid, DOT, JSON, or a Markdown document. Works for the general scanner and for Salesforce Apex. - Apply safe fixes automatically.
vulkro fixprints suggested fixes as unified diffs, andvulkro fix --writeapplies a strict, deterministic allowlist of rewrites in place. The desktop console shows the suggested fix and a copy-patch action right in the Findings drawer. - CRA readiness bundle.
vulkro cra-bundlebuilds a single zip for EU Cyber Resilience Act readiness: CycloneDX + SPDX SBOMs, an OpenVEX exploitability document, and a compliance evidence pack, with a self-contained readiness one-pager. - VEX output. New OpenVEX and CycloneDX VEX formats for stating which known vulnerabilities actually affect your product.
- More SBOM / CBOM formats. Added SPDX 3.0.1, CycloneDX 1.7, and CycloneDX
CBOM 1.7 output, plus real open-source license identifiers (SPDX
expressions) in SBOM output.
match-cvenow accepts SPDX SBOM input as well as CycloneDX, so you can generate the SBOM once and re-match it whenever the CVE bundle updates. - Git hook installer.
vulkro install-hookinstalls idempotent pre-commit and pre-push hooks that fail only on findings that are new versus HEAD, so pre-existing debt never blocks a commit. - SARIF code flows for Apex. Apex taint findings now carry the full source-to-sink code-flow steps, so SARIF viewers and editors can walk the path.
- Container app-layer SBOM. Container scanning now inventories the application dependencies installed inside an image, not just OS packages, with image-whiteout handling.
- Portfolio trends. A 90-day severity time series and burndown across all your projects, in both the CLI and the desktop console.
- New Salesforce checks. OmniStudio / Vlocity Integration Procedures and FlexCards, Salesforce Flow analysis parity, an offline maintainer- and ownership-change risk signal, and five additional detectors tuned on a 30-repo Salesforce benchmark.
- Desktop console. One unified Findings table across every source, with faceting, triage, bulk actions, and saved views; a dedicated Supply-chain section; built-in SBOM / VEX / CRA export; and the new Data flow map.
Fixed
- Reachability gate accuracy. The SCA reachability gate no longer mis-gates non-dependency findings; it now uses a forward-closure call-graph join, so only genuinely reachable vulnerable code is flagged.
- Data flow map detail. The detail view now shows the sink a flow reaches (it could previously report a flow as "not traced" even when the sink was known), large graphs render legibly instead of collapsing, "highlight by name" now lights the connected edges, and coverage notes are honest when a language's call graph could not be fully traced.
- Desktop console reliability. A broad pass fixed crashes on unusual scan data, incorrect counts and KPIs, stale selection and filter state when switching scans, and several controls that previously did nothing.
- Salesforce summary counts. Scan-summary severity buckets now reconcile with the findings list.
- Fewer false positives. A 30-repo benchmark precision pass recalibrated code-quality severities and suppressed dynamic-SOQL builder patterns that were firing on safe code.
Added
- Editor watch mode (VS Code). The Vulkro and Vulkro for Salesforce
language servers can now rescan your whole project as files change on
disk, including files you never opened and changes made by
git checkout, code generation, or another tool, and surface findings for every file - not just the editors you have open. It is off by default; turn it on with the "Toggle watch mode" command or thevulkro.watch/vulkro-sf.watchsetting.
Fixed
- No more duplicate findings in the editor. On macOS a finding under
a symlinked temp path (for example
/tmpresolving to/private/tmp) could be reported twice in the Problems pane. Findings are now mapped back to the path your editor opened, so each one appears once.
This release sharpens the review on real open-source Salesforce projects. We benchmarked the scanner against 30 popular OSS Salesforce repositories (official sample apps, the fflib enterprise-pattern libraries, Nebula Logger, trigger frameworks, NPSP, EDA, and more), graded every finding by hand, and used the results to both add new checks and stop a large amount of noise on legitimate code.
Added
- Unconditional mass-delete detection. Flags an
@AuraEnabled/@RestResourcemethod that deletes every row of an object via an unfiltered query (noWHERE/LIMIT) with no permission check. Any caller who can reach the endpoint wipes the table. A delete that is filtered or gated behind aCanTheUser.destroy/isDeletable()/ user-mode check stays silent. escapeSingleQuotesused in the wrong place.String.escapeSingleQuotes()only protects a value inside a quoted SOQL string. The review now flags a value that is escaped but dropped into an unquoted position - a field or object name, anORDER BY/LIMITclause, a numeric or date slot, or a SOSL clause - where it is still injectable. The correct quoted,LIKE, and SOSL-wildcard forms are recognized as safe.- Guest-reachable privileged actions. Correlates the site guest user's Apex
class grants with the methods on those classes and flags an
@AuraEnabledmethod that performs a privileged operation (DML, an async job, or a dynamic query) with no authorization gate - an action an unauthenticated visitor can trigger. - Wider injection coverage. Flow and Agentforce inputs (
@InvocableVariablefields and invocable parameters) and externally callableglobal/webservicemethod parameters are now treated as untrusted sources, so user input flowing from them into a dynamic query is caught as SOQL injection. - Install-time supply-chain check. Flags a
package.jsonpreinstall/install/postinstallscript, which runs arbitrary code onnpm installbefore any application code - a common supply-chain foothold.
Fixed
A precision pass removed a large class of false positives so the high-severity list reflects real risk. On the benchmark this cut high-severity findings by about 70 percent, almost all of it noise on safe code:
Test coverage and complexity are no longer high-severity security findings. Estimated Apex test-coverage and code-complexity metrics are code-quality signals, not vulnerabilities; they now report at low severity and the test-reference estimator recognizes framework classes exercised through their
*Testclasses.Safe dynamic SOQL stays quiet. A query built only from string literals and
:bindvariables, a query produced by an fflib / CRLPQueryFactorybuilder, andDatabase.executeBatch(which takes a batch job, not a query) no longer trip the injection and dynamic-execution checks.Secrets and PII heuristics tightened. Configuration keys named like a credential but holding a UI label, URL path, version string, or template variable no longer flag as secrets; placeholder credentials in
*Examples/*Sample/*Democlasses are excluded; and record etags and Salesforce-Id-shaped literals are no longer mistaken for credit-card numbers.Authorization helpers recognized. REST endpoints gated by the apex-common
CanTheUserhelper orWITH USER_MODEare no longer reported as unprotected, andMap.put(...)is no longer mistaken for a dynamic SObject field write.Right tool for the platform. Apex
@AuraEnabledmethods, which are governor-limited by the platform, no longer receive web-framework rate-limiter advice, and a 128-bit AES initialization vector is no longer reported as a weak encryption key.--include-pmdnow runs. The optional PMD-for-Apex pass shipped with a malformed bundled ruleset that PMD rejected at startup, sovulkro-sf scan --include-pmdquietly produced the same results as a scan without PMD. The ruleset is fixed and the PMD pass now runs and merges its findings. If you used--include-pmdon 0.2.1, re-run on 0.2.2 to get the PMD results.
Added
Cargo and Maven dependency scanning. Dependency-CVE matching now covers five ecosystems: npm, PyPI, Go, Cargo (
Cargo.toml+Cargo.lock), and Maven (pom.xml). Point Vulkro at a Rust or Java/JVM project and its known-vulnerable dependencies are flagged against the OSVcrates.io/Mavenadvisories, with the same severity, EPSS, and CISA-KEV enrichment as the other ecosystems. Versions resolve from the lockfile where present (Cargo.lock), including the workspace root; Maven reads the declared<dependencies>and resolves simple${property}versions.vulkro formats. A new command that prints the exact specification version Vulkro emits for each standards-based output format (SARIF 2.1.0, CycloneDX 1.6, SPDX 2.3, JUnit), as a table or as JSON for tooling. Handy for a compliance checklist that pins a spec version.More backends actually get scanned. A Go project that ships a frontend
package.jsonis no longer misread as a Node-only project: its Go backend is now scanned too. Projects that don't match a standard layout fall back to detecting the dominant language from the files present instead of guessing, so TypeScript / NestJS monorepos are no longer scanned as if they were Python.More web frameworks recognized for endpoint analysis. Route extraction now covers Flask
MethodViewand Django class-based views, Go's go-chi / httprouter /net/httpmethod patterns, NestJS decorator controllers, and Next.js App Router route handlers - so the authentication, IDOR, and CSRF checks that depend on knowing your routes now fire on these stacks.New detectors: Go SQL injection via
fmt.Sprintf-built queries, JWT algorithm-confusion, CSV/formula injection, unsafe URL schemes (javascript:/file:), non-constant-time secret comparison, a route that is missing the guard its sibling routes have, state-changing work on an HTTP GET, auth/session cookies withoutHttpOnly/Secure, entropy-reduced or non-crypto random used as a token, and guard logic whose name negates but whose body does not.
Fixed
- The same bug is no longer reported twice. When two detectors flag the same issue on the same line (for example a taint-tracked SQL injection that a pattern rule also matches), Vulkro now shows a single, strongest finding instead of a duplicate pair. Genuinely different issues on one line, and the same issue on different lines, are still reported separately.
- Repetitive style warnings collapse per file. A file with many of the same low-level hygiene issue (unhandled promise chains, PII columns without column encryption, loose-equality smells, unclosed resources) now surfaces one representative finding with a "+N more in this file" note instead of a wall of identical lines that buries the real bugs.
- Far fewer false alarms on common safe patterns:
- Rate-limit auditing no longer flags every single route. High-risk handlers (auth, payment, LLM, database) are still called out individually; the rest roll up into one per-file note.
- Public-by-design routes (
/login,/register,/health, OAuth callbacks, webhooks) are no longer reported as "missing authentication". - The cross-tenant IDOR rule stopped firing on HTTP route registration
(
app.get("/x", ...)) and on non-database getters (headers.get(...),searchParams.get(...)); it now fires only on real data-store reads. - The non-constant-time comparison rule skips test and
challenge-harness code, so assertions like a test's
password === '...'no longer read as a production secret check. - Loose-equality warnings skip benign emptiness and existence guards
(
x == '',x == null).
Updated
Output-format documentation. The
--formatreference now lists the precise spec version each emitter produces, generated from the binary itself so the docs can never drift from what is written.PII findings now reflect how sensitive the field actually is. An unencrypted Social Security or credit-card column is flagged High; a low-sensitivity identity field (username, display name, IP) drops to Low, instead of every PII field reading the same. The same calibration applies across ORM columns, log statements, analytics calls, and client storage. As a correctness fix, sensitive identifiers in logs or sent to a third-party analytics SDK are no longer under-reported.
Added
- Bruteforce sinks. A new
--bruteforce-sinksmode statically drives an adversarial payload corpus at every critical-surface call site (SQL, shell, HTTP, payment, LLM, file-write, deserialization, email) and flags the payloads that reach the sink without an effective recognized guard. Fully offline; no real API call is made. Covers payloads like negative amounts atstripe.charges.create, path traversal ats3.getObject, SQLi shapes atcursor.execute, prompt injection at LLM calls, prototype pollution atObject.assign, oversized strings atsendgrid.send. Python + JavaScript / TypeScript. About 30 payload classes; about 70 guard recognizers. Renders as a dedicated tab in the desktop console with a discover-then-run flow so the user picks which functions to test. - AI-code audit, executable from the UI. The desktop console's
AI-code tab now has a
Run AI-code auditbutton. One click walks the project for Claude / Copilot / Cursor / Aider / ChatGPT / generic AI-generated markers and renders the per-tool + per-file marker ratio inline. CLI flags--ai-code-segregationand--attest-reviewed --reviewer <name>continue to work and now also surface in the run-options dialog as checkboxes. - AI-tool risk delta. New report (
--ai-risk-delta) walks recent git history and reports, for each AI-authored commit, the per-file delta in (a) new sink categories newly reachable, (b) new third-party imports added, (c) function parameter surface growth. The "Cursor introduced 3 new SQL sinks inauth_service.py" headline. Markdown and JSON output. - LLM defense pack. Three rule packs for the AI-era surface:
- Token-budget audit (TOK-001..005): no
max_tokenscap, system prompt larger than 40 KB, oversizedtools[], LLM call inside a hot loop, system prompt routed via user message (exfiltratable via jailbreak). - Prompt-leak fingerprint (LEAK-001..003): prompt variables flowing into error responses, log statements, or telemetry sinks (Sentry / Datadog / OpenTelemetry).
- Static rate-limit auditor (RATE-001..004): per-endpoint missing limiter (severity bumps to High when the body touches a payment / LLM / SQL sink), library imported but not wired, missing global default on Express.
- Token-budget audit (TOK-001..005): no
- TOCTOU race synthesizer. Four check-then-act race detectors beyond the existing file-race surface: SELECT-then-UPDATE without row lock, balance-check-then-debit without atomic decrement (the double-spend shape, Critical), queue peek-then-claim, ACL-check-then-act on stale ID.
- Attack-path graph. For every detected entry point, walks the call graph forward and emits one attack path per (entry, reachable critical sink) pair. Each path highlights hops whose function names lack a standard auth marker. Surfaces the kill chain.
- Reverse symbolic execution. Pick any finding and trace it
back to every entry point that reaches it. New
Trace backbutton on each finding card in the desktop console. - PR-speed scan. New
--diff-only <ref>mode restricts the scan input to files that differ vs the supplied git ref. On a 1000-file repo where the PR changes 6 files, the scan walks 6 files. Composes with--gate-vs. - Run-options dialog with sticky defaults. The desktop console's Run-analysis button now shows a checkbox dialog covering all the optional scan modes (validate secrets, AI-code segregation, attest reviewer, bruteforce sinks). Choices persist per-project so the dialog opens with the user's last-chosen toggles already ticked.
- Coverage hints in the Security tab. A banner above the Security tab group lists which optional scan modes were NOT in the current scan. Each missing mode is a chip that deep-links into the Run-options dialog with that exact toggle pre-checked.
Added
- Deeper coverage in six more languages. Java and Kotlin: deserialization
gadget chains (
ObjectInputStreamon a request body or a Base64 request payload), JNDI lookup on tainted input and the${jndi:ldap://...}shape inside logger calls (the Log4Shell class, even when log4j is patched), Spring Expression Language injection in@PreAuthorize/@PostAuthorize/@Query/@Valueand in direct parser calls, and Hibernate raw-SQL concatenation. PHP:unserializeon a superglobal without'allowed_classes' => false, variable clobbering throughextractandparse_str, Laravel Eloquent mass assignment from$request->all(), and double-extension upload bypass. Ruby and Rails:Marshal.loadandYAML.unsafe_loadon request data, StrongParameters permit-all, andskip_before_action :authenticate_user!on admin, internal, or billing controllers. .NET:BinaryFormatter/SoapFormatter/NetDataContractSerializerdeserialization, taintedType.GetTypefollowed by.Invoke(,Process.Startwith a concatenated shell string, and hardcodedmachineKeyvalidation and decryption keys in*.config. Python:random.*used to mint tokens, passwords, salts, or reset codes (with a pointer tosecrets.*), and@csrf_exempton a state-changing Django view with no authentication check. Go: weak crypto (MD5 / SHA-1 used for signing, RC4, DES), JWT algorithm confusion and short hardcoded HMAC secrets, zip-slip inarchive/zipandarchive/tarextractors, and insecure gRPC transport. - Wider cross-language coverage. Server-side template injection for Twig,
Velocity, and FreeMarker. JWT
kidheader injection, where a parsedkidreaches a file path or a SQL string with no allowlist check (Python, Node / TypeScript, Java, PHP). GraphQL introspection left on in production across Apollo Server, graphene-django, and Strawberry FastAPI. Sensitive routes with no auth-guard decorator in FastAPI, Flask, Django, and Express. World-readable secret files (.env,id_rsa,*.pem,keystore.jks,client_secret*). TOCTOU file races, where an access check is followed by an open withoutO_EXCLorO_NOFOLLOW. - AWS IAM posture auditing. Vulkro now reads IAM policy JSON and flags
god-mode policies (
Action: "*"plusResource: "*"plus no condition), wildcard actions with no condition, sensitive actions such asiam:*,s3:DeleteBucket,kms:Decrypt, andsecretsmanager:GetSecretValueoverResource: "*", andPrincipal: "*"in resource policies. - Bundled threat-intel catalogues. Two catalogues ship with the scanner and cross-walk with your dependency scan: compromised npm package versions and compromised Docker image tags. A finding fires when a project pins a known-bad version. A new console tab lists every loaded bundle with its name, version, entry count, last-updated timestamp, and hash prefix.
- Cross-method taint tracing. The taint engine is now function-aware within a source file: it follows tainted parameters across method boundaries, return-value taint, and field assignments. Every taint-driven detector gets more precise across Apex, Python, Node / TypeScript, Java, Go, Ruby, PHP, and .NET.
vulkro mcp serve. An MCP server so Claude Code and other MCP clients can drive Vulkro scans conversationally, plus a bundled Claude Code skill exposing ten slash commands.vulkro gate --base <ref>andvulkro init --pre-commit. Gate now diffs findings against any base ref, so a commit fails only on findings that are new versus the base and pre-existing debt never blocks a merge. The pre-commit installer scaffolds a Husky hook (or a plain git hook when Husky is absent) that runs the gate on staged files, with a documented single-commit bypass.vulkro scan --post-to. Sends scan summaries to Slack, Microsoft Teams, Jira (creates an issue), or PagerDuty (raises an incident) when at least one critical or high finding is reported. Channels can be combined;--helpdocuments the payload and rate-limit policy per provider.vulkro portfolio dashboard <input-dir>. Walks a directory of per-project scan JSON files and renders a multi-project rollup as HTML or JSON: a per-project risk table with severity counts and risk score, cross-project finding groups (the same rule and file appearing in two or more projects), and a severity grid showing which projects miss a rule entirely. A matching Portfolio tab in the desktop console reads the same shape.vulkro bench. Runs your fixture corpus and asserts that each positive fixture fires the matching detector and each negative fixture stays silent. Known limitation in this release: single-file fixtures with no project manifest can be misclassified by project detection.- Suppressions modal in the desktop console. A dialog to inspect and delete per-finding suppressions, scoped to the current file, reachable from any finding row.
- Editor experience: hover, quick fixes, and debounce. Hover a finding in
any LSP-capable editor to see its rule ID, severity, message, and
remediation. Two quick fixes per finding: Suppress (writes a
vulkro:disable-next-linedirective) and Explain (opens the rule explainer). Re-scan on typing is debounced, configurable between 50 and 5000 ms and defaulting to 300 ms. The VS Code extension moves to 0.2.0 to pick these up. - JetBrains IDE support. A sideloadable IntelliJ Platform plugin reuses
the
vulkro lspserver to stream diagnostics into IntelliJ IDEA, PyCharm, WebStorm, GoLand, Rider, and RubyMine. Settings live under Preferences > Tools > Vulkro. - A GitHub App you can self-host. A template app that listens for
pull-request events, runs
vulkro gatein a sandbox against the head commit, and posts findings as inline PR review comments. It ships with a hardening checklist (sandbox isolation, tenant boundary, secrets vault, rate limiting, audit log, abuse-protection backoff) to work through before you run it in production. - Live Salesforce org auditing. Connect a Salesforce org through your own
sfCLI (read-only, metadata only: the scanner never issues SOQL against your business records, and the OAuth token stays in the official CLI) and audit the org's posture alongside your code:- Org posture: SecuritySettings element by element (session timeout, lock-to-login-IP, clickjack protection, CSRF, password policy), each with the current value, the recommended value, and the fix.
- Identity: over-privileged profiles and permission sets (View All / Modify All) plus dormant admin accounts.
- Connected Apps: OAuth posture for installed Connected Apps, anchored to the 2025-26 token-sprawl breach class.
- Installed packages: managed and unmanaged packages with version and expiry warnings.
- Agentforce: agent, plugin, and action inventory plus the ForcedLeak (CVSS 9.4) class-bypass check.
- PII map: which standard objects hold personal data, with GDPR and HIPAA relevance, read from the org's real schema.
- Org audits are point-in-time snapshots you can diff. Capture the org's posture, identity, packages, and PII state at a moment in time; a later audit shows what is new and what was resolved, so you can show remediation without re-reading code.
- A "source plus live org" scan stores the org snapshot with the scan. The console's org views open instantly and always match the scan you are looking at, instead of re-querying the org on every refresh.
- AppExchange Security Review readiness report. Group every finding by the published reviewer-checklist sections and export an HTML report you can hand to your reviewer.
Fixed
- Salesforce org posture now reads correctly. The SecuritySettings audit looked for the wrong retrieved filename and returned nothing against real orgs; it now reads the settings file and reports the session, clickjack, CSRF, and password findings.
- "Source plus live org" scans now target the connected org. The desktop console sent a placeholder org name instead of the connected org's alias, so the org half of a combined scan came back empty. It now uses the real connected org.
- Honest empty states everywhere in the Salesforce console. No tab shows a placeholder or synthesised number: every tab renders real scan or org data, or a clear "nothing here yet" state.
Added
Detect dynamic Apex dispatch that bypasses sharing analysis. A new finding catches Apex methods that call
Type.forName(<runtime string>).newInstance()and then invoke a method on the resulting object when no class anywhere in your workspace declareswith sharingorinherited sharing. Salesforce's Graph Engine cannot resolve a runtime dispatch like this, so the downstream class's DML is invisible to the platform scanner. The check stays silent onType.forName('LiteralName')(Graph Engine can resolve that) and on any workspace where at least one class is declaredwith sharingat the class level.Catch Apex constructs Salesforce's Graph Engine does not cover. Three new findings flag patterns the Graph Engine documents as out of scope:
- Apex triggers that read
Trigger.new/Trigger.newMapand issue DML without a CRUD or FLS check upstream. - Property chains of depth three or more (
req.params.lead.email) flowing into a DML or SOQL sink on the same line. - Chained method calls (
Foo.bar().baz()) feeding a DML sink.
- Apex triggers that read
Catch the ForcedLeak attack chain on Agentforce (CVSS 9.4). Four new findings cover the steps Noma Security demonstrated in late 2026:
- PII-bearing free-text fields (
Lead.Description,Case.Description,Contact.Description,Account.Description, and siblings) read by an@InvocableMethod/@AuraEnabledaction and returned to the agent context with no sanitization. - Trusted URL entries that match Salesforce's removed-by-default
wildcard set (
*.salesforce.com,*.force.com,*.cloudforce.com,*.visualforce.com,*.lightning.com,*.documentforce.com,*.salesforceliveagent.com,*.my.salesforce.com). - Trusted URLs granting
img-src/connect-src/frame-src/script-src/style-src/media-srcto a host that is not obviously org-owned. - Apex classes that expose an agent-action surface (any
@InvocableMethod/@AuraEnabledmethod) over a PII-bearing sObject and carry nowith sharing/without sharingmodifier.
- PII-bearing free-text fields (
Catch Agentforce planner, topic, and action misconfiguration. Five new findings cover the Salesforce-side metadata mistakes that produce wide-open agent actions, in line with the rules the major third-party reviewers ship:
- Planners that mention a PII-bearing standard object
(
Contact,Lead,Case,Account) but wire no customer- verification action. - Planners that reference the standard customer-verification action
but are missing the
attributeMappingsblock for the action's required outputs (isVerified,customerId). - Topics whose
<masterLabel>,<description>, or<scope>word counts fall below the documented minimums (5 / 15 / 15). - Planners that exceed the documented per-planner topic threshold (default 15), where routing accuracy starts to degrade.
- Agents, topics, or actions in production metadata that have no
paired
*.aiTestCase-meta.xml,*.botTest-meta.xml, or*.agentTest-meta.xmltest file.
- Planners that mention a PII-bearing standard object
(
Cross-method auth / DML / callout tracking now follows your helpers. Four existing findings that previously read only the method they fired in now walk the workspace-wide call graph:
- The REST endpoint auth check accepts an auth marker
(
UserInfo,Auth.,FeatureManagement,PermissionSetAssignment,getSessionId(,checkPermission() in any reachable helper, so delegating auth toAccessControlService.assertCanRead()no longer mis-fires. - Partial-success DML (
Database.insert(rows, false)) accepts a SaveResult inspection (.getErrors(),.isSuccess(), or a helper whose name starts withprocessSaveResults/handleSaveResults/assertSavedOk) anywhere in the reachable graph. - HTTP callout retry detection accepts a
for/while/catchblock or a helper whose name matcheswithRetry/executeWithRetry/retryUntil/retryWrapperanywhere in the reachable graph. - Invocable validation accepts a guard anywhere in the call graph rather than only in the entry method.
- The REST endpoint auth check accepts an auth marker
(
Detection-rule updates ship without a binary release. The Agentforce knowledge catalog (standard verification actions, topic word thresholds, excessive-topic limit, test-file patterns, PII free-text fields, Trusted URL wildcard blocklist) refreshes from a signed JSON bundle hosted at
dist.vulkro.com/sf/rules/on a rolling 24-hour cadence. A bundle whose signature does not match the ed25519 public key baked into the binary is rejected and the scan uses the seed catalog that shipped with the binary. The same path honoursVULKRO_SF_OFFLINE=1(force the seed),VULKRO_SF_RULES_BUNDLE_OVERRIDE=<path>(sideload a local JSON),VULKRO_SF_RULES_BUNDLE_URL=<base>(point at a mirror), and a newVULKRO_SF_RULES_BUNDLE_MIN_REFRESH_SECS=<n>override on the 24-hour cadence (clamped between 1 minute and 7 days).Settings panel matches the general vulkro scanner. The desktop console's Settings view now has the same seven sections in the same order: Scanner, History, Defaults, Database & updates, AI coding agent, License, About. A left-rail navigator scrolls between them. An operator who knows one binary can find every control in the other without re-learning the layout.
License panel shows the machine ID prominently with a one-click Copy button. Licenses are bound to this machine ID; the panel surfaces it as readable hex with the same verbiage the CLI uses for
vulkro-sf machine-id. The current tier (Free / Trial / Pro-monthly / Pro-annual / Enterprise), days remaining before lapse, and the included-feature matrix all live in the same panel.One-click pricing-page deep link. The License panel's "Buy Vulkro for Salesforce Pro" button opens
vulkro.com/pricing?product=sf&machine_id=<hex>in a new tab so the Paddle checkout has your machine ID pre-filled. After payment your signed.licfile arrives by email; drop it into the same panel's file picker to activate.In-console
.licactivation. The Settings > License > "Activate a license file" picker POSTs your.licblob to the scanner. The Ed25519 signature and machine binding are verified before the activation lands; the panel surfaces a clear pass or fail inline.vulkro-sf machine-idsubcommand. Prints this machine's 32-hex fingerprint to stdout. The same value the desktop console's Settings > License panel shows under "This machine's ID" and the same valuevulkro.com/pricing?product=sf&machine_id=<hex>reads. Documented in the License panel hint but missing from the v0.1.4 CLI; v0.1.5 adds the binding.
Fixed
- Connected Salesforce orgs no longer show a phantom "Unreachable"
badge. An internal change in the 0.1.3 development cycle had
forced the Salesforce CLI to use its file-backed credential store
instead of macOS Keychain whenever vulkro-sf invoked it. On a
machine where you authenticate the CLI via Keychain (the macOS
default), every Salesforce CLI call returned
AuthDecryptErrorand the Orgs tab rendered "Unreachable" on a perfectly healthy org. The silencer is now scoped to the one CLI probe that needs it, so every other Salesforce CLI invocation inherits your normal credential store unchanged. - The Projects landing now shows last-scan times for every project that has scan history. The card used to read "Never scanned" on every project, including projects with dozens of completed scans in the sidebar. You now see the timestamp of the most recent scan and a colour-coded severity badge.
- The Access > Effective Perms tab no longer replays a long loading screen every time you click into it. Effective-perms checks against a large org (thousands of users) take 25-30 seconds because they pull cumulative permission state for every active user. The tab now renders the prior result instantly when you return to it and shows a small "Refreshing posture against the live org..." indicator while a fresh check runs in the background. Click "Run audit / Refresh" in the org header to force a clean re-run that evicts every per-tab cache for that org.
- The Surface > Trust Status tab no longer reads as a broken
install. When the optional
trust.salesforce.comintel feed has not been wired up, the tab now shows a neutral "Intel feed not configured" pill and copy that leads with "Nothing is broken: the Orgs tab, Posture, Access, and Inventory all work without it." Configure the feed by exportingVULKRO_SF_INTEL_TRUST_BUNDLE_OVERRIDE=<path>or runningvulkro-sf intel refreshwhen a packaged bundle is available. - The new dynamic-dispatch finding no longer mis-fires on idiomatic
Apex. A class declared
public with sharing class Foo { ... }covers every method in the scope, even when the methods themselves don't repeat the marker. The workspace-wide sharing check now recognises class-levelwith sharingandinherited sharingdeclarations and grants coverage.without sharing classis intentionally excluded (that is exactly the unsafe shape this check is built to catch).
Fixed
- Apex method findings now point at the signature line instead of the
annotation above it. Findings on annotated Apex methods (e.g.
@AuraEnabledfollowed by apublic static String foo(...)) previously reported the annotation's line number instead of the signature's. The "Show code" preview in the desktop console then highlighted@AuraEnabledwhen you expected the method signature. Affects cognitive-complexity findings, dead-code findings, and any rule that names a method as its location. - Desktop console: the macOS Salesforce CLI "MissingKeychain" error
now shows you the fix instead of the raw error. When the
Salesforce CLI cannot reach the macOS Keychain (running under
launchd, an SSH session, a sandboxed wrapper, or any context where
Keychain access is denied), the console now surfaces:
"Switch the CLI to a file-based store and re-authenticate. Run:
export SF_USE_GENERIC_UNIX_KEYCHAIN=true, thensf org login web --alias <your-alias>, then restartvulkro-sf servefrom the same shell."
Added
- "Show code" preview on every code-reference tab in the desktop
console. Click "Show code" on any finding card and you see the
actual source: 5 lines above the issue, the highlighted target
line, and 5 lines below. A "Show more context" button cycles out
to 20 then 50 lines either side. Available on:
- Code > Findings
- Security > Issues (existing surface, now uses the same component)
- Security > Secrets / Crud-FLS / Taint analysis / Git history
- Quality > Anti-patterns
- "Getting started" welcome card on first run. A brand-new install (no projects added, no orgs authenticated) now opens to a Welcome card with two primary buttons: "Add an SFDX project" and "Connect a Salesforce org". The empty zero-tiles dashboard is hidden until you have something to look at.
- Server-side validation when adding a project. Pointing the
desktop console at a folder that isn't a real SFDX project now
returns an actionable error:
"the path is not an SFDX project:
<path>/sfdx-project.jsonis missing. Try: cd to your project root or runsf project generate --name <name>to create one." - Settings > Data location now shows the real on-disk path of the local SQLite store, its current size, and how recently it was modified, so you can confirm backups capture the right file.
- Live progress stream auto-reconnects with exponential backoff
if
vulkro-sf serverestarts mid-scan. When all retries fail the console shows a "Live progress stream disconnected" banner so you know to refresh. - Multi-tab leader election. Open the desktop console in two browser tabs against the same project and only one tab subscribes to the live-progress stream; the second tab follows along via a cross-tab broadcast channel. No more duplicate fetches.
Changed
- Desktop console information architecture cleanup. The left
sidebar dropped from 9 top-level buttons to 4 (Portfolio, Orgs,
Projects, Settings). Surfaces that needed a project or org context
moved into the right place:
- Compliance is now a tab inside the project shell (it needs a scan to render a framework).
- Intel's per-org cross-walk moved into each Org view; the catalog listings moved to Settings.
- Rules (Custom Rule SDK / Marketplace / Plugin Packs) moved to Settings.
- Engagements (multi-org consultancy dashboard) IS the Orgs landing page now.
- Per-org view restructured from 22 flat tabs to 6 grouped sections (Overview / Access / Surface / Inventory / Intel / Activity). The previous flat tab strip overflowed on smaller monitors.
- Better error messages across the console. 11 generic "Backend error: ..." banners were rewritten with concrete next-step hints (e.g. "Trends require at least two completed scans. Run another scan and come back.", "Verify the project root is a git repository with at least one commit.").
- Triage Queue assignee field is now free text. Previously the Assignee dropdown was populated from a small fixed list of fake names. It's now a text input with autocomplete suggestions pulled from assignees you've actually typed before on this machine, across every project.
- Triage Queue handles large finding lists faster. Lists over 200 findings render the first 200 and offer a "Load next 200" button instead of painting everything at once.
- Default LOC preview context is 5 lines either side. Bumped from 4 either side based on real usage in dataflow and SOQLi reviews.
Performance
- Faster page loads. Per-scan read endpoints (findings, pillars, connected apps, PII map, anti-patterns, AppExchange readiness) now carry HTTP cache headers so rapid back-and-forth navigation in the console no longer re-hits the server.
Added
41 new Well-Architected anti-pattern detectors (AP-015 through AP-058, skipping the deliberately-unallocated AP-056). Catalog grows from 14 to 55. New surfaces: PII / Agentforce safety (AP-015..022), failure mitigation + dependency hygiene (AP-023..029), data modeling (AP-030..033), application-complexity live-org signals (AP-034..037), Validation Rule rebuilds + REST / callout hygiene (AP-038..045), Flow execution context, Custom Metadata / Custom Setting secrets, GDPR right-to-erasure gap (AP-046..049), LWC / Aura JavaScript security (AP-050..052), Visualforce page / component security (AP-053..055), live-org Permission Set Group over-privilege (AP-057), live-org Apex test-coverage gate (AP-058). Per-detector descriptions are on the Anti-patterns CLI doc page; SOC 2 / HIPAA / PCI / GDPR mappings on the Compliance pages.
New file walkers. The scanner now walks Salesforce metadata XML (object / flow / GenAiFunction / agent / prompt template / installed-package / external-service registration), LWC and Aura JavaScript controllers (
.jsfiles underlwc/andaura/), and Visualforce pages and components (.pageand.component). The Apex.cls/.triggerwalker is unchanged.Multi-namespace project support. The scanner now reads every namespace declared in
sfdx-project.json(top-levelnamespace,packageAliaseskeys,packageDirectories[].package) and treats them all as the project's own namespace. New--exclude-namespace=npe01,npe03,...CLI flag for legacy managed-package projects (NPSP-style historical prefixes).Suppress comments.
// vulkro: ignore AP-029(orAP-029, AP-013, or*) on a finding's line or the line above drops it from the output. Works in both Apex and metadata-XML files..vulkro-sf.ymlproject config. Optional file at the scan root withdisable: [...]andextra_excluded_namespaces: [...].SARIF v2.1.0 output (
--format=sarif). Standard format for the GitHub Security tab, SonarQube, Codacy.JUnit XML output (
--format=junit). One<testcase>per finding so CI runners (GitHub Actions, GitLab CI, Jenkins) surface findings as failed tests.HIPAA + PCI compliance profiles.
--compliance=hipaaadds the 18 HIPAA Safe Harbor identifier classes;--compliance=pciadds PCI-DSS card-data identifiers (PAN, CVV, expiry, cardholder name, magnetic-stripe). Both flags stack (--compliance=hipaa,pci).Incremental scan cache. Per-file findings cached by SHA-256
- detector version + flags at
~/.vulkro/sf-antipattern-cache.json. Subsequent scans only re-run detectors on changed files.--no-cacheand--cache-dirflags for CI / forced re-scan.
- detector version + flags at
Parallel scanning (rayon). Per-file work runs across CPU cores. Roughly half wall-time on an 8-core machine for managed-package-scale codebases (~50k files).
Tree-sitter AST migration begins. 8 detectors now run against the parsed Apex AST when the file parses, falling back to the line-scanner when it doesn't: AP-001 (SOQL in loop), AP-002 (DML in loop), AP-004 (Empty catch), AP-008 (Schema describe in loop), AP-013 (SOQL without WHERE/LIMIT), AP-014 (@isTest SeeAllData), AP-018 (Agentforce action without validation), AP-023 (Catch-and- rethrow), AP-024 (Partial-success DML). Multi-line SOQL / DML now caught for AP-001 / AP-013 / AP-024 (was a documented limitation). The other 20 per-file Apex detectors still run on the line-scanner.
Cross-method taint analysis (AP-018). When an
@InvocableMethodlacks validation in its own body, the detector now asks the call graph whether any helper the method invokes validates the input first. Real cross-method taint; AP-018 suppresses correctly when validation lives in a helper.SOC 2 Trust Services Criteria mapping. Every detector (AP-001..AP-058) is mapped to CC6.1 / CC6.6 / CC7.2 / CC8.1 on the Compliance SOC 2 page.
Live org coverage UI. New
Quality -> Live org coveragesub tab in the desktop console. ReadsOrgSnapshot.coverage(populated by a new Tooling API query againstApexCodeCoverageAggregate), renders per-class coverage with the 75% AppExchange gate.Claude Code Skill. Install the skill at
.claude/skills/vulkro-sf/to get slash commands inside Claude Code:/vulkro-sf scanto scan your project (returns a compact summary instead of dumping 30k tokens into the chat),/vulkro-sf show <id>to see one finding,/vulkro-sf diff <baseline>to see only what changed,/vulkro-sf grep <pattern>to filter,/vulkro-sf suppress <id>to add a// vulkro:disableannotation in place,/vulkro-sf fix <id>to apply an auto-safe fix when available,/vulkro-sf explain <id>for the rule rationale,/vulkro-sf rules list,/vulkro-sf intel status. Scans cache by working-tree hash so re-runs are instant. Built so Claude Code users can run a Salesforce security review without ever leaving the chat.New live-org audit subcommands. Beyond
org status/org perms/org packages, the CLI now reaches nine more live posture surfaces:org login-history: flags admin logins from untrusted IPs, failed-login bursts, and admin Application-type logins that bypass MFA.org audit-trail: recent privilege escalations from SetupAuditTrail and out-of-hours Setup edits.org health-check: pulls the Salesforce-computed Health Check score and lists every risk category with a non-zero risk count.org effective-perms: the cumulative permissions users actually hold right now (per-object ModifyAllRecords / ViewAllRecords, field-level Read/Edit on PII-shaped fields, and which active users currently sit on a broad-system-perm permset).org connected-apps: live Connected App OAuth: Full-scope apps with active tokens, apps with only stale tokens, cleartext callback URLs.org named-credentials: live Named Credential audit: cleartext HTTP endpoints, Anonymous principals paired with an AuthProvider (baked-in credential), merge-fields-in-body enabled.org guest-live: Experience Cloud guest exposure: self-registration on, default guest access on a Site, Chatter guests.org domain: MyDomain SSL: domains that still allow HTTP, mixed Standard + CustomDomain rollouts.org mail: OrgWideEmailAddress with "all profiles can send-as", EmailServicesAddress on non-salesforce.comdomains. All output the same exit-code contract (0 clean, 1 findings, 2 error) and the same--format table|json|sarifflags asorg perms.
SecuritySettings rules wired into
org perms. The session-timeout, password-policy, clickjacking, and CSRF-on-POST rules (SF-SESSION-001/-002, SF-PWD-001/-002/-003, SF-CLICKJACK-001, SF-CSRF-001) ran only through the desktop console before; they now run on the CLI too.Per-object record-permission detector. A profile or permission set that grants
<modifyAllRecords>true</>or<viewAllRecords>true</>on a specific object is the per-object equivalent of ModifyAllData / ViewAllData and was not inspected before. Three sub-rules: modify-all-records (High), view-all-records (Medium), view-all-records on a PII-shaped object (escalates to High). Skips Salesforce-delivered standard profiles and the standard System Administrator file.PII custom-field cleartext detector. Flags
objects/<Obj>/fields/<Field>.field-meta.xmlfiles whose developer name matches a PII shape (SSN, DOB, bank account, government ID, medical record) but whose<type>is plain Text / TextArea / Number / Phone / Email / Url and which carry no<encrypted>flag and are not formula-derived.Custom-object sharing-model correlator. Reads
<sharingModel>on a custom object and flags non-Private values (Read, ReadWrite, ReadWriteTransfer, FullAccess, ControlledByParent / Campaign / LeadOrContact) when the object also carries PII-shaped fields. Skips__mdt,__b,__e, and standard objects.Brute-force-able guest entry detector. Flags
@AuraEnabledmethods onwithout sharingApex classes that query a PII column and gate the response on a low-entropy equality check (last-4 SSN, right-N, endsWith) with no rate limit or lockout. The 10,000-attempt search space is iterable by a guest caller until a match is found.@AuraEnabledmutator authorization detector. Flags@AuraEnabledApex methods that DML an SObject without a visible authorization gate (FeatureManagement.checkPermission, aPermissionSetAssignmentlookup, a profile-name comparison,WITH SECURITY_ENFORCED,as usermode, or a customrequire*Permission/assertRolehelper).LLM / Agentforce detector subpack (5 sub-rules). Closes the Apex callout surface for LLM integrations:
- User input flowing into an LLM prompt body without escaping (CWE-1427).
- Untyped LLM response deserialization (LLM output is non-deterministic; reflecting it downstream is a content-injection vector).
- LLM callout missing
max_tokensand / orsetTimeout(runaway prompt blows the CPU governor and racks up vendor cost). - Single LLM Named Credential referenced from three or more classes (rotation hazard).
- LLM model / endpoint configuration stored in an unprotected
__mdtrecord (anyone with Customize Application can redirect every LLM call without a deployment).
Platform Event publish-authorization detector. Flags
EventBus.publishcalls reachable without an authorization gate from an@AuraEnabledor REST entry (High), from a broadly-callable non-entry method (Medium), or inside a trigger body (Low, informational).Async-class sharing-context detector. Queueable / Batchable / Schedulable /
@futureclasses with nowith sharingmodifier (Apex default in async dispatch is system mode) get flagged when the body references a PII-shaped sObject; severity escalates one notch when the entry method DMLs PII without a priorWITH SECURITY_ENFORCED.Custom Metadata reference-tamper detector. Flags
*.md-meta.xmlrecords that are<protected>false</protected>AND store a string value referencing a sensitive Salesforce resource (Named Credential, Connected App, RemoteSite, AuthProvider, External Credential, endpoint URL, OAuth callback). Anyone with Customize Application can rewrite the reference and redirect every Apex callout without a deployment.Custom Setting credential detector. Walks
objects/<Obj>__c/<Obj>__c.object-meta.xmlfor<customSettingsType>and flags credential-shaped field names (API_Key,Secret,Token,Password,Private_Key,Connection_String, etc.) on the sibling fields. Severity scales by setting shape: Hierarchy + Public is Critical, Hierarchy + Protected is High, List is Medium.Static-resource SCA. Flags vulnerable copies of jQuery (< 3.5.0 prototype pollution / XSS), Lodash (< 4.17.21), CryptoJS (< 4.2.0 weak PBKDF2 default), Moment.js (deprecated), AngularJS 1.x (EOL 2022), and Bootstrap (< 4.3.1 XSS) shipped via
staticresources/.lwc:dom="manual"detector. Flags LWC templates that use themanualdirective: the host element bypasses Lightning Web Security sandboxing, so anyinnerHTMLset on it from the component's JS renders arbitrary HTML (including<script>) in the user's tab DOM.// vulkro:disablesource-annotation suppression. Inline annotations now let you mark a verified-OK line so future scans skip the finding:// vulkro:disable next-line <rule-id>,// vulkro:disable-line <rule-id>,// vulkro:disable-file <rule-id>, and the<!-- ... -->form for XML / HTML /*-meta.xmlfiles. The CLI reports how many findings were suppressed;--no-suppress(orVULKRO_NO_SUPPRESS=1) short-circuits the filter for CI lanes that need the full picture.AP-004 (Empty catch) escalation. Empty
catchblocks now escalate to High severity when the surroundingtrybody performs DML, an HTTP callout, anEventBus.publish, a queued async job, or aMessaging.sendEmail. Silent mutation failure is data corruption, not a Medium concern.
Changed
@isTestclasses no longer trigger PII / DML / hardcoded-ID / god-method findings. Files named*Test.cls/*_Test.clsor carrying an@isTestannotation at the top manufacture hardcoded fixtures and are not user-facing; they no longer trip the Apex-source detectors that previously fired on test bodies. Cuts ~9 false positives per typical SFDX project.Database.query/countQuerybind-only paths downgraded to Low. When the analyzed dynamic SOQL containsWITH SECURITY_ENFORCEDAND every interpolation is a:bindvariable, the finding still fires but the severity reflects the meaningful mitigation. Eliminates multiple cross-project false-positive Highs.Untyped JSON deserializationdeduped per file + dropped to Info. A class with N untyped-deserialize calls now emits one Info finding noting the total count, not N separate Low findings.- SF-PERM-001 stops flagging Salesforce-managed permsets and standard
profiles.
vulkro-sf org permsno longer reports permission sets carrying aNamespacePrefix(force-shipped) or standard system profiles (System Administrator, Standard User, the SF-shipped integration users). When a profile-owned permission set is flagged, the finding title now shows the profile name rather than the auto-generatedX<id>Name field. loginiprange-missing-on-admindowngrades for the standard Admin profile. When the profile XML carries<custom>false</custom>(the Salesforce-delivered System Administrator), severity drops to Low and the message acknowledges the standard-profile context instead of implying the user authored the profile.- Dependency-confusion no longer fires on official scoped packages.
@salesforce,@lwc,@prettier,@types,@babel,@typescript-eslint,@aws-sdk,@google-cloud,@azure,@vue,@angular,@anthropic-ai,@nestjs, and ~30 other official org scopes are now allowlisted. Cuts six false positives per typical Salesforce-tooling project frompackage.json. - Mass-assignment no longer fires on Map / List
The detector now requires the receiver to be a declared SObject;
Apex
Map<K, V>.put()is a map insertion, not a dynamic field write. Cuts ~19 cross-project false positives. - Custom Metadata
custommetadata-unprotected-secretallowlists LLM-config fields.Max_Tokens,Temperature,Top_P,Top_K,Stop_Sequence,Frequency_Penalty,Presence_Penalty, and similar numeric-config field names no longer trip the secret-shape match. - Custom Metadata
cmt-reference-field-tamper-surfacenarrowed. The carrier fallback no longer matches plain_Namesuffixes; it requires_Reference,_Ref,_DevName,_API_Name,_APIName, or_ApiNameand the value must look like a developer name (callout:...or^[A-Z][A-Za-z0-9_]{2,79}$with at least one underscore). Eliminates ~50 false positives from value-key records in custom metadata threshold and config tables. - Cognitive-complexity findings no longer classified as
SecurityMisconfiguration. They move to a newCodeQualitycategory so the OWASP rollup is no longer polluted by complexity noise. mixed-versioned-unversioned-routesfilters Aura RPC.@AuraEnabledendpoints no longer count against the versioned-vs-unversioned ratio (Aura RPC has no URL-path versioning).dangerous-js-primitive-literaldeduped per handler + reframed forwindow.opentargets. AhandlePrint()function with five consecutiveprintWindow.document.writecalls now emits one finding, not five. When the receiver looks like awindow.open()result, the message acknowledges that Lightning Web Security does NOT sandbox the new window.- Stable kebab-case signal IDs across the board. Every emitted
finding now carries a non-empty kebab-case signal that downstream
consumers (SARIF dedup, suppression annotations, dashboards) can
index against. Dead-code detector specifically: signals renamed from
the placeholder
functiontodead-code-unused-function(and four sibling kinds), and the detector no longer flags Salesforce framework entry points (Queueable / Batchable / Schedulableexecute, Batchablestart/finish,Messaging.InboundEmailHandler.handleInboundEmail, and@InvocableMethodtargets).
Fixed
AP-028 no longer false-fires on Salesforce compound-field shapes like
Geolocation__Latitude__s. Real Salesforce namespaces are lowercase by convention; the detector now requires the namespace prefix to be lowercase.AP-029 no longer false-fires on standard field accesses like
acct.Description.containsIgnoreCase(...)orrecord.Field__c.method(...). The "class" slot now rejects identifiers containing__(custom-field access) and a 75-entry stop-list of common Salesforce standard field names (Name, Description, Email, Phone, BillingStreet, OwnerId, etc.).AP-028 / AP-029 no longer false-fire on a project's own namespace. A managed package's references to its own namespace are not a hardcoded-namespace anti-pattern by construction.
BFLA tests no longer flake under parallel execution. The
security::tests::bfla_*tests previously walked/tmp/claude-501/as their scan root and picked up leftoversf_b2c_commercefixtures from other tests, breaking their assertion counts. Each BFLA test now writes its fixtures to a unique tempdir keyed on pid + nanos and scopes the walker to that directory only, with a Drop guard for cleanup. The siblingsf_b2c_commercetests were updated to follow the same pattern so they do not bleed into other tests' walkers.
Notes
- Multi-platform binaries (macOS, Linux, Windows) for
vulkro-sf 0.1.1are not yet on the CDN. The version bump, the public changelog, and the website are all live. Binary distribution is the next step.
Added
- Vulkro is now an MCP server.
vulkro mcp servemakes the scanner callable from any MCP client (Claude Desktop, Cursor, Windsurf, Continue, VS Code) over stdio or over SSE bound to localhost. Five tools are exposed:scan_project(full scan, returns the standard JSON plus ascan_id),scan_file(scan the containing project, return only that file's findings),explain(markdown explainer for any rule ID),list_rules(every rule with ID, title, OWASP category, default severity, and rule-page URL), andget_findings(re-read a prior scan's findings with an optional severity filter). Result shapes matchvulkro scan -f jsonwhere applicable, so existing automation works unchanged. Read-only by design: no tool writes to disk or mutates your repo, and your code, file paths, and findings never leave the machine. Setup guide atvulkro.com/docs/cli/mcp-serve. - Vulkro now runs as a Language Server.
vulkro lspexposes the scanner over the Language Server Protocol, so any editor with an LSP client streams findings into the gutter as diagnostics. Each diagnostic carries the stable Vulkro rule ID as its code andvulkroas its source, and maps severity the way you would expect (critical and high become errors, medium a warning, low information, info a hint). The first file you open triggers a full project scan, cached for the session; saving re-runs it.workspace/executeCommandsupportsvulkro.explainto render a rule's explainer. Read-only by design: no code action writes to your files, there is no formatter, and your code, file paths, and findings never leave the machine. Setup snippets for Neovim, Helix, and Emacs eglot are in the full guide atvulkro.com/docs/cli/lsp. - VS Code and Cursor extension. Built on
vulkro lsp, it streams findings into the gutter and Problems pane across every language the scanner understands (Python, JavaScript, TypeScript, JSX/TSX, Go, Ruby, Java, C#, PHP, and Salesforce Apex), and adds three Command Palette commands: explain a finding by rule ID, run a full project scan without saving first, and open settings. Pointvulkro.pathat your binary, setvulkro.severityThresholdto filter before findings reach the gutter, and choose a rule pack withvulkro.rulePack(default,owasp-api,owasp-asvs,salesforce,supply-chain, ormcp-server). The same.vsixloads in Cursor. No cloud round-trip and no telemetry: the extension spawns the scanner locally. vulkro scan-mcp-server <path>: audit the code that implements an MCP server. Scans Python (mcp.server, FastMCP) or TypeScript / JavaScript (@modelcontextprotocol/sdk) server source for eight issue classes at stable rule IDsMCP-SERVER-001throughMCP-SERVER-008: tool-description injection, tool poisoning (caller-controlled side-effect targets), rug-pull risk (descriptions that mutate at runtime), sensitive sinks inside a tool handler, manifest-versus-handler mismatch, unbounded resource access, secrets or PII leaking into tool results, and auth bypass on sensitive tools. Distinct fromvulkro mcp-audit, which audits MCP host configs, and fromvulkro mcp serve, which makes Vulkro itself speak MCP. Fully offline, standard--format table|json|sarif|ndjson, standard exit codes.vulkro scan --ai-pr: a calibration mode for reviewing AI-generated code drops. Tunes the scan for diffs produced by a coding agent, where you want tighter scrutiny of the access-control surface. Vulkro inspects the latest commit for four shape signals: a diff over 200 lines added or removed, new non-test files with no paired test, runs of near-identical doc-comment blocks, and a marker phrase in the commit subject. When at least two fire, every access-control finding (object-level, object-property, and function-level authorization, broken authentication, and CSRF) moves up one severity, carries anai-prtag so reports can tell calibrated severity from baseline, and low-confidence findings drop out of the displayed report. A one-line header prints what the detector saw, for exampleai-pr signals: diff=YES files=4 docstrings=2 marker=YES (4/4). If git is unavailable or the repo has fewer than two commits, the calibration sits out silently. Detection itself is unchanged: this only changes how loudly an existing finding is reported.- Three deep compliance frameworks for the audit-evidence pack.
vulkro compliance-packaccepts three new--frameworkvalues:pci-dss-4-0(Requirements 6, 11, and 12; 32 controls),nist-800-53(the moderate baseline across the AC, AU, CM, IA, RA, and SI families; 42 controls), andsoc2-full(Common Criteria CC1.1 through CC9.2 plus Availability, Confidentiality, Processing Integrity, and Privacy; 61 controls). Each ships per-control metadata: the published control title, a plain-English description of what it requires, the rule IDs whose findings support the assertion, and the rule IDs whose findings contradict it. Each run also writes a self-contained<framework>.htmlwith a control-by-control evidence table, Pass / Partial / Fail status pills, and rule-ID chips that turn red when a contradicting finding fired. The existing JSON, Markdown, and CSV outputs still land, and the three original frameworks (soc2,iso27001,hipaa) are unchanged. - Salesforce views in the desktop console. A top-level Salesforce tab groups findings by detector pack (Apex, LWC and Aura, Visualforce, Flow, metadata, B2C Commerce, Marketing Cloud, Health Cloud, Financial Services Cloud, CRM Analytics, Salesforce Functions, Heroku Connect, plus the PMD, ESLint, and RetireJS wrappers), each tile showing finding count and severity breakdown. The 10-section AppExchange Security Review readiness view the CLI renders as HTML is now a tab inside it, with pass / fail / not-evaluated status per section and the findings behind each verdict.
- A
Respondbutton on CVE rows. Run incident response in place from any CVE finding: which lockfile pins the vulnerable version, which files import it directly, which transitive dependents pull it in, and the suggested safe upgrade. Also available on Salesforce supply-chain advisory findings. - Salesforce Marketing Cloud detector pack. Six rules,
MC-001throughMC-006: AMPscript injection (a request-derived variable printed without anEncodeHTMLwrap), SSJS eval of caller input, SQL Query Activity injection, a hardcodedSubscriberListIdin deployable code (the cross-tenant leak shape), a cleartext REST APIclient_secretin Cloud Pages source (placeholder-aware, soREPLACE_MEsentinels stay quiet), and a mass-mail send with no preceding opt-in or consent check (CAN-SPAM and GDPR e-Privacy). Vulkro walks.amp,.ssjs, and.html/.js/.xmlfiles under a Marketing Cloud directory marker; projects without one emit nothing. - Salesforce Industries Clouds pack for Health Cloud and Financial Services
Cloud. Six rules,
IND-001throughIND-006, that run automatically when a scan finds a Health Cloud or FSC schema in the project: patient, care plan, medical condition, or practitioner DML with no paired audit-log write (the HIPAA 45 CFR 164.312(b) gap); FSC relationship and financial-account DML from a request-reachable entry point with no ownership check; a regulated field (date of birth, SSN, medical condition code, patient MRN, masked account number, NPI, tax ID) written without Shield Platform Encryption or a field-accessibility check;RuntimeIndustriesContextread without a permission check; an@AuraEnabledmethod returning a PHI object withoutSecurity.stripInaccessibleor an explicit field projection; and a programmaticAccountShareinsert granting Edit or All access from a request-reachable method. On a project with no Health Cloud or FSC schema the pack returns empty before reading a single Apex file. - Salesforce B2C Commerce Cloud pack for cartridge and SFRA storefronts.
Six rules,
B2C-001throughB2C-006: a data-modifying OCAPI or SFRA route registered with no auth or CSRF middleware; hardcoded payment-processor credentials (Adyen, Cybersource, Stripe, Braintree, PayPal, plus real-format Stripe keys, with placeholders skipped); cardholder data reaching a log, response body, cookie, or privacy slot with no masking call (the PCI-DSS 3.3 violation); unsafe ISML template injection, both<isprint encoding="off"/>and${expr}inside<isscript>, escalating to high when the expression reads frompdictor the HTTP parameter map; Service Registry credentials that bypass the Business Manager credential vault; and customer PII flowing into a URL parameter, response body, or cookie with no redact, mask, or hash wrapper. Recognised encoders and masking wrappers suppress the matching findings. The pack self-filters in microseconds on projects with nocartridges/directory. - Salesforce Heroku Connect pack. Five rules,
HC-001throughHC-005, across mapping configurations, Heroku app config, Procfiles, and the Postgres-side SQL schema: a sensitive Salesforce field replicated into Postgres with no redaction transform (high), a mapped column with no column-level encryption anywhere in the project (high), write-back enabled with no conflict-resolution strategy (medium), a plaintext Postgres connection URL in a committed config file (high), and a hardcoded Heroku Connect API token (medium). - Salesforce CRM Analytics pack for SAQL, dataflow, and dashboard JSON. Five rules: SAQL injection, dashboard binding to tainted free-text input, row-level security disabled on a PII dataset, public lens or dashboard sharing on a PII dataset, and hardcoded Salesforce IDs in dashboard JSON that break on sandbox refresh.
- Salesforce Functions pack for Node.js and Java functions under a DX
project's
functions/tree. Five rules: invocation-context credential leak (logging the access token), runtime token mishandling (caching it across invocations), DataApi DML with unvalidated payloads, outbound HTTP with no rate-controlling wrapper, and synchronous CPU-heavy or unbounded-recursion shapes the Functions runtime will run out of memory on. - Aura component markup analysis. Vulkro now reads the Lightning Aura
markup itself (
.cmp,.app,.evt,.intf), not just the JavaScript controllers, closing a blind spot for orgs still shipping Aura. Five rules,SF-AURA-CMP-001throughSF-AURA-CMP-005: a dangerous HTML attribute with a tainted merge expression, an<aura:html html="...">raw sink, an<aura:unescapedHtml>sink,<lightning:formattedRichText>bound to unsanitised input, and an action handler that pushes a view attribute straight into DML or a callout. It only fires when the bound attribute comes from a request-shaped source, so admin-controlled rich text stays quiet. - Lightning Web Security policy audit for LWC bundles. Vulkro reads every
bundle's metadata and CSP files and flags the sandbox relaxations an
AppExchange reviewer would catch by hand. Six rules,
SF-LWS-001throughSF-LWS-006:allowUnsafeEval(high), a CSP override declaring'unsafe-inline'forscript-srcordefault-src(high), a wildcard origin inscript-src/connect-src/style-src/default-src(medium), a wildcard origin inframe-src/child-src/frame-ancestors(medium), an explicit Lightning Web Security opt-out or an exposed community page paired with a permissive capability (high), and a legacy LockerService marker or an API version below 54.0 with no migration (low, informational). Findings route to the Lightning Component Security section of the AppExchange readiness report. - New LWC detector: a
@wirevalue flowing intoinnerHTML. Catches the canonical shape where a property decorated with@wireis assigned straight toinnerHTML,outerHTML, orinsertAdjacentHTMLwith no sanitisation. This is the most common XSS shape AI coding assistants produce when asked to render a server field. Known sanitisers are recognised, and one hop of local-variable flow inside lifecycle and handler blocks is tracked, so a loop that pulls each item out of a wired collection before inserting it still fires. Confidence is high when source and sink share a lifecycle or handler body, medium when they sit in different methods of the same class. - Apex code-coverage estimator. Surfaces production Apex classes at risk
of failing Salesforce's 75% production-deploy coverage threshold before you
spend org time on a test run.
APEX-COVERAGE-002(high) fires when a production class has no references from any@isTestclass;APEX-COVERAGE-001(medium) when the estimated coverage is under 75%. Every finding carries the caveat plainly: this is a static estimate and a pre-flight signal, and the org-side runtime measurement is the authoritative number. vulkro sf-appexchange-report <path>: the AppExchange Security Review readiness report. Scans a Salesforce DX or legacy MDAPI project and renders one self-contained HTML report grouping findings by the ten published Partner Community checklist sections: Code Quality, Object and Field Permissions (CRUD / FLS), Sensitive Data Storage and Logging, Cryptography, Sharing and Visibility, Lightning Component Security, Visualforce Security, Profiles and Permission Sets and Named Credentials and Connected Apps, External Integrations and Callouts, and Flow Security. Each row is rated PASS, FAIL, or NOT EVALUATED with per-finding file and line links and the originating rule ID. The header pins the checklist mapping version date, so a report generated today looks the same a year from now even if the published checklist changes. Open it in any browser and use File > Save as PDF for an offline deliverable. Exit code 0 when every covered section passes, 1 when any section fails.vulkro sf-appexchange-report --fpw-out PATH: generate the False Positives Whitelist the AppExchange submission portal accepts. Reviewers ask submitters to attach anFPW.jsondocumenting which static-analysis findings were reviewed and accepted, and historically that file was written by hand. Vulkro now builds it from the project's existing suppressions: inline// vulkro:disable[<rule>] reason="..."comments across Apex, Visualforce, Flow XML, LWC and Aura source, and metadata XML, plus.vulkroignoreentries with a trailing# reason="...". Each entry carries the engine name, file path, line number, rule identifier, and your explanation; entries with no reason carry a placeholder for you to fill in. The schema version is printed on the "wrote FPW.json" line so a reviewer can match a stored file back to what produced it. The HTML report is unchanged and the JSON is written only when you pass the flag.vulkro portfolio engagement-bundle <parent-dir> -o bundle.zip: a multi-org bundle for Salesforce consultancies. Scans a folder of Salesforce DX projects (one per client org), generates an HTML executive report per org, and packs everything into one zip ready to hand to a client or attach to an audit deliverable. The zip contains anindex.htmllisting every org with total, critical, and high finding counts, NIST 800-53 Rev. 5 and SOC 2 pass and fail counts, and a link to each per-org report. Built for the 5-to-50-orgs-per-engagement workflow. Every report is self-contained HTML with no external CSS, JavaScript, fonts, or network calls, so it opens cleanly in an air-gapped review environment. Exit code 0 when no org reports findings, 1 when at least one does.vulkro scan --include-pmd: run PMD for Apex alongside the native detectors. Vulkro ships a curated security-only PMD Apex ruleset, invokes a locally installedpmdagainst your project, and folds the violations into the same report. The ruleset covers PMD's Apex security category in full (ApexBadCrypto, ApexCRUDViolation, ApexCSRF, ApexDangerousMethods, ApexInsecureEndpoint, ApexOpenRedirect, ApexSharingViolations, ApexSOQLInjection, ApexSuggestUsingNamedCred, ApexXSSFromEscapeFalse, ApexXSSFromURLParam) plus a security-only slice of the error-prone category. Style, documentation, design, and performance categories are excluded by design so the report stays signal-only. Each violation appears as a regular finding with severity derived from PMD's priority, aPMD-APEX-<rule>identifier, and a rule-specific remediation hint, and duplicates emitted by both PMD and a native detector on the same line are collapsed. Install PMD frompmd.github.io(brew install pmdworks on macOS). The native detectors run unchanged whether or not the flag is set.vulkro scan --include-eslint: ESLint coverage for LWC and Aura. Wraps a host-installed ESLint with a curated security ruleset and merges its findings into the same report. Covers the core ESLint security rules (no-eval,no-implied-eval,no-new-func,no-script-url,no-octal-escape,no-prototype-builtins) plus the Salesforce LWC and Aura plugins. Formatting and style rules are excluded on purpose. Each message becomes a regular finding with anESLINT-LWC-<rule>orESLINT-AURA-<rule>ID, a mapped OWASP category, and hand-written remediation. If no ESLint binary resolves, the scan exits with code 2 and an install hint rather than silently reporting zero findings.vulkro scan --include-retirejs: RetireJS for Salesforce static resources, LWC, and Aura bundles. Runs RetireJS against the client-side surface, which is where vendored copies of jQuery, AngularJS, Bootstrap, lodash, and moment hide inside managed packages with no lockfile. Each detected library version becomes one finding with severity mapped from the advisory, a[RETIRE]prefix, remediation pointing at the safe minimum version, and evidence carrying every identifier RetireJS attached (CVE, GHSA, upstream bug numbers). A drop-in replacement for the historical sfdx-scanner RetireJS step. If no RetireJS binary is available, the wrapper prints an actionable warning listing every install option and continues with the native rule set.- All wrapped and Salesforce findings flow through every existing output.
PMD, ESLint, RetireJS, and the new
MC-*,IND-*,B2C-*,HC-*,SF-AURA-CMP-*, andSF-LWS-*rules all appear in the CLI table, JSON, SARIF, NDJSON, JUnit, CSV, gh-pr, CycloneDX, SPDX, PDF, and RoPA outputs, and are honoured by--fail-on,--gate,--strict-confidence, the baseline diff, and the AppExchange readiness report. - Deeper Salesforce coverage: Visualforce, Named Credentials, Connected
Apps, and a standard-object PII map. Visualforce pages and their
controllers surface five more shapes: a dynamic
<apex:iframe>source (an open-redirect and phishing surface), controller properties pulled from URL parameters with no escape helper (a reflected-XSS source), and<apex:commandButton>actions wired to controllers that lackwith sharing, so the action runs in system context for every page visitor. Named Credential metadata now flagsallowMergeFieldsInBody(server-side template injection) and an anonymous principal paired with a hardcoded password. Connected App metadata now flagshttp://callback URLs and literal consumer keys and secrets committed to source, with template placeholders recognised and skipped. The profile and permission-set audit now gradesAuthorApex,ModifyMetadata, andCustomizeApplicationlower on a developer or release-engineer profile than on a business-user profile, because the grant is expected there but still worth tracking. A new Salesforce PII map recognises personal-data fields on six standard objects (Account, Contact, Lead, Opportunity, Case, User) and surfaces Apex SOQL and DML access to them as informational findings that feed the GDPR, HIPAA, SOC 2, and NIST reports.vulkro compliancealso accepts--profile nist-800-53and the--profile staterampalias, with eleven mapped controls (AC-2, AC-3, AC-4, AC-6, AU-2, IA-5, SC-8, SC-13, SI-7, SI-10, CM-7). - A recall benchmark against real, publicly catalogued vulnerabilities. A new benchmark tree measures Vulkro's recall and precision against a labelled corpus of 19 real public vulnerabilities, each pinned to a specific pre-fix commit in an upstream repository (anxolerd/dvpwa, we45/Vulnerable-Flask-App, stamparm/DSVW, mpirnat/lets-be-bad-guys, OWASP/NodeGoat, and juice-shop/juice-shop). The harness clones each repo at its pinned commit, runs Vulkro at the default medium-and-above confidence floor, and reports per-class numbers across eight classes: SQL injection, cross-site scripting, open redirect, CSRF, insecure cookie, IDOR, insecure deserialization, and hardcoded secret. The methodology, the inclusion rules, the per-class signature mapping, and every incident's pinned commit are documented, so you can check any number against the incident it cites. At the time of this release the scorecard measured 58% recall and 44% precision overall: numbers reported as they came out, not curated, with the known recall holes documented per incident. This is additive; the existing regression and head-to-head benchmarks are unchanged.
Improved
Sharper "security misconfiguration" findings. Six false-positive shapes in this category were the loudest noise source in the head-to-head benchmark. Each is now gated on a concrete signal, so the rule fires only when the surrounding code actually looks vulnerable:
- A promise chain ending in
.then(...)with no.catch(...)reaches medium only in a route, handler, controller, view, api, or endpoint directory AND when the chain touches a database mutation. Unhandled rejections in build and boot scripts drop to low. print(...)in Python fires only when the enclosing function is a request handler. CLI and startup prints no longer trigger.- Loose equality in JavaScript reaches medium only when an operand names
something credential-shaped (password, token, secret, api key, session,
otp). A bare
x == 0stays low. - "Administrative endpoint authenticated but no role check" fires at medium
only when an admin-marker decorator was actually seen at the route. A
match on the URL containing
/adminalone drops to low, so it stops dominating the rollup on apps with admin-shaped paths. - JWT findings now attach a confirmation signal when the matching line names a JWT call, and a tainted-source signal when request data sits nearby. The finding still emits at its original severity; the extra evidence makes the signal traceable.
- A promise chain with no catch is reported once instead of twice; the duplicate emitter was retired.
Together these shed false positives in the head-to-head benchmark (the security-misconfiguration false-positive count drops from 95 to 84 at the broadest threshold and from 53 to 47 at the default medium threshold) while recall on the same rule group holds steady.
- A promise chain ending in
New detectors
- XXE (XML external entity) misconfiguration. Catches a Python
lxml.etree.XMLParser(...)built withresolve_entities=True,no_network=False, orload_dtd=True. These flags turn off the parser's external-entity protections, so any document parsed through it is a CWE-611 vector regardless of where the data came from. Files importingdefusedxmlare suppressed, and remediation points atdefusedxml.lxml.parse. - Meta-refresh open redirect. Catches Python handlers that build a
redirect by hand instead of using a framework helper: a
<meta http-equiv="refresh">with a request-derived URL, asend_header('Location', ...)with request data, and the.format(...)and f-string variants. It sits next to the existing open-redirect rule, which only saw the framework-call shapes. - Insecure session-storage cookies. Catches Python aiohttp, Tornado, and
Flask-Session storage constructors built with
httponly=Falseorsecure=False. Every session cookie minted from that store inherits the unsafe flag, so one flag here turns the protection off for the whole app. One bad flag is medium; two stacked is high. - URL-attribute XSS in
.tsx/.jsx/.ts/.js. Catches ProseMirror and TipTaptoDOMand React<a href={...}>shapes where thehref,src,action, orformactionvalue comes from a user-controlled chain and no URL sanitiser is imported in the file. Ajavascript:,data:text/html;, orvbscript:value in such an attribute executes when the element renders. Covers the multi-line ternary shape where the property and the dangerous fallback sit five to ten lines apart. - JavaScript named-template-literal SQL injection. Catches the
store-then-execute pattern, where a SQL template literal is assigned to a
variable and executed further down the file, too far apart for a per-line
scanner to connect. Vulkro walks up to 40 lines forward from the assignment
looking for a
.query,.execute,.raw, or.runon that name. The template must contain an interpolation; constant SQL templates do not fire.
Detection improvements
- Commented-out CSRF middleware is now caught in bootstrap files. The
check previously only ran on Python files that already had a route
decorator, so it never fired on the
app.pyorwsgi.pyfiles where the middleware list actually lives. It now catches a commented-out CSRF entry in the app constructor. - Hand-rolled query-string handlers now seed taint correctly. Python
handlers that parse the query string into a
paramsdict broke the taint chain because that shape was not in the source list. It is now recognised, so SQL injection, command injection, and XXE flows through those handlers are traced. Code using framework helpers is unchanged.
Fixed
- License files issued before this release are read correctly again. A
change to how a license file encodes the capabilities it carries meant some
previously issued files decoded incorrectly, so a correctly licensed machine
could be refused a capability its own license covers (Salesforce Apex
scanning, for example) while
vulkro license-statusreported that same license as active. Those files now decode correctly. Files issued after the change were never affected, nothing needs to be re-issued, and the fix applies automatically on upgrade.
Added
Live-org PII map. A new view that lists which standard Salesforce objects in your connected org hold personal data, read directly from the org's real schema. Each object shows a field count, whether it is GDPR-relevant, whether it is HIPAA-relevant, and a small set of field examples. Useful for an audit kickoff conversation: it answers "where does our personal data live" without you typing a single query.
Live-org audits now capture a full point-in-time snapshot. A Salesforce org audit used to record only its findings; now every audit captures the full state of the org it reviewed (status, identity and permissions, configuration, connected apps, packages, Agentforce) and stores it alongside the findings. Past audits stay readable: a new "View snapshot" button on the Audits tab opens any past audit's captured state read-only, so you can see exactly the posture that existed when the review ran.
Live-org audits now include configuration hardening findings. Previous audits surfaced only identity, packages, and login-IP issues. Audits now cover the full five categories the homepage promises, including session policy, clickjack and forgery protection, and password lockout. A clean org records a clean snapshot with zero findings; a hardened-but-not-perfect org now shows the precise settings that need attention.
Per-stage progress while a live-org audit runs. A short progress strip updates as the audit moves through status, identity, configuration, connected apps, packages, and Agentforce, so a 15-20 second run stops looking stalled.
Added
Salesforce tab in the desktop console. A new top-level Salesforce view that groups every Vulkro finding by detector pack: Apex, LWC and Aura, Visualforce, Flow, metadata, B2C Commerce, Marketing Cloud, Health Cloud, Financial Services Cloud, CRM Analytics, Salesforce Functions, and Heroku Connect, plus the PMD, ESLint, and RetireJS replacements. Each pack tile shows finding count and severity breakdown at a glance.
AppExchange Security Review checklist in the desktop console. The same ten-section readiness view the CLI renders as HTML is now a tab inside the Salesforce view. Each section shows pass, fail, or not-evaluated status and the findings that drove the verdict.
AppExchange Security Review readiness HTML report. A single HTML report grouped by the published Security Review checklist. Section by section, what cleared and what still needs work. Email it to your reviewer or hand it to a client as-is. No login required to read it.
Salesforce Functions review for Node.js and Java functions under a Salesforce DX project. Five checks: credential leak inside the invocation context, runtime token mishandling, DataApi writes with unvalidated payloads, outbound HTTP without rate-limiting, and CPU-heavy or unbounded recursion that the runtime will reject.
Salesforce CRM Analytics review for SAQL, dataflow, and dashboard JSON. Five checks: SAQL injection, dashboard binding to free-text input, row-level security disabled on a personal-data dataset, public sharing on a personal-data dataset, and hardcoded Salesforce IDs that break on sandbox refresh.
Salesforce Heroku Connect review. Vulkro reviews Heroku Connect mapping configurations, Heroku app config files, Procfiles, and the Postgres schema on the Heroku side. Catches sensitive Salesforce fields replicated to Heroku without redaction, plaintext connection URLs, missing column-level encryption, hardcoded Heroku Connect API tokens, and write-back without a conflict-resolution strategy.
Salesforce Marketing Cloud review. AMPscript injection, server-side eval of caller input, SQL Query Activity injection, and the other patterns that have driven Marketing Cloud incidents.
Connected App OAuth posture review. Five checks that catch the over-permissioned token pattern behind the 2025 to 2026 third-party Salesforce breaches (Drift, Gainsight): broad scope co-occurring with refresh tokens, never-rotated client secrets, no IP relaxation, no admin-only profile restriction, and an unused legacy app left active.
Agentforce ForcedLeak detector. Catches the class-bypass pattern (CVSS 9.4) where an AI agent action calls an Apex class declared without sharing.
Salesforce Well-Architected anti-pattern review. A built-in scan against the published Salesforce Well-Architected anti-pattern catalogue: queries inside loops, missing test coverage, hardcoded record IDs, multiple triggers per object, recursive triggers, and the rest of the patterns that fail Security Review for reasons unrelated to security itself.
Fixed
- Pro license holders no longer hit Free-tier gates on Salesforce reviews. Some Pro licenses issued during a pre-release window were decoded incorrectly after the license layout was finalised. Symptom: the CLI printed "Vulkro Free does not include Salesforce Apex" while the license-status check showed Pro. Affected licenses now decode correctly on first review with no re-activation required.
Supply-chain and AI-host security headline this release. Three new commands
ship: vulkro mcp-audit looks at the MCP host configs your editors load,
vulkro extension-audit looks at the editor and browser extensions installed
on your machine, and vulkro respond answers "is this advisory or package in
my project?" in under a second. The default scan gained a compromised-release
catalog, so 15 historical supply-chain incidents are caught with no new flag.
Scan presets, an NDJSON format for SIEM pipelines, a documented confidence
rubric, and per-rule documentation round out the release.
Added
SARIF column information. Block-shaped findings now emit start column, end line, and end column, which gives IDE highlights the actual region instead of a single line.
CSV confidence column.
findings.csvexposes aconfidencecolumn at position 11. Existing columns and their indices are unchanged so any spreadsheet pivots keep working.Two new Ruby on Rails authorization checks. Catches controllers where read endpoints are guarded but mutating endpoints are not, and whole-controller missing-auth on
*Controller < ApplicationControllerclasses. Aware of Devise (before_action :authenticate_user!), the legacybefore_filter,require_login,require_signed_in_*, and honoursonly:/except:filter scopes.vulkro mcp-audit: audit the MCP host configs your editors load. Covers Claude Desktop, Cursor, Windsurf, VS Code, Cline, Continue, and Gemini, plus project-local.mcp.jsonandmcp.json, across six issue classes,MCP-001throughMCP-006: unpinnednpxoruvxregistry installs, mutable git refs, overbroad filesystem mounts (root, home, shallow directories), inline secrets in env blocks, cleartext or unauthenticated remote endpoints, and known-compromised MCP server releases. Secrets are reported by length bucket only: the literal value never appears in a finding. Default paths are discovered automatically, or pass a file or directory to scope the run.vulkro explain MCP-001renders the rationale and remediation for any of them even with no live finding.vulkro extension-audit: audit the extensions installed on your machine. Covers VS Code, Cursor, Windsurf, and VSCodium by default, and Chromium-family and Firefox browser extensions with--include-browser, across three classes,EXT-001throughEXT-003: a hit against the supply-chain compromise catalog (which covers the December 2024 Cyberhaven Chrome publisher phish and its cluster including Internxt VPN, and the 2023 VS Code Marketplace malicious-extension campaign), overbroad manifest permissions (<all_urls>,debugger,webRequestBlocking, or several broad API permissions together), and a CSP that permits remote code loading. Pass--require-extensionto fail on empty discovery, and--fail-on critical,highto wire it into CI.vulkro respond: incident response for the 3am advisory drop. Give it an advisory ID or aname@versionpackage spec and it walks every lockfile (npm, Poetry, Pipfile, Cargo, Go) and per-file imports across JavaScript, TypeScript, Python, Go, and Rust, then reports every place the package shows up, including transitive dependents. Cold-start wall clock is well under a second on a typical project. Exit code 1 when you are exposed, 0 when you are not, 2 on bad input, with aCVE-*ID returning an actionable "use--packageinstead". The reverse index is cached and invalidated whenever a lockfile changes; pass--no-cacheto bypass it.Compromised-release catalog, wired into the default scan. No new subcommand and no new flag:
vulkro scannow matches your pinned versions against 15 seeded historical supply-chain incidents (event-stream 2018, ua-parser-js 2021, colors and node-ipc 2022, ctx on PyPI 2022, the xz / liblzma backdoor 2024, @solana/web3.js 2024, and more), emitting under five stable rule IDs by compromise kind:SUP-COMPROMISE-001hijacked publish,-002typosquat,-003malicious postinstall,-004backdoored release, and-005compromised pipeline build. The catalog refreshes withvulkro update, and a baked-in copy ships in the binary so fresh installs and offline machines still get the seed incidents. Runvulkro explain SUP-COMPROMISE-003for per-kind remediation.Scan presets.
vulkro scan quick <path>,vulkro scan ci <path>, andvulkro scan deep <path>(or--preset).quickis the pre-commit lane (high confidence, source only, last-commit diff).ciis what CI should run (the current default plus--gateand--fail-on critical,high).deepis for pre-release thoroughness (all confidence levels, includes unreachable code, evidence floor at zero). A barevulkro scan [path]still defaults tociand prints a one-time hint suggesting an explicit form. No flag was removed or renamed.NDJSON output (
--format ndjson). One finding per line plus a final summary line carrying severity counters, scan ID, and duration. Built for SIEM pipelines andjq. Available onvulkro scanandvulkro probe.--strict-confidenceonvulkro scan. Filters out high-severity findings whose evidence bag is empty. Off by default, so behaviour is unchanged unless a CI gate opts in for maximum precision..vulkroignorefingerprint allowlist. One 16-character hash per line, with#comments. Fingerprints are stable across reformatters (Prettier, rustfmt, gofmt), so suppressions survive a formatting pass. Inline directives gainedfp=<hex>metadata too, so a finding can be suppressed by fingerprint alone with no category needed.vulkro sbomandvulkro match-cve. The SBOM emit and the CVE matching are now standalone commands. Produce a CycloneDX 1.6 or SPDX 2.3 SBOM once, then re-run CVE matching whenever the bundle refreshes. Both are offline-safe;match-cvereads only the local CVE cache.--pq-auditonvulkro scan: an opt-in post-quantum crypto audit. Flags classical RSA, ECDSA, ECDH, and classical Diffie-Hellman usage that NIST PQC migration guidance (FIPS 203 ML-KEM, FIPS 204 ML-DSA, FIPS 205 SLH-DSA) targets for replacement. Symmetric primitives (AES, ChaCha20, SHA-2 and SHA-3) are deliberately not flagged: Grover only halves effective key length, so AES-256 stays 128-bit strong. Off by default, because these findings are a planning surface rather than an exploit-today defect.A
Verifiedconfidence tier. A finding moves from high to verified when its endpoint is reachable without auth AND the handler file reads user-controlled input (body, params, query, or headers, across Python, JavaScript / TypeScript, Go, Java / Spring, C# / ASP.NET, and Ruby / Rails). Fully static and offline, with no network call. The GitHub-PR output renders verified findings with a prominent callout.--format cbomonvulkro scan,vulkro probe, andvulkro compliance. Emits a CycloneDX 1.6 Cryptographic Bill of Materials. Each weak algorithm found (MD5, SHA-1, MD4, AES-ECB, RC4, DES, static-IV misuse, insecure RNG) becomes one cryptographic-asset component listing every file and line where it was used. Built for FedRAMP and post-quantum reviewers.CycloneDX SBOM moves from spec 1.5 to 1.6 on
vulkro scan --format cyclonedx. Wire-compatible for the fields Vulkro emits.vulkro rules import-semgrep <semgrep.yaml>. Reads a subset of Semgrep YAML and writes a Vulkro-format rule file the scanner picks up automatically, as a migration path for teams with an existing Semgrep rule library. Metavariable and ellipsis translation is best-effort; compound shapes (pattern-either,pattern-not, AND-composedpatterns:) are skipped with a per-rule warning rather than mistranslated.AST patterns in custom rules (
kind: ast). The custom rule format now supports a tree-sitter query in thepatternfield alongside the existing default regex kind, so matching runs against the parse tree instead of the text. Available for every grammar Vulkro parses (Python, JavaScript, TypeScript, Go, Ruby, Java, Kotlin, C#, PHP, and Salesforce Apex), one language per AST rule.vulkro fixprints the originating finding ID in each suggestion header, so you can grep a stream by ID and cross-reference it withvulkro explain <ID>. The banner also states plainly that Vulkro never modifies your files: the unified diffs are read-only patches forgit apply.Per-rule documentation pages. One page per rule with a description, what Vulkro detects, framework-specific remediation, linked CWE entries, and matched compliant and non-compliant examples.
vulkro explain <ID>now prints the rule-page URL next to every finding.
Changed
Test, fixture, and database seed files are now classified correctly across every detector. A single source of truth replaces 21 drifting copies; the previous versions missed database seed files entirely.
Comments no longer skew route line numbers. Endpoint extractors for Node.js, Python, Go, PHP, and JVM languages mask comment contents before matching, so a commented-out route is no longer reported as a live endpoint.
NoSQL injection detector tightened. A generic
.find(req.body)no longer fires unless a Mongo-family library (mongoose,mongodb,@mikro-orm,prisma) is imported in the same file. Unambiguous Mongo operators ($where,$function,$accumulator,$expr) in the same window still trigger.Template-injection lookback. A 30-line lookback for
const tmpl = "..."(andlet/var/ Python bare assignment) meansMustache.render(tmpl, data)wheretmplis a string-literal constant declared above no longer fires.Process spawn array argv recognised as not-shell.
child_process.execFile/execFileSync/spawn/spawnSyncinvoked with an array as the second argument no longer triggers command injection on tainted argv entries. These calls invoke execve directly. Tainted command names still fire.Path-traversal sanitizer registry tightened.
os.path.basename/posixpath.basename/path.basenameare no longer treated as sanitizers. They strip the directory component only; URL-decoded inputs and crafted paths can survive a basename round trip.IDOR membership-check heuristic recognises Rails CanCanCan. Controllers using
authorize!,load_and_authorize_resource, orcan?(no longer trip the missing-ownership-check check.CSRF detector reads the project's cookie policy. When the project pins
SameSite=StrictorSameSite=Laxon the session cookie, the cookie-session CSRF pathway is recognised as safe per RFC 6265bis and the detector suppresses for that language.SameSite=None(the unprotected case) is explicitly not matched.CVE matching: OSV LastAffected boundary fixed. A dependency pinned to exactly the last-known-vulnerable version was previously not flagged because the boundary was treated as exclusive. The interval representation now tracks inclusive vs exclusive per upper bound, matching the OSV schema. Real CVEs this missed are now caught. Regression test added.
Every detector now declares its confidence against one documented rubric. High requires evidence: a source-to-sink taint flow, an exact-match invariant (a literal credential against a provider pattern, a compromised-release catalog hit, a CVE-bundle match), a schema-versus-code contradiction, or two corroborating signals. Pattern-plus-context heuristics are correctly medium. In aggregate the behaviour is preserved, but specific detectors moved tiers as the rubric was applied uniformly, and the rubric itself is published.
Fewer false positives from the object-property authorization check. The PII-density heuristic was the loudest false-positive source in the benchmark (137 emissions on juice-shop alone). Three tightenings: the minimum keyword threshold rises from 2 to 4, so a file mentioning "email" and "address" no longer trips it; test paths are filtered out; and severity drops from high to medium, so a CI gate on
--fail-on critical,highno longer fails on a module-level density heuristic. Measured on the benchmark: precision 0.21 to 0.22, F1 0.34 to 0.35, recall held at 0.89, and the check's false-positive to true-positive ratio halved from 11.0 to 5.5.Unreachable dependency CVEs are downgraded, not dropped. The scan-time reachability filter is now on by default, and a dependency finding tagged unreachable moves down exactly one severity tier rather than disappearing. Pass
--no-reachability-filterto restore raw severities for a compliance audit that wants every CVE at its nominal level.GraphQL introspection moves from high to medium when no introspection guard is visible, because the absence of a marker is not proof. SDL sensitive-field exposure and fragment cycles stay high: both are schema contradictions rather than heuristics.
A CORS wildcard on its own is now explicitly medium. Combined with
credentials: trueit remains high.Several Salesforce and Apex findings now correctly land at high rather than medium, because those detectors emit evidence that clears the high threshold. Conversely, single-pattern emissions that used to surface as high now settle at medium: GraphQL introspection, open-redirect heuristics, missing helmet, and missing cookie flags. The net effect is that a CI gate on
--fail-on critical,highsees less pattern-only noise and more exact-match signal.
Fixed
Whole classes of project were being scanned as the wrong language. Several project shapes fell through to the Python fallback and were mis-parsed, so their findings never appeared:
- Any directory with
pom.xml,build.gradle,build.gradle.kts, orsettings.gradlenow detects as a JVM project. Spring Boot still gets its specific verdict; Quarkus, Micronaut, plain JAX-RS, Servlets, and vanilla Kotlin services now route to the JVM analyzer instead of the Python one. - Any
composer.jsonnow marks the directory as PHP. Laravel still gets its specific verdict; Symfony, Slim, CodeIgniter, WordPress, and Lumen now run through the PHP analyzer. - Console apps and Worker Services are no longer misclassified as
ASP.NET Core. That verdict now requires a real web signal
(
WebApplication.CreateBuilder,app.Map*,UseStartup,ConfigureWebHostDefaults, or an ASP.NET Core package reference); a bare C# project routes to the .NET analyzer instead. - A NestJS API that renders through Next.js is no longer flagged as a Next.js project.
- A Python project listing several frameworks in one manifest used to keep only the first match and silently drop the rest. It now runs every extractor. Single-framework manifests are unchanged.
- Mixed-language sub-projects (Python plus JavaScript tooling, Java plus Kotlin, Go plus TypeScript scripts) used to lose one side entirely. Vulkro now sweeps up any secondary language with three or more source files.
- A monorepo's overall language was taken from whichever sub-project sorted first, so a nine-Python, one-Go monorepo reported as Go. It now picks the most common language and framework across sub-projects, and monorepo detection recognises Yarn Berry and Bazel marker files.
- Legitimate code under a directory named
envwas being skipped wholesale. A real virtualenv at the project root is still skipped;src/env/config.pyorservices/env/handlers.gois now scanned.
- Any directory with
scan --rule-pack <name>had no effect. The flag was parsed but never dispatched, so the opt-in business-logic, state-machine, and concurrency detectors never ran. It now runs the matching detectors:business_logicandmoney_handlingrun the business-logic and money-flow rules,state_machineruns the state-machine rules, andconcurrencyruns the concurrency rules. Pack names are case-insensitive and whitespace-tolerant, and an unrecognised name prints a warning listing the valid names instead of being ignored. These detectors stay opt-in and out of the default scan.Scanning no longer crashes on non-ASCII source. Accented identifiers, CJK characters, or emoji near an internal lookahead boundary could abort the scan. Fixed, with regression tests.
Sanitized React HTML no longer flags as XSS.
dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(x)}}andel.innerHTML = DOMPurify.sanitize(x)are React's documented safe pattern, and the JS and JSX XSS rules now consult a sanitizer registry before firing. A baredangerouslySetInnerHTML={{__html: props.bio}}still fires.Production paths that merely contain "test" are no longer skipped. SQL, JWT, and command-injection rules classified any path containing
testorspecas a test file, so real code underattestation/,protest/, andinspector/was silently excluded from scanning.Multi-line JWT calls no longer produce false criticals. The JWT detector read only a three-line slice, so a
jwt.decode(...)withalgorithms=["RS256"]on a later line fired as critical or high. It now walks the full call expression, respecting strings and comments, and JWT findings carry explicit evidence for why they fired.Fewer false positives from Python
.filter(...). The SQL rule's.filter()shape now requires a SQLAlchemy import in the same file, sologger.filter(record + name)and other unrelated.filtercalls stop tripping it. A real SQLAlchemy.filter("name = " + var)still fires.Database seed and factory files no longer produce false PII and secret findings. A literal SSN or card number in a seed file is reported at low confidence (intentional sample data, hidden from the default view) and literal phone numbers and emails are skipped entirely. A real provider-format key (
sk_live_...,AKIA...) in a seed file still trips at full strength.Hardcoded-PII detection no longer flags data inside comments. An SSN, email, or phone number in a trailing or block comment is documentation, not live data.
Big-history secret scan no longer runs out of memory. Git log output is now capped at 100 MB with an actionable warning. Repos with very long histories no longer crash the scan on memory- constrained CI runners. File-level secret scanning is unaffected by the cap.
Errors now exit with code 2 instead of 1, so an IO, argument, or internal failure is distinguishable from a finding-driven
--fail-onexit. The existing exit code 1 for reported findings is unchanged.vulkro explain <id>no longer crashes in debug builds. The positional argument order was reversed so the required rule ID comes before the optional path, which now defaults to the current directory.
Added
Salesforce scanning. Detects Salesforce DX projects (
sfdx-project.json+.cls/.triggerunder anypackageDirectoriespath) and legacy MDAPI layouts. Scans:- Apex code: twelve security rules including SOQL injection,
secrets, hardcoded IDs, unsafe deserialisation, callout
credentials, weak crypto, runAs misuse, CRUD/FLS bypass (the
core AppExchange Security Review check, both class-level and
per-method), IDOR/BOLA in non-
with sharingclasses, mass assignment viaJSON.deserialize, and open-redirect fromPageReference. Endpoint extraction covers@AuraEnabledmethods and@RestResourceREST endpoints. - Salesforce metadata: audits Profiles, Permission Sets, Named
Credentials, and Connected Apps for over-privileged grants,
hardcoded Named-Credential passwords, cleartext endpoints, and
the broad
FullOAuth scope. - Visualforce pages: XSS from
escape="false"on a dynamic merge value, reflected merge fields inside inline<script>blocks, and<apex:includeScript>loading non-$ResourceURLs. - Lightning Web Components and Aura: manual-DOM XSS
(
lwc:dom="manual"withinnerHTML),eval/Function/document.write, real-provider key literals, credential-shaped identifiers, and credential writes tolocalStorage/sessionStorage. - Flows:
runInModesystem-context execution, hardcoded Salesforce IDs in<stringValue>elements, DML elements running in system mode.
Validated against Salesforce NPSP (~330k LOC of Apex, 250 components) and fflib-apex-common. No new flags or config keys.
- Apex code: twelve security rules including SOQL injection,
secrets, hardcoded IDs, unsafe deserialisation, callout
credentials, weak crypto, runAs misuse, CRUD/FLS bypass (the
core AppExchange Security Review check, both class-level and
per-method), IDOR/BOLA in non-
Dead-code analysis covers unused functions and classes across every language, alongside the existing unused imports, parameters, and locals. A symbol is flagged only when its name appears exactly once project-wide. Conservative entry-point skips: decorated / annotated symbols, exported JS/TS symbols, Apex
globalmembers, top-level Apex classes, and lifecycle method names are never flagged.Control-flow analysis for Python, JavaScript, and TypeScript. Per-function control-flow graph with typed basic blocks and edges (sequential, true, false, exception), cyclomatic complexity, dominator trees, dominance frontiers, and SSA phi-node placement. Powers more precise taint analysis in subsequent releases.
vulkro scan --web. Runs a scan, saves the result into the desktop console's database, and opens the console. CLI scans and UI scans now share one scan history.Per-detector false-positive measurement tool. Reports true-positives, false-positives, and ratio per detector; surfaces real-app precision as the headline number; can fail CI when a detector exceeds an FP/TP threshold.
CVEs tab in the desktop console. Browse dependency CVEs by severity, ecosystem, and package, with search and pagination.
Changed
- Vendored, minified, and generated code is skipped. The single
largest source of false-positive noise on real-world repos. Bundled
files,
*.min.js,@generatedheaders,vendor/andnode_modules/paths are no longer scanned. - Mass-assignment findings are taint-confirmed. They downgrade to Low when no request-data flow is proven.
- CSRF detection no longer treats HTTP-client calls as routes.
- Missing-rate-limiting check is middleware-chain aware instead of line-proximity based.
- Hardcoded-secret detection rejects UUIDs, git SHAs, SRI hashes, and i18n keys, and recognises more monorepo test and fixture paths.
Fixed
- Mixed JS/TS Node projects collect both file types. A repo with
some
.tsfiles no longer silently drops half its source; Express endpoints in TypeScript files were previously missed entirely. console.logand other logging calls no longer flag as PHI. The detector matched short concept lemmas as unbounded substrings; it now matches whole identifiers in the logged arguments only.- Go modules build a connected dependency graph. Import paths
resolve against
go.modinstead of comparing as raw strings, so the codebase-map view is no longer empty for Go projects. - Go modules no longer mis-labelled as ORM Model files. File-kind classification is now language-aware.
skill-install.shreads interactive prompts from the terminal so they work under a pipedcurl ... | bashinstall.
Added
vulkro updateprompts to upgrade the binary before refreshing CVE data when a newer release is available, mirroring the flowvulkro upgradealready had.install.shshows a progress bar when running on a TTY, so long downloads on slow connections no longer look hung.
Detection coverage and quality lift. Roughly doubles the detector
inventory while keeping the recommended --min-confidence high tier
quiet on real applications.
Added
- GraphQL detector suite (22 checks). Introspection in
production, missing depth or query-complexity limits, alias
amplification, fragment cycles, unbounded resolvers without
pagination, missing field-level auth on sensitive output fields,
batched-query DoS, federation
__resolveReferenceauth bypass, plaintextws://subscriptions, persisted-query enforcement, resolver PII leaks, error masking, and N+1 patterns without DataLoader. - gRPC detector suite (15 checks). Reflection without an env gate, insecure non-TLS servers, missing interceptor chain on registered services, unbounded message or concurrent-stream caps, ignored stream cancellation, payload-logging interceptors, internal-error string leakage, metadata used as auth without a verified token, business logic in trailers, externally exposed health endpoints, and proto-marshal output reaching an HTTP response sink.
- WebSocket detector suite (10 checks). Missing origin
verification, no auth before message-handler registration, tokens
in the URL query string, missing per-connection rate limiting,
unbounded payload size, compression-oracle
(
permessage-deflate), default-namespace exposure, missing heartbeat or ping, plaintextws://in non-localhost config, and cross-tenant broadcast. - Dependency-injection antipatterns (8 checks). Across NestJS,
Angular, FastAPI, and
dependency-injector: request-scoped guards captured in singletons,forwardRefsmells, services withprovidedIn: 'root'holding per-user state, HTTP interceptors that send auth to any host,forRootAsyncwith secrets in factory closures, async-factory races, FastAPI dependency generators withouttry/finally close, and captive-database singletons. - Broken-defender family (11 checks). The shape of "code does
validate but the validation is incomplete":
- Discarded permission-check result (
await canRead(...)whose return is thrown away). - Incomplete denylist on session-lifecycle fields.
- Authentication validators returning success on empty input.
- Module-level singleton reused across concurrent OAuth identities.
- Stale permission cache after an
await. - Taint-aware IDOR across tenants.
- GraphQL resolver auth bypass via boolean-OR with a non-security predicate.
- Unbounded recursion (no depth guard) and per-branch visited-set clone (exponential fan-out).
- pg-promise identifier injection via
$N:name/$N:raw. - Prototype-chain traversal of a user-controlled dot-path without
hasOwnPropertyor null-prototype guard. - MIME-extension parsing that trims only whitespace.
- PII flow into third-party analytics SDKs (Mixpanel, Segment,
Sentry
setUser, Amplitude, PostHog, Heap, FullStory).
- Discarded permission-check result (
vulkro scan --jobs <N>. Pin the parallel-scan thread pool size. Combined with the parallel walk that landed in this release, roughly 5x faster cold-cache scans on large repos.vulkro scan --since <git-ref>. Incremental scan. Re-runs extraction and detection only on files changed since the named ref, merging cached findings for unchanged files. A five-file PR diff scans in seconds.[concepts]table invulkro.toml. Extend any detector's vocabulary (custom auth-middleware names, proprietary CSRF libraries, project-specific PII fields, ORM helpers) without forking detector code. Misspelled concept keys produce a clear load-time warning with a "did you mean?" suggestion.- Three opt-in rule packs.
--rule-pack broken-defender,--rule-pack graphql-strict,--rule-pack websocket-stricttighten hygiene beyond the defaults. Off unless explicitly enabled. - Stable rule IDs across every output format. A central detector
inventory ensures SARIF, GitHub PR comments, CSV, PDF, RoPA, and
JUnit all reference the same rule IDs, CWE tags, and OWASP
categories. New
rule_id,cwe, andconcept_slugcolumns in CSV are additive.
Changed
- GraphQL introspection / depth / complexity checks no longer
fire on every
graphene.Field(...)declaration. Only on realSchema(...)instantiation. - Mass-assignment regex no longer matches generic Python
operations like
.extend(...)/.merge(...)/.dict()/.json(). - Cleartext-HTTP check ignores
www.example.com/www.example.org/www.example.net, and any path undertests/fixtures/. - Dead-code analyzer skips
__init__.py,apps.py,conftest.py, and**/migrations/**. - Named-except-pass check exempts
asyncio.CancelledError,RequestAborted,KeyboardInterrupt,StopIteration,StopAsyncIteration, and*.DoesNotExist. - Hardcoded-PII detector skips files under
tests/and lines insidepytest.mark.parametrize(...)argument tuples. - Secret scanner ignores lockfiles. Dependency names containing
token/key/secret(e.g.jsonwebtoken,js-tokens,gtoken) no longer trigger findings insidepackage-lock.json,yarn.lock,pnpm-lock.yaml,composer.lock,Gemfile.lock,poetry.lock,Cargo.lock,go.sum, ornpm-shrinkwrap.json. - Session-fixation detector de-duplicates GET+POST registrations on the same login route, and no longer panics on routes registered on the last line of a file with no trailing newline. The crash previously masked three real SQL-injection true positives on the juice-shop benchmark.
- Baseline / diff identity. Findings now hash by (concept, file, line) instead of (OWASP category, file, line, message). Renaming a finding's message text inside the same concept no longer resets the baseline.
Removed
- Project-wide "no rate limiting" finding that fired at file
line 0 for every project. Replaced by a per-endpoint variant that
fires only on auth-sensitive routes (
/login,/register,/reset,/password,/verify,/otp,/mfa,/token,/auth,/session). - Four maintainability lints demoted out of the security
category:
console.log/print()in non-test code,parseIntwithout radix, "TODO without ticket" tag, "wildcard import". They remain available behindVULKRO_ENABLE_DEAD_CODE_FINDINGS=1/VULKRO_ENABLE_DUPLICATE_CODE_FINDINGS=1.
Major release. New language coverage, a richer auth model, cross-service intelligence, an audit-evidence pack generator, an LSP server.
Added
- Four new language extractors with framework support. Ruby on Rails, Java + Kotlin / Spring Boot, C# / ASP.NET Core, PHP / Laravel. Each with detection (manifest fingerprints + framework probes) and endpoint extraction (route DSL + middleware + decorators / annotations).
- Auth model expansion. Endpoints now carry an authentication tier (Anonymous / User / Admin / Service / Machine / Unknown), per-endpoint scopes, and tenant scoping (Scoped / Global / Unknown). Inferred from decorators, middleware, and annotations across all supported languages.
- Tier-aware IDOR + missing-function-level-auth. Both now consult the endpoint's auth tier and tenant scoping before firing, suppressing findings the visible auth gate already covers. Measured: Gitea high-confidence findings dropped 576 to 12.
- Cross-service correlation.
vulkro portfolionow matches each repo's outgoing HTTP calls against every other repo's endpoints (path-template aware). Emits three new finding kinds: AuthCoherenceMismatch (callee requires auth, caller sends none), AuthOverProvisioning, UnauthenticatedInternalCall. Plus cross-service taint flows for PII / Secret / UntrustedInput data crossing service boundaries. vulkro rbac: emits aMETHOD PATH × AuthTiermatrix in Markdown or JSON.vulkro openapi: generates an OpenAPI 3.1 specification from discovered endpoints, withx-vulkro-tier/-scopes/-tenant-scopedextensions.vulkro compliance-pack --framework soc2|iso27001|hipaa: produces a self-contained audit evidence directory (manifest, summary, findings CSV, per-control JSON, README) mapping findings to specific control IDs.- CVE reachability gating.
vulkro scan --reachable-onlydrops CVE findings whose vulnerable symbols aren't called from first-party code. Off by default. - Incremental scanning. Per-file extraction cache keyed on path,
modification time, and content hash. New
vulkro cache clearsubcommand and--no-cacheflag. 1.5 to 3x speedup on no-change re-scans. - LSP server + VSCode extension scaffold. Diagnostics on save, hover-to-explain, quick-fix code actions.
- Web UI triage features. Bulk-select + suppress + reassign owner, saved views, scan comparison view. New tabs: RBAC matrix, cross-service taint flows, compliance-pack download button, settings with a Clear scan cache button.
Changed
--min-confidencedefault is nowhigh(waslow). The kitchen-sink default produced 59,922 findings on the 8-repo SaaS benchmark; high-confidence emits 523.- Mass-assignment recognises DRF serializers with explicit
fields = [...]allowlists and JS/TS handlers with Zod / Joi / Yup / class-validator / TypeBox-validated input. Suppresses on those legitimate patterns; still fires on unrestricted shapes. - Plain-HTTP URL detector scoped to production config paths
only (
settings.py,.env*,helm/**,docker-compose.yml,appsettings.json,application.yml). Skipstests/,fixtures/,migrations/,*.example, docs. - Python
assertrule suppressed in pytest test files whereassertis the idiomatic pattern. Stops the 44,775-finding flood this previously produced on Saleor's test suite. - Walkers honour
.gitignore/.ignore/.git/info/exclude/ global excludes. Hardcoded skip lists are kept as fallback for repos without.gitignore, expanded to include.next,.nx,.turbo,.cache,coverage,out,.svelte-kit,.vercel,.parcel-cache,.angular,bower_components,__snapshots__. - Python path reconstruction. FastAPI
app.include_router(prefix=...), FlaskBlueprint(url_prefix=...), and DRFurls.pycorrelation now produce the URLs the server actually serves. The prefix was previously dropped.
Fixed
- Bearer 2.x raw output normaliser handles severity buckets at the
top level of the JSON envelope (the shape Bearer 2.x emits), in
addition to the older nested
{ findings: {...} }convention.
Added
- IDOR / BOLA detector for Express, Flask, FastAPI, Django, aiohttp, Bottle.
- CSRF detector for csurf, lusca, flask_wtf.CSRFProtect, aiohttp_csrf, Django CSRF middleware.
- Server-side template injection detector and template-rendered
XSS detector for Jinja2, Mako, Bottle, Handlebars, Pug, Eta,
Mustache, EJS, plus
dangerouslySetInnerHTMLandautoescape=Falsepatterns. - Empirical TP-rate calibration table. Downgrades High-confidence findings to Medium when the benchmark corpus shows a sub-30% true-positive rate.
--min-confidence {low, medium, high}flag (defaulthigh).--allis an alias forlow.--scope srcflag and# vulkro-disable-{next-line,file}inline-suppress pragmas.- Three new desktop UI tabs: Access control, CSRF, Injection.
- Go language support. net/http, gin, echo, chi, fiber.
install.shcurl-pipe installer. Detects OS and architecture, downloads from the release CDN, and verifies SHA-256 checksums.
Changed
- Cross-file taint pass now requires an actual function-boundary crossing before emitting a finding. Eliminates intra-file duplicates.
bug_patterns,sonar_rules, andredosdetectors are AST- driven instead of line-regex. Removes about 8% noise on real codebases.
Fixed
- Compliance map. Corrected a typo across ~40 control entries; CSRF and injection controls now surface findings under the categories the detectors actually emit.
Security
- Dependency bumps. rustls 0.21 to 0.23, webpki-roots 0.25 to 0.26, reqwest 0.11 to 0.12. Closes RUSTSEC-2026-0098, 0099, 0104 (rustls-webpki certificate validation) and RUSTSEC-2023-0071 (RSA Marvin Attack).
Added
- Web dashboard (
vulkro serve) with React and D3.js. Endpoints, Findings, Structure, and Impact tabs. - Scan history stored in a local SQLite database
(
~/.vulkro/scans.db). - SARIF output format for IDE and CI integration.
- Next.js App Router (
app/api/route.ts) and Pages Router (pages/api/) support with dynamic[param]to{param}conversion. - NestJS and Hono framework detection.
- Angular and React SPA detection.
- Monorepo auto-detection (one level deep).
vulkro historyto list past scans.--saveflag onvulkro scanto persist results.- Module dependency graph (
vulkro graph). - Exit code 1 on critical or high findings for CI gating.
- Cross-platform build targets via Makefile.
Changed
- Auth detection heuristics improved to reduce false positives.
Fixed
- Table view full-width layout, no duplicate sidebar.
- Scan progress logs now show during
vulkro scanso the terminal does not appear frozen.
Added
- Initial release.
vulkro discover: endpoint discovery for Express, FastAPI, Flask, Django.vulkro scan: OWASP API Top 10:2023 checks (API1 to API10).- Table and JSON output formats.
- Single self-contained binary.