2026-09-08 15:00:41 +02:00
|
|
|
// 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).
|
|
|
|
|
//
|
2026-09-09 08:46:21 +02:00
|
|
|
// Parameters: cpgFile, settings, function (optional), file (optional: restrict to this source file).
|
|
|
|
|
//
|
|
|
|
|
// `file` matters more than it looks. A region with no enclosing function -- a module of settings, an
|
|
|
|
|
// __init__.py -- used to send function="" and match EVERY setting in the repository, and the driver then
|
|
|
|
|
// attributed the whole answer to whichever region asked. One `host='0.0.0.0'` in app.py became eight
|
|
|
|
|
// identical entailed findings in eight files that do not contain it.
|
2026-09-08 15:00:41 +02:00
|
|
|
// Output: fenced JSON array of {setting, line, method, args, literalArgs}.
|
|
|
|
|
|
2026-09-09 07:57:35 +02:00
|
|
|
// BATCHED, like taint.sc. Single-request form kept for the committed measurements.
|
|
|
|
|
|
2026-09-09 16:27:58 +02:00
|
|
|
@main def exec(
|
|
|
|
|
cpgFile: String,
|
|
|
|
|
settings: String = "",
|
|
|
|
|
function: String = "",
|
|
|
|
|
file: String = "",
|
|
|
|
|
requests: String = "",
|
|
|
|
|
requestsFile: String = ""
|
|
|
|
|
) = {
|
2026-09-08 15:00:41 +02:00
|
|
|
importCpg(cpgFile)
|
|
|
|
|
|
2026-09-09 16:27:58 +02:00
|
|
|
// The batch arrives as a FILE. A single command-line argument is capped at 128KB on Linux and a
|
|
|
|
|
// repository's requests are megabytes, so passing them as `--param requests=` failed with E2BIG and the
|
|
|
|
|
// driver read the empty result as "no rows". `requests` is kept for the single-request forms and the tests.
|
|
|
|
|
val requestsJson =
|
|
|
|
|
if (requestsFile.nonEmpty) scala.io.Source.fromFile(requestsFile).mkString
|
|
|
|
|
else requests
|
|
|
|
|
|
2026-09-08 15:00:41 +02:00
|
|
|
def split(raw: String): List[String] = raw.split(",").map(_.trim).filter(_.nonEmpty).toList
|
2026-09-09 07:57:35 +02:00
|
|
|
|
2026-09-09 08:46:21 +02:00
|
|
|
def rowsFor(settingsS: String, functionS: String, fileS: String): List[ujson.Obj] = {
|
2026-09-09 07:57:35 +02:00
|
|
|
val names = split(settingsS)
|
|
|
|
|
val function = functionS
|
2026-09-08 15:00:41 +02:00
|
|
|
|
|
|
|
|
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
|
2026-09-09 08:46:21 +02:00
|
|
|
val inFile = if (fileS.isEmpty) all else all.filter(_.method.filename.endsWith(fileS))
|
|
|
|
|
if (function.isEmpty) inFile else inFile.filter(_.method.name == function)
|
2026-09-08 15:00:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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))): _*)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-09 07:57:35 +02:00
|
|
|
rows
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-08 15:00:41 +02:00
|
|
|
println("---OUSAST-CPG-BEGIN---")
|
2026-09-09 16:27:58 +02:00
|
|
|
if (requestsJson.nonEmpty) {
|
|
|
|
|
val parsed = ujson.read(requestsJson).obj
|
2026-09-09 07:57:35 +02:00
|
|
|
val answers = parsed.map { case (id, req) =>
|
|
|
|
|
def field(name: String): String = req.obj.get(name).map(_.str).getOrElse("")
|
2026-09-09 08:46:21 +02:00
|
|
|
id -> ujson.Arr(rowsFor(field("settings"), field("function"), field("file")): _*)
|
2026-09-09 07:57:35 +02:00
|
|
|
}
|
|
|
|
|
println(ujson.write(ujson.Obj.from(answers)))
|
|
|
|
|
} else {
|
2026-09-09 08:46:21 +02:00
|
|
|
println(ujson.write(ujson.Arr(rowsFor(settings, function, file): _*)))
|
2026-09-09 07:57:35 +02:00
|
|
|
}
|
2026-09-08 15:00:41 +02:00
|
|
|
println("---OUSAST-CPG-END---")
|
|
|
|
|
}
|