SAST
Taint Flow
Cross-repository

The problem

Scanners stop at the repository boundary. Untrusted data doesn't.

A search term from a web page passes through three repositories before it reaches a SQL query. Each one calls the next over the network, and the value gets a new name at every hop. A scanner that reads one repository at a time sees a source with no sink, then code with neither, then a sink with no source.

sink reached: SQL query (sqli)
  1. WEBAPP · SVELTESearch page+page.svelte · URLSearchParams.get value: ?q=
  2. POST /api/search
  3. WEBAPP · TYPESCRIPTBFF endpointsearchClient value: token
  4. GraphQL searchByToken
  5. FEDERATION · GOGraphQL resolverQuery.searchByToken value: token
  6. gRPC Account/GetAccount
  7. BACKEND · GOgRPC handler → SQLFindAccountByNumber → Selectx value: req.Number
One repository at a time webapp: source ?q=, leaves over GraphQL. No SQL sink. federation: token arrives, leaves over gRPC. The SQL is in another repository. backend: req.Number reaches SQL, but the route starts at GetAccount. The web page is out of sight. → the route from ?q= to SQL is never reported
All three together, with panopticode ?q= → searchClient → graphql:Query.searchByToken → pb.Account/GetAccount → FindAccountByNumber → Selectx → 1 finding: sqli, with the full route

Solution

Analyse all the repositories as one program.

A frontend reads each repository and writes its code graph (CGF). The engine loads any number of them, matches every gRPC and GraphQL client call to its handler in the other repository by contract name, and runs taint analysis across the joined program. A network call becomes an ordinary function call.

The engine is language-agnostic. It never reads source code, only CGF. Each language is a frontend: Go and TypeScript/Svelte today. Every new frontend adds a language, and the engine doesn't change.

> panopticode deep dive

One repository? No microservices?

Yes, it works there too.

Cross-repository flows are what panopticode adds. Everything under them is ordinary taint analysis, and it runs on a single repository the same way.

One repository · yes Extract one repository and run the engine on that one CGF directory. You get every flow from a source to a sink inside it, with the same routes and the same catalog.
pc-fe build . --out cgf/app
panopticode taint --catalog catalog.example.toml \
    --cgf cgf/app > chains.json
A monolith · yes A single Go module or SvelteKit app is the simplest case: no contracts to join, just the call graph and the value flow inside it. In the example app, the xss and open_redirect findings never leave the web app.

From the blog

Coming soon