| description | Guidance for developing CodeQL queries targeting JavaScript and TypeScript code |
|---|
For general CodeQL query development guidance, see Common Query Development.
import javascript
import DataFlow::DataFlow as DataFlow
import DataFlow::TaintTracking as TaintTracking
import semmle.javascript.security.dataflow.SqlInjection
import semmle.javascript.frameworks.Express
import semmle.javascript.frameworks.React- Classes:
ClassDefinitionwithVarDeclfor class names - Functions:
FunctionDeclStmt,FunctionExpr,ArrowFunctionExpr - Methods:
MethodDefinitionfor class methods,ClassInitializedMemberfor class members - Statements:
BlockStmt,DeclStmt,ExprStmt,IfStmt,ForStmt,WhileStmt,TryStmt,ReturnStmt - Expressions:
MethodCallExpr,NewExpr,VarRef,AssignExpr,BinaryExpr,UpdateExpr,IndexExpr - Declarations:
VariableDeclarator,SimpleParameterfor function parameters - Literals:
Literal,ArrayExpr,ObjectExpr,TemplateLiteral - Property Access:
DotExprfor property access,Labelfor property names - Control Flow:
CatchClausefor exception handling,Propertyfor object properties
FunctionDeclStmt- Function declarationsVarDeclStmt- Variable declarationsExprStmt- Expression statementsIfStmt- Conditional statementsForStmt/WhileStmt- Loop statements
// Express route handlers
predicate isExpressRouteHandler(Function f) {
exists(MethodCallExpr call |
call.getReceiver().(VarAccess).getName() = "app" and
call.getMethodName() in ["get", "post", "put", "delete", "use"] and
call.getAnArgument() = f
)
}
// Express middleware pattern
predicate isExpressMiddleware(Function f) {
f.getNumParameter() = 3 and
f.getParameter(0).getName() = "req" and
f.getParameter(1).getName() = "res" and
f.getParameter(2).getName() = "next"
}predicate isReactComponent(Function f) {
exists(ReturnStmt ret |
ret.getContainer() = f and
ret.getExpr() instanceof JSXElement
)
}
predicate isReactHook(CallExpr call) {
call.getCalleeName().matches("use%")
}predicate isDOMSink(Expr expr) {
exists(PropAccess pa | pa = expr |
pa.getPropertyName() in ["innerHTML", "outerHTML", "insertAdjacentHTML"] or
pa.getBase().(CallExpr).getCalleeName() in ["getElementById", "querySelector"]
)
}class XssConfiguration extends TaintTracking::Configuration {
XssConfiguration() { this = "XssConfiguration" }
override predicate isSource(DataFlow::Node source) {
// HTTP request parameters
source instanceof Express::RequestInputAccess
}
override predicate isSink(DataFlow::Node sink) {
// DOM manipulation sinks
isDOMSink(sink.asExpr())
}
}predicate isSqlQuery(CallExpr call) {
exists(string method | method in ["query", "execute", "prepare"] |
call.getCalleeName() = method and
call.getReceiver().getType().hasQualifiedName("mysql", "Connection")
)
}predicate isPrototypePollution(AssignExpr assign) {
exists(PropAccess pa | pa = assign.getLhs() |
pa.getPropertyName() = "prototype" or
pa.getPropertyName() = "__proto__"
)
}// Request data sources
DataFlow::SourceNode expressRequestSource() {
result = DataFlow::parameterNode(any(Express::RouteHandler rh).getRequestParameter())
}
// Response sinks
predicate isExpressResponseSink(DataFlow::Node sink) {
exists(MethodCallExpr call |
call.getReceiver().(VarAccess).getName() = "res" and
call.getMethodName() in ["send", "json", "render"] and
sink.asExpr() = call.getAnArgument()
)
}// Dangerous React patterns
predicate isDangerouslySetInnerHTML(JSXElement jsx) {
jsx.getAnAttribute().getName() = "dangerouslySetInnerHTML"
}
// State updates
predicate isStateUpdate(CallExpr call) {
call.getCalleeName().matches("set%") and
exists(CallExpr useState | useState.getCalleeName() = "useState" |
DataFlow::flow(DataFlow::valueNode(useState), DataFlow::valueNode(call.getReceiver()))
)
}// NON_COMPLIANT: XSS vulnerability
app.get('/user/:id', (req, res) => {
const userId = req.params.id
res.send(`<h1>User: ${userId}</h1>`) // Direct interpolation
})
// COMPLIANT: Proper escaping
app.get('/user/:id', (req, res) => {
const userId = req.params.id
res.render('user', { userId }) // Template engine handles escaping
})/**
* @name Reflected XSS
* @description User input flows into HTTP response without sanitization
* @kind path-problem
* @problem.severity error
* @security-severity 6.1
* @precision high
* @id js/reflected-xss
* @tags security
* external/cwe/cwe-079
*/
import javascript
import DataFlow::PathGraph
class ReflectedXssConfiguration extends TaintTracking::Configuration {
ReflectedXssConfiguration() { this = "ReflectedXssConfiguration" }
override predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node sink) {
sink instanceof XssSink
}
}
from ReflectedXssConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.",
source.getNode(), "user-provided value"- Use specific call patterns: Prefer
MethodCallExproverCallExprwhen targeting method calls - Leverage framework libraries: Use Express, React modules for better precision
- Consider async patterns: JavaScript's async nature requires careful data flow analysis
- Handle dynamic property access: Account for bracket notation property access