Files
Open_Ultra_SAST/src/openultrasast/cpg/queries/config.sc
T
Marius CiepluchandClaude Opus 5 d9ceeb6a09 feat(model-grounded-detection): 4.2 constant abstraction, the third arbiter form
A configuration bug has no flow to follow and no guard to dominate.
CORS(app, origins="*") is dangerous because of the value it is SET TO, so the
arbiter evaluates the setting's arguments against the closed permissive set in
the obligation facts -- the third form beside taint reachability and guard
dominance, and the one Req 6.3 names.

  ENTAILED      a security setting is passed a literal in the permissive set
  CORROBORATED  the argument is computed, so constant abstraction cannot decide
                it -- that residual is the judge's, never silently "safe"
  (none)        every literal is outside the set, or the call is not a setting

Two Joern node-shape bugs, both found by running it and both of the same class
as group 2's receiver bug -- the graph's indexing is not what the source text
suggests:

  * a keyword argument (`origins="*"`) is an assignment-shaped node, so
    isLiteral is false on the argument itself and the literal sits in its
    subtree; literals are collected from the argument's AST now.
  * Joern indexes the receiver at 0, positional arguments from 1, and NAMED
    arguments at -1. Settings are configured overwhelmingly by keyword, so
    argumentIndexGt(0) -- correct when counting arity for the taint shape test
    -- dropped exactly the arguments that matter and every keyword-configured
    setting read as "computed".

Live, deterministic, zero model calls:
  create_app -> model_entailed  CORS(app, origins = "*"): permissive literal "*"
  safe_app   -> no finding      (origins = "https://app.example")

All three arbiter forms now work against a real CPG: taint reachability for the
flow families, guard dominance for absence, constant abstraction for config.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
2026-09-08 15:00:41 +02:00

45 lines
2.2 KiB
Python

// Security settings and the literal values they are given (model-grounded-detection Req 6.3).
//
// A configuration bug has no flow and no guard: `CORS(app, origins="*")` is dangerous because of the value.
// This query reports each setting call with its arguments split into literals (which the model can evaluate)
// and non-literals (which it cannot -- those become `corroborated`, never silently safe).
//
// Parameters: cpgFile, settings, function (optional).
// Output: fenced JSON array of {setting, line, method, args, literalArgs}.
@main def exec(cpgFile: String, settings: String, function: String = "") = {
importCpg(cpgFile)
def split(raw: String): List[String] = raw.split(",").map(_.trim).filter(_.nonEmpty).toList
val names = split(settings)
def matches(name: String): Boolean = names.exists(n => name == n || name == n.split("\\.").last)
val calls = {
val all = cpg.call.filter(c => matches(c.name)).l
if (function.isEmpty) all else all.filter(_.method.name == function)
}
val rows = calls.map { call =>
// Joern indexes the receiver at 0, positional arguments from 1, and NAMED arguments at -1. A setting is
// configured overwhelmingly by keyword (`origins="*"`, `secure=False`), so filtering `argumentIndexGt(0)`
// -- correct when counting arity for the taint shape test -- drops exactly the arguments that matter here
// and every keyword-configured setting reads as "computed".
val args = call.argument.filter(_.argumentIndex != 0).l
ujson.Obj(
"setting" -> call.code.take(200),
"line" -> call.lineNumber.getOrElse(-1).toString,
"method" -> call.method.name,
"args" -> ujson.Arr(args.map(a => ujson.Str(a.code.take(80))): _*),
// A keyword argument (`origins = "*"`) is an assignment-shaped node, so `isLiteral` is false on the
// argument itself and the literal sits inside it. Collect literals from the argument's SUBTREE, or
// every keyword-configured setting reads as "computed" and nothing is ever evaluated.
"literalArgs" -> ujson.Arr(args.flatMap(a => a.ast.isLiteral.code.l).distinct.map(c => ujson.Str(c.take(80))): _*)
)
}
println("---OUSAST-CPG-BEGIN---")
println(ujson.write(ujson.Arr(rows: _*)))
println("---OUSAST-CPG-END---")
}