Skip to main content

Language support

Every language we read, and every one we do not.

This is the page that answers the first question worth asking: how far does the analysis actually go on the code you already have, and what does it not read at all. Depth is stated per language, the limit that bites first is stated with it, and the languages with no parser today are listed by name. You should find that out here, not a week after you install.

  • 5 languages parsed
  • Cross-file taint up to 4 hops
  • Java taint is same-file
  • Scans run locally

01 / Depth, not breadth

The depth matrix

A wall of checkmarks tells you nothing, because every scanner claims every language. This is the table the docs carry: each supported language against each analysis capability, with the cells we cannot fill left visible.

analysis depth by language7 languages x 5 capabilities
Analysis depth for each supported language, by capability. Each cell is full, partial or none.
LanguageRoute mappingentry pointsTaint, same filesource to sinkTaint, across filesvia call graphFramework awarenessrouters, ORMsDedicated detectorslanguage rules
PythonDjango, Flask, FastAPIFullFullFullFullFull
JavaScriptExpress, Koa, Next.jsFullFullFullFullFull
TypeScriptExpress, NestJS, Next.jsFullFullFullFullFull
Gonet/http, Gin, Echo, chiFullFullFullFullFull
Javasame-file taint onlyFullFullNoneFullFull
Apexentry points, not URL routesPartialFullPartialFullFull
Terraform, Dockerfileconfiguration, not dataflowNoneNoneNoneFullFull
  • Fullruns on every scan of that language
  • Partiallimited to the cases named in the row
  • Nonenot analysed at this depth today

Cross-file taint means a source in one file reaching a sink in another through the call graph. Java is same-file today: a tainted value that leaves the method is not followed into the callee's file, so a Java result set is narrower than a Python or Go one on the same codebase.

What the engine does per language. The blank cells are stated, not hidden.

5

Languages with a parser

Python, JavaScript, TypeScript, Go, Java

4

Cross-file taint hops

Python, JavaScript, TypeScript and Go. Java is same-file only.

150+

Security checks

Shared across the supported languages, plus the configuration and dependency passes.

Read the matrix as a floor rather than a ceiling. A cell marked full means the capability runs on every scan of that language, not that it resolves every case a language can express.

02 / Per language

What each language actually gets

The frameworks the engine understands, and the limit that bites first. The limits are the useful half of this section: they tell you what a clean result does not prove.

  • Python

    cross-file, up to 4 hops

    Routes are read from decorators and from URL configuration, so the endpoint inventory is built before any rule runs. Taint follows a request value out of the handler, through helper modules, and into the sink that trusted it, crossing file boundaries through the call graph for up to four hops.

    Frameworks understood
    Django, Django REST Framework, Django Ninja, Flask, FastAPI, Starlette, aiohttp, Tornado, Litestar
    Where it stops
    A route registered by a plugin loader at runtime, or through a factory whose path argument cannot be resolved to a literal, is missing from the inventory. Anything reached only through getattr or a string-keyed registry breaks the call graph, and a path that runs through it is down-ranked rather than reported with a proven trace.
  • JavaScript

    cross-file, up to 4 hops

    Router registrations are resolved through their mount prefixes, including across files, so a handler declared in a sub-router is reported at the path it is actually served on rather than the fragment written beside it. Taint crosses module boundaries: a value read from the request in a controller is followed into the service file and the query builder that consume it.

    Frameworks understood
    Express, Koa, Fastify, Hapi, Hono, Elysia, AdonisJS, tRPC, Next.js, plus React and Angular on the front end
    Where it stops
    A mount whose prefix cannot be resolved to a literal leaves its routes recorded at their local path, which reads as a different endpoint than the one you serve. Dynamic property access and callbacks handed through higher-order helpers can drop a call-graph edge, and a dropped edge means a real path is missed rather than guessed at. Bundled and minified output parses, but a finding in it points at a build artifact rather than a line anyone edits.
  • TypeScript

    cross-file, up to 4 hops

    Treated as its own grammar rather than as JavaScript with the types stripped, which is what lets decorator-driven routing be read directly. The same four-hop cross-file taint applies, and class methods reached through an interface-typed dependency are resolved to their implementations.

    Frameworks understood
    NestJS, Express, Fastify, Next.js route handlers, tRPC, Angular
    Where it stops
    Types are read as syntax, not resolved by a full type checker. Heavily generic indirection, conditional types and re-export chains can lose an edge, and a lost edge narrows the result rather than widening it.
  • Go

    cross-file, up to 4 hops

    Route paths are resolved even when the pattern is assembled from a variable or concatenated, provided the value can be traced to its literal parts. Taint follows a request value across packages and files for up to four hops, and gRPC service definitions are recognised as entry points alongside HTTP handlers.

    Frameworks understood
    net/http, Gin, Echo, chi, gorilla/mux, Fiber, gRPC
    Where it stops
    Interface dispatch where the concrete implementation is only chosen at runtime is not resolved, so a handler wired through such a boundary can be missed. Code behind build tags that were not selected is not analysed.
  • Java

    same-file taint only

    Routing is read from the annotations: a class-level @RequestMapping supplies the base path and the method-level mapping family supplies the verb, so the endpoint inventory and the authorization checks around it are as complete as for any other language. A dedicated Java rule set covers injection, unsafe deserialization, XXE, JNDI, expression-language injection, path traversal and Spring Security configuration.

    Frameworks understood
    Spring Boot, Spring MVC, Spring Security, and JAX-RS resources (a class-level @Path with @GET, @POST, @PUT, @DELETE or @PATCH)
    Where it stops
    Taint is same-file. Resolving a Java import onto the source file it names is not wired yet, so a tainted value that leaves the method is not followed into the callee in another file. On the same codebase a Java result set is narrower than a Python or Go one, and that gap is a missing capability rather than a clean bill of health.
vulkro explain VULK-10424 hops
Dataflow pathSQL injection in order lookupVULK-1042
  1. 01checkout.py:214request handlersource

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

  2. 02services/orders.py:88helper

    passed through lookup_order(order_id) unchanged

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

    concatenated into the WHERE clause with an f-string

  4. 04db/session.py:57sinksink

    cursor.execute(sql) runs the assembled statement

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

Four resolved hops across four files. In Java the same trace stops at the first file boundary.

Apex is not in this list because it belongs to a different binary. Apex, Lightning components, Flow and org metadata are read by Vulkro for Salesforce, which is this same review pointed at a Salesforce build: the same engine, with a rule set written for the platform rather than for the web.

03 / Monorepos and mixed stacks

One repository, several stacks

Most repositories are not one language. A single primary verdict at the root is the usual reason a backend subtree is never scanned, so the project detector does not stop at the root.

A sibling-language sub-project is registered by its own build marker even when an enclosing project has already declared a different primary language. A repository whose root looks like a Node application still has its Go service registered from go.mod, its Java service from pom.xml or build.gradle, and its Python service from pyproject.toml, setup.py, requirements.txt, Pipfile or poetry.lock.

package.json is deliberately not one of those markers: the Node file family is already collected across the whole tree from the root, so recovering it again would only duplicate work. A nested module of the same language under an outer service of that language is pruned for the same reason, while a genuinely separate service in a disjoint subtree is kept. The walk is depth-capped, respects your ignore files, and is bounded by a hard sub-project cap, so it stays cheap on a large repository.

If another tool reported nothing in your backend, this is usually why. The scan found the root, decided the repository was one language, and never descended to the service that mattered. Check the route inventory first: if your backend endpoints are not in it, no rule ever ran on them, and a clean result means nothing.

vulkro discover --format table3 subprojects

487 routes discovered in 3 subprojects (Express, FastAPI, chi). 12 reachable without authentication. Showing 9.

Discovered HTTP routes with handler location, authentication state and finding count.
MethodPathSubprojectHandlerAuthFindings
POST/api/v1/invoices/{id}/refundservices/billingroutes/invoice.ts:47anonymousHIGH2
GET/api/v1/invoices/{id}services/billingroutes/invoice.ts:112authenticatedMED1
POST/api/v1/webhooks/paymentsservices/billingroutes/webhooks.ts:29unknownMED1
POST/checkout/confirmapps/storefrontcheckout.py:214authenticatedCRIT1
GET/cartapps/storefrontapi/cart.py:88anonymousno findings
GET/healthzapps/storefrontapi/health.py:12anonymousno findings
POST/v1/sessionservices/identityinternal/http/session.go:64anonymousno findings
DELETE/v1/session/{sid}services/identityinternal/http/session.go:118authenticatedno findings
GET/internal/admin/usersservices/identityinternal/http/admin.go:31unknownCRIT1
Unknown means the handler runs behind a guard Vulkro could not resolve to a session check. It is listed, not scored.
One repository, three services, three languages. Every route names the subtree it came from.

04 / Not read at all

Languages with no parser today

There is no partial credit here. Where no grammar loads, files are counted in the inventory and skipped, and the report says so rather than reporting a clean subtree.

Languages with no parser today, what happens to their files, and what is still read for them.
Not readWhat happens todayNotes
RubyNo grammar loads for .rb files. They are walked, counted in the file inventory, and skipped.Gemfile.lock is still read for the resolved gem list, so Ruby dependencies are inventoried even though no Ruby source is analysed.
PHPNo grammar loads for .php files. A PHP subtree inside a supported repository is skipped, not partially analysed.Framework routing is where most of the value would be, and none of it is modelled today.
C# and .NETNo grammar loads for .cs files. ASP.NET routing is not read.The enum variant exists in the scanner for future use, but no file is ever classified as C#, so no detector runs on one.
KotlinKotlin source is not parsed. A Kotlin service in a Spring repository is invisible to the code analysis.A Gradle build file still registers the subtree as a project, and a gradle.lockfile beside it is parsed for its resolved dependencies.
RustRust source is not parsed. No route, taint or detector pass runs on it.Cargo.toml and Cargo.lock are read, so Rust dependencies are still resolved to exact versions and inventoried.
C, C++, Swift, Scala, Elixir and the restNo grammar, no rules, no partial coverage. Files in these languages are counted and skipped.A language is listed as supported here only when a grammar loads for its extensions and a rule set exists that is worth running.
Source: the scanner's own language table. A language counts as supported only when a grammar loads for its file extensions and a rule set exists for it.

This is a roadmap fact rather than an apology. Loading a grammar is the cheap half of supporting a language; the expensive half is the framework model, the route extraction and the rule set that make the results worth reading. A language ships here when that second half is done, which is why the supported list is short and why the depth on it is worth quoting.

05 / Beyond application code

The files that are not source code

Plenty of what gets exploited was never written in an application language. These passes do not need a grammar for your stack, so they run whatever the repository is written in.

5 manifest formats are parsed (npm, PyPI, Go modules, crates.io, Maven). Five manifest formats are parsed. The default published bundle currently ships npm and PyPI; the wider signed bundle covers Go modules, crates.io and Maven.

VULKRO_SCA_REACHABLE=1 vulkro scan .offline bundle
  • CRITorg.apache.commons:commons-text@1.9MavenCVE-2022-42889reachable

    pom.xmlStringSubstitutor.replace called from ReportController.render at src/main/java/com/acme/report/ReportController.java:88

  • HIGHlodash@4.17.20npmCVE-2021-23337GHSA-35jh-r3h4-6jhmreachable

    package-lock.jsontemplate() called from buildInvoice at routes/invoice.ts:47, two hops from the POST /invoices route

  • MEDurllib3@1.26.4PyPICVE-2021-33503unknown

    poetry.lockadvisory declares no vulnerable symbols, so no reachability claim is made and the severity is left as matched

  • INFOgithub.com/gin-gonic/gin@1.7.7GoCVE-2023-29401unreachable

    go.sumContext.FileAttachment is not called from the forward closure of any route, main, or plugin hookdowngraded from medium

  • INFOtime@0.1.44crates.ioCVE-2020-26235unreachable

    Cargo.lockpulled in transitively by chrono; no project function in the forward closure calls the affected symboldowngraded from high

Unreachable findings are downgraded, not dropped. Severity falls to Info, the row keeps its call-graph reason, and it stays in the report and in the JSON. A call site missed through dynamic dispatch shows up as down-ranked rather than as absent.

Five manifest formats are parsed. The default published bundle currently ships npm and PyPI; the wider signed bundle covers Go modules, crates.io and Maven.

Dependency findings matched offline, each carrying the manifest it was resolved from.