Merge pull request #20733 from asgerf/js/incremental-api-graphs

JS: Incremental API graph
This commit is contained in:
Asger F
2026-01-14 12:49:41 +01:00
committed by GitHub
32 changed files with 1334 additions and 938 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -192,6 +192,7 @@ module DOM {
* A data flow node or other program element that may refer to
* a DOM element.
*/
overlay[global]
abstract class Element extends Locatable {
ElementDefinition defn;

View File

@@ -29,6 +29,7 @@ class ES2015Module extends Module {
override string getName() { result = this.getFile().getStem() }
/** Gets an export declaration in this module. */
pragma[nomagic]
ExportDeclaration getAnExport() { result.getTopLevel() = this }
overlay[global]
@@ -38,6 +39,7 @@ class ES2015Module extends Module {
/** Holds if this module exports variable `v` under the name `name`. */
overlay[global]
pragma[nomagic]
predicate exportsAs(LexicalName v, string name) { this.getAnExport().exportsAs(v, name) }
override predicate isStrict() {
@@ -345,6 +347,7 @@ abstract class ExportDeclaration extends Stmt, @export_declaration {
/** Holds if this export declaration exports variable `v` under the name `name`. */
overlay[global]
pragma[nomagic]
final predicate exportsAs(LexicalName v, string name) {
this.exportsDirectlyAs(v, name)
or
@@ -821,18 +824,31 @@ class SelectiveReExportDeclaration extends ReExportDeclaration, ExportNamedDecla
result = ExportNamedDeclaration.super.getImportedPath()
}
overlay[global]
pragma[nomagic]
private predicate reExportsFrom(ES2015Module mod, string originalName, string reExportedName) {
exists(ExportSpecifier spec |
spec = this.getASpecifier() and
reExportedName = spec.getExportedName() and
originalName = spec.getLocalName() and
mod = this.getReExportedES2015Module()
)
}
overlay[global]
override predicate reExportsAs(LexicalName v, string name) {
exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() |
this.getReExportedES2015Module().exportsAs(v, spec.getLocalName())
exists(ES2015Module mod, string originalName |
this.reExportsFrom(mod, originalName, name) and
mod.exportsAs(v, originalName)
) and
not (this.isTypeOnly() and v instanceof Variable)
}
overlay[global]
override DataFlow::Node getReExportedSourceNode(string name) {
exists(ExportSpecifier spec | spec = this.getASpecifier() and name = spec.getExportedName() |
result = this.getReExportedES2015Module().getAnExport().getSourceNode(spec.getLocalName())
exists(ES2015Module mod, string originalName |
this.reExportsFrom(mod, originalName, name) and
result = mod.getAnExport().getSourceNode(originalName)
)
}
}

View File

@@ -198,6 +198,7 @@ module ClientRequest {
private string urlPropertyName() { result = "url" or result = "uri" }
/** An API entry-point for the global variable `axios`. */
overlay[local?]
private class AxiosGlobalEntryPoint extends API::EntryPoint {
AxiosGlobalEntryPoint() { this = "axiosGlobal" }

View File

@@ -6,6 +6,7 @@ private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
/** Provides classes and predicates modeling aspects of the `d3` library. */
module D3 {
/** The global variable `d3` as an entry point for API graphs. */
overlay[local?]
private class D3GlobalEntry extends API::EntryPoint {
D3GlobalEntry() { this = "D3GlobalEntry" }

View File

@@ -41,6 +41,7 @@ module Electron {
BrowserView() { this = DataFlow::moduleMember("electron", "BrowserView").getAnInstantiation() }
}
overlay[local?]
private class ElectronEntryPoint extends API::EntryPoint {
ElectronEntryPoint() { this = "Electron.Browser" }

View File

@@ -94,6 +94,7 @@ module EventRegistration {
/**
* A registration of an event handler on an EventEmitter.
*/
overlay[global]
abstract class Range extends DataFlow::Node {
EventEmitter::Range emitter;
@@ -148,6 +149,7 @@ module EventDispatch {
/**
* A dispatch of an event on an EventEmitter.
*/
overlay[global]
abstract class Range extends DataFlow::Node {
EventEmitter::Range emitter;

View File

@@ -5,6 +5,7 @@ import javascript
/** Provides classes modeling the [`history`](https://npmjs.org/package/history) library. */
module History {
/** The global variable `HistoryLibrary` as an entry point for API graphs. */
overlay[local?]
private class HistoryGlobalEntry extends API::EntryPoint {
HistoryGlobalEntry() { this = "HistoryLibrary" }

View File

@@ -13,6 +13,7 @@ private module Immutable {
/**
* An API entrypoint for the global `Immutable` variable.
*/
overlay[local?]
private class ImmutableGlobalEntry extends API::EntryPoint {
ImmutableGlobalEntry() { this = "ImmutableGlobalEntry" }

View File

@@ -32,6 +32,7 @@ private module Console {
/**
* An API entrypoint for the global `console` variable.
*/
overlay[local?]
private class ConsoleGlobalEntry extends API::EntryPoint {
ConsoleGlobalEntry() { this = "ConsoleGlobalEntry" }

View File

@@ -140,6 +140,7 @@ module NestJS {
}
/** API node entry point for custom implementations of `ValidationPipe` (a common pattern). */
overlay[local?]
private class ValidationNodeEntry extends API::EntryPoint {
ValidationNodeEntry() { this = "ValidationNodeEntry" }

View File

@@ -260,6 +260,7 @@ module NodeJSLib {
DataFlow::Node getRouteHandlerNode() { result = handler }
}
overlay[global]
abstract private class HeaderDefinition extends Http::Servers::StandardHeaderDefinition {
ResponseNode r;

View File

@@ -1099,6 +1099,7 @@ module Redux {
* Used to catch cases where the `connect` function was not recognized by API graphs (usually because of it being
* wrapped in another function, which API graphs won't look through).
*/
overlay[local?]
private class HeuristicConnectEntryPoint extends API::EntryPoint {
HeuristicConnectEntryPoint() { this = "react-redux-connect" }

View File

@@ -16,6 +16,7 @@ module SQL {
* An dataflow node that sanitizes a string to make it safe to embed into
* a SQL command.
*/
overlay[global]
abstract class SqlSanitizer extends DataFlow::Node {
DataFlow::Node input;
DataFlow::Node output;

View File

@@ -33,7 +33,7 @@ module Templating {
*/
bindingset[prefix]
string getDelimiterMatchingRegexpWithPrefix(string prefix) {
result = "(?s)" + prefix + "(" + concat("\\Q" + getADelimiter() + "\\E", "|") + ").*"
result = "(?s)" + prefix + "(" + strictconcat("\\Q" + getADelimiter() + "\\E", "|") + ").*"
}
/** A placeholder tag for a templating engine. */
@@ -703,7 +703,7 @@ module Templating {
*
* These API nodes are used in the `getTemplateInput` predicate.
*/
overlay[global]
overlay[local?]
private class IncludeFunctionAsEntryPoint extends API::EntryPoint {
IncludeFunctionAsEntryPoint() { this = "IncludeFunctionAsEntryPoint" }

View File

@@ -11,6 +11,7 @@ private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
* Module for working with uses of the [Trusted Types API](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API).
*/
module TrustedTypes {
overlay[local?]
private class TrustedTypesEntry extends API::EntryPoint {
TrustedTypesEntry() { this = "TrustedTypesEntry" }

View File

@@ -7,6 +7,7 @@ import semmle.javascript.ViewComponentInput
module Vue {
/** The global variable `Vue`, as an API graph entry point. */
overlay[local?]
private class GlobalVueEntryPoint extends API::EntryPoint {
GlobalVueEntryPoint() { this = "VueEntryPoint" }
@@ -18,6 +19,7 @@ module Vue {
*
* This `EntryPoint` is used by `SingleFileComponent::getOwnOptions()`.
*/
overlay[local?]
private class VueExportEntryPoint extends API::EntryPoint {
VueExportEntryPoint() { this = "VueExportEntryPoint" }
@@ -437,6 +439,7 @@ module Vue {
*
* This entry point is used in `SingleFileComponent::getComponentRef()`.
*/
overlay[local?]
private class VueFileImportEntryPoint extends API::EntryPoint {
VueFileImportEntryPoint() { this = "VueFileImportEntryPoint" }

View File

@@ -5,6 +5,7 @@
private import javascript
/** Treats `Response` as an entry point for API graphs. */
overlay[local?]
private class ResponseEntryPoint extends API::EntryPoint {
ResponseEntryPoint() { this = "global.Response" }
@@ -12,6 +13,7 @@ private class ResponseEntryPoint extends API::EntryPoint {
}
/** Treats `Headers` as an entry point for API graphs. */
overlay[local?]
private class HeadersEntryPoint extends API::EntryPoint {
HeadersEntryPoint() { this = "global.Headers" }

View File

@@ -48,6 +48,7 @@ private predicate areLibrariesCompatible(
}
/** Treats `WebSocket` as an entry point for API graphs. */
overlay[local?]
private class WebSocketEntryPoint extends API::EntryPoint {
WebSocketEntryPoint() { this = "global.WebSocket" }
@@ -55,6 +56,7 @@ private class WebSocketEntryPoint extends API::EntryPoint {
}
/** Treats `SockJS` as an entry point for API graphs. */
overlay[local?]
private class SockJSEntryPoint extends API::EntryPoint {
SockJSEntryPoint() { this = "global.SockJS" }

View File

@@ -9,6 +9,7 @@ private import javascript
*/
module Webix {
/** The global variable `webix` as an entry point for API graphs. */
overlay[local?]
private class WebixGlobalEntry extends API::EntryPoint {
WebixGlobalEntry() { this = "WebixGlobalEntry" }

View File

@@ -492,6 +492,7 @@ private predicate invocationMatchesCallSiteFilter(
Specific::invocationMatchesExtraCallSiteFilter(invoke, token)
}
overlay[local?]
private class TypeModelUseEntry extends API::EntryPoint {
private string type;
@@ -505,6 +506,7 @@ private class TypeModelUseEntry extends API::EntryPoint {
API::Node getNodeForType(string type_) { type = type_ and result = this.getANode() }
}
overlay[local?]
private class TypeModelDefEntry extends API::EntryPoint {
private string type;

View File

@@ -93,6 +93,7 @@ private predicate parseRelevantTypeString(string rawType, string package, string
}
/** Holds if `global` is a global variable referenced via a the `global` package in a CSV row. */
overlay[local]
private predicate isRelevantGlobal(string global) {
exists(AccessPath path, AccessPathToken token |
isRelevantFullPath("global", path) and
@@ -103,6 +104,7 @@ private predicate isRelevantGlobal(string global) {
}
/** An API graph entry point for global variables mentioned in a model. */
overlay[local?]
private class GlobalApiEntryPoint extends API::EntryPoint {
string global;

View File

@@ -114,6 +114,7 @@ class ClientSideRemoteFlowKind extends string {
* `name` and `address` of global variable `user` should be considered as remote flow sources with
* source type "user input".
*/
overlay[local?]
private class RemoteFlowSourceAccessPath extends JsonString {
string sourceType;
@@ -167,6 +168,7 @@ private class RemoteFlowSourceAccessPath extends JsonString {
* The global variable referenced by a `RemoteFlowSourceAccessPath`, declared as an API
* entry point.
*/
overlay[local?]
private class ExternalRemoteFlowSourceSpecEntryPoint extends API::EntryPoint {
string name;

View File

@@ -129,6 +129,7 @@ module SecondOrderCommandInjection {
/**
* A sink that invokes a command described by the `VulnerableCommand` class.
*/
overlay[global]
abstract class VulnerableCommandSink extends Sink {
VulnerableCommand cmd;

View File

@@ -194,6 +194,7 @@ module TaintedPath {
* There are currently four flow labels, representing the different combinations of
* normalization and absoluteness.
*/
overlay[global]
abstract class PosixPath extends DataFlow::FlowLabel {
Normalization normalization;
Relativeness relativeness;

View File

@@ -101,6 +101,7 @@ module UnsafeHtmlConstruction {
* A sink for `js/html-constructed-from-input` that constructs some HTML where
* that HTML is later used in `xssSink`.
*/
overlay[global]
abstract class XssSink extends Sink {
DomBasedXss::Sink xssSink;