diff --git a/unified/ql/lib/ext/legacy-swift.model.yml b/unified/ql/lib/ext/legacy-swift.model.yml index d262fadcec1d..2a9e33425f5e 100644 --- a/unified/ql/lib/ext/legacy-swift.model.yml +++ b/unified/ql/lib/ext/legacy-swift.model.yml @@ -111,13 +111,13 @@ extensions: - ["", "", false, "sqlite3_bind_text16(_:_:_:_:_:)", "", "", "Argument[2]", "database-store", "manual"] - ["", "", false, "sqlite3_bind_text64(_:_:_:_:_:_:)", "", "", "Argument[2]", "database-store", "manual"] - ["", "", false, "sqlite3_bind_value(_:_:_:)", "", "", "Argument[2]", "database-store", "manual"] - - ["", "", false, "sqlite3_exec(_:_:_:_:_:)", "", "", "Argument[1]", "database-store", "manual"] - - ["", "", false, "sqlite3_prepare(_:_:_:_:_:)", "", "", "Argument[1]", "database-store", "manual"] - - ["", "", false, "sqlite3_prepare16(_:_:_:_:_:)", "", "", "Argument[1]", "database-store", "manual"] - - ["", "", false, "sqlite3_prepare16_v2(_:_:_:_:_:)", "", "", "Argument[1]", "database-store", "manual"] - - ["", "", false, "sqlite3_prepare16_v3(_:_:_:_:_:)", "", "", "Argument[1]", "database-store", "manual"] - - ["", "", false, "sqlite3_prepare_v2(_:_:_:_:_:)", "", "", "Argument[1]", "database-store", "manual"] - - ["", "", false, "sqlite3_prepare_v3(_:_:_:_:_:_:)", "", "", "Argument[1]", "database-store", "manual"] + - ["", "", false, "sqlite3_exec(_:_:_:_:_:)", "", "", "Argument[1]", "sql-injection", "manual"] + - ["", "", false, "sqlite3_prepare(_:_:_:_:_:)", "", "", "Argument[1]", "sql-injection", "manual"] + - ["", "", false, "sqlite3_prepare_v2(_:_:_:_:_:)", "", "", "Argument[1]", "sql-injection", "manual"] + - ["", "", false, "sqlite3_prepare_v3(_:_:_:_:_:_:)", "", "", "Argument[1]", "sql-injection", "manual"] + - ["", "", false, "sqlite3_prepare16(_:_:_:_:_:)", "", "", "Argument[1]", "sql-injection", "manual"] + - ["", "", false, "sqlite3_prepare16_v2(_:_:_:_:_:)", "", "", "Argument[1]", "sql-injection", "manual"] + - ["", "", false, "sqlite3_prepare16_v3(_:_:_:_:_:_:)", "", "", "Argument[1]", "sql-injection", "manual"] - ["", "", false, "sqlite3_database_file_object(_:)", "", "", "Argument[0]", "path-injection", "manual"] - ["", "", false, "sqlite3_filename_database(_:)", "", "", "Argument[0]", "path-injection", "manual"] - ["", "", false, "sqlite3_filename_journal(_:)", "", "", "Argument[0]", "path-injection", "manual"] @@ -230,13 +230,23 @@ extensions: - ["", "Connection", true, "keyAndMigrate(_:db:)", "", "", "Argument[0]", "encryption-key", "manual"] - ["", "Connection", true, "rekey(_:db:)", "", "", "Argument[0]", "encryption-key", "manual"] - ["", "Connection", true, "sqlcipher_export(_:key:)", "", "", "Argument[key:]", "encryption-key", "manual"] - - ["", "Connection", true, "execute(_:)", "", "", "Argument[0]", "database-store", "manual"] - - ["", "Connection", true, "prepare(_:_:)", "", "", "Argument[0]", "database-store", "manual"] - - ["", "Connection", true, "prepare(_:_:)", "", "", "Argument[1]", "database-store", "manual"] - ["", "Connection", true, "run(_:_:)", "", "", "Argument[0]", "database-store", "manual"] - ["", "Connection", true, "run(_:_:)", "", "", "Argument[1]", "database-store", "manual"] - ["", "Connection", true, "scalar(_:_:)", "", "", "Argument[0]", "database-store", "manual"] - ["", "Connection", true, "scalar(_:_:)", "", "", "Argument[1]", "database-store", "manual"] + - ["", "Connection", true, "execute(_:)", "", "", "Argument[0]", "sql-injection", "manual"] + - ["", "Connection", true, "prepare(_:)", "", "", "Argument[0]", "sql-injection", "manual"] + - ["", "Connection", true, "prepare(_:_:)", "", "", "Argument[0]", "sql-injection", "manual"] + - ["", "Statement", true, "init(_:_:)", "", "", "Argument[1]", "sql-injection", "manual"] + - ["", "SQLRequest", true, "init(stringLiteral:)", "", "", "Argument[stringLiteral:]", "sql-injection", "manual"] + - ["", "SQLRequest", true, "init(unicodeScalarLiteral:)", "", "", "Argument[unicodeScalarLiteral:]", "sql-injection", "manual"] + - ["", "SQLRequest", true, "init(extendedGraphemeClusterLiteral:)", "", "", "Argument[extendedGraphemeClusterLiteral:]", "sql-injection", "manual"] + - ["", "SQLRequest", true, "init(stringInterpolation:)", "", "", "Argument[stringInterpolation:]", "sql-injection", "manual"] + - ["", "SQL", true, "init(stringLiteral:)", "", "", "Argument[stringLiteral:]", "sql-injection", "manual"] + - ["", "SQL", true, "init(unicodeScalarLiteral:)", "", "", "Argument[unicodeScalarLiteral:]", "sql-injection", "manual"] + - ["", "SQL", true, "init(extendedGraphemeClusterLiteral:)", "", "", "Argument[extendedGraphemeClusterLiteral:]", "sql-injection", "manual"] + - ["", "SQL", true, "init(stringInterpolation:)", "", "", "Argument[stringInterpolation:]", "sql-injection", "manual"] + - ["", "StatementCache", true, "statement(_:)", "", "", "Argument[0]", "sql-injection", "manual"] - ["", "Connection", true, "init(_:readonly:)", "", "", "Argument[0]", "path-injection", "manual"] - ["", "Connection.Location", true, "uri(_:parameters:)", "", "", "Argument[0]", "path-injection", "manual"] - ["", "Database", true, "changePassphrase(_:)", "", "", "Argument[0]", "encryption-key", "manual"] @@ -341,7 +351,6 @@ extensions: - ["", "SQL", true, "init(arguments:sql:)", "", "", "Argument[arguments:]", "database-store", "manual"] - ["", "SQLRequest", true, "init(adapter:arguments:cached:sql:)", "", "", "Argument[arguments:]", "database-store", "manual"] - ["", "SQLStatementCursor", true, "init(arguments:database:prepFlags:sql:)", "", "", "Argument[arguments:]", "database-store", "manual"] - - ["", "Statement", true, "init(_:_:)", "", "", "Argument[1]", "database-store", "manual"] - ["", "Statement", true, "scalar(_:)", "", "", "Argument[0]", "database-store", "manual"] - ["", "Statement", true, "bind(_:)", "", "", "Argument[0]", "database-store", "manual"] - ["", "Statement", true, "execute(arguments:)", "", "", "Argument[arguments:]", "database-store", "manual"] diff --git a/unified/ql/src/queries/security/CWE-089/SqlInjection.qhelp b/unified/ql/src/queries/security/CWE-089/SqlInjection.qhelp new file mode 100644 index 000000000000..6975d95d48a1 --- /dev/null +++ b/unified/ql/src/queries/security/CWE-089/SqlInjection.qhelp @@ -0,0 +1,38 @@ + + + + +

+If a database query (such as a SQL query) is built from user-provided data without sufficient sanitization, a user may be able to run malicious database queries. An attacker can craft the part of the query they control to change the overall meaning of the query. +

+ +
+ + +

+Most database connector libraries offer a way to safely embed untrusted data into a query using query parameters or prepared statements. You should use these features to build queries, rather than string concatenation or similar methods. You can also escape (sanitize) user-controlled strings so that they can be included directly in an SQL command. A library function should be used for escaping, because this approach is only safe if the escaping function is robust against all possible inputs. +

+ +
+ + +

In the following examples, an SQL query is prepared using string interpolation to directly include a user-controlled value userControlledString in the query. An attacker could craft userControlledString to change the overall meaning of the SQL query. +

+ + + +

A better way to do this is with a prepared statement, binding userControlledString to a parameter of that statement. An attacker who controls userControlledString now cannot change the overall meaning of the query. +

+ + + +
+ + +
  • Wikipedia: SQL injection.
  • +
  • OWASP: SQL Injection Prevention Cheat Sheet.
  • + +
    +
    diff --git a/unified/ql/src/queries/security/CWE-089/SqlInjection.ql b/unified/ql/src/queries/security/CWE-089/SqlInjection.ql new file mode 100644 index 000000000000..62872779069d --- /dev/null +++ b/unified/ql/src/queries/security/CWE-089/SqlInjection.ql @@ -0,0 +1,76 @@ +/** + * @name Database query built from user-controlled sources + * @description Building a database query from user-controlled sources is vulnerable to insertion of malicious code by the user. + * @kind path-problem + * @problem.severity error + * @security-severity 8.8 + * @precision high + * @id unified/swift/sql-injection + * @tags security + * external/cwe/cwe-089 + */ + +import unified +private import codeql.unified.internal.typeinference.TypeInference as TypeInference + +private predicate sqliteSwiftSink(DataFlow::Node node) { + // TODO: Use MaD once legacy selectors distinguish Connection methods from Statement bindings. + exists(CallExpr call, FunctionDeclaration target | + target = TypeInference::resolveCallTarget(call) and + target.getEnclosingClass().getName() = "Connection" and + target.getName() = ["run", "scalar"] and + node.asExpr() = call.getArgument(0).getValue() + ) +} + +/** + * An SQL injection sink that is determined by imprecise methods. + */ +private predicate heuristicSink(DataFlow::Node node) { + exists(Argument arg | + node.asExpr() = arg.getValue() and + ( + // by argument name + arg.getName() = "sql" + or + // by parameter name + // TODO: make a helper predicate `Argument.getParameter` for this? + exists(CallExpr call, int index, Parameter param | + arg = call.getArgument(index) and + param = TypeInference::resolveCallTarget(call).getParameter(index) and + param.getPattern().(Identifier).getValue() = "sql" + ) + ) + ) +} + +/** + * A taint configuration for tainted data that reaches a SQL sink. + */ +module SqlInjectionConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { Models::isSource(node, _) } + + predicate isSink(DataFlow::Node node) { + Models::isSink(node, "sql-injection") + or + sqliteSwiftSink(node) + or + heuristicSink(node) + } + + predicate isBarrier(DataFlow::Node node) { + // TODO: Port the numeric-type barrier from original Swift's SqlInjectionExtensions.qll. + none() + } + + predicate observeDiffInformedIncrementalMode() { any() } +} + +module SqlInjectionFlow = TaintTracking::Global; + +import SqlInjectionFlow::PathGraph + +from SqlInjectionFlow::PathNode sourceNode, SqlInjectionFlow::PathNode sinkNode +where SqlInjectionFlow::flowPath(sourceNode, sinkNode) +select sinkNode.getNode(), sourceNode, sinkNode, "This query depends on a $@.", + sourceNode.getNode(), "user-provided value" diff --git a/unified/ql/src/queries/security/CWE-089/SqlInjectionBad.swift b/unified/ql/src/queries/security/CWE-089/SqlInjectionBad.swift new file mode 100644 index 000000000000..085b1b547983 --- /dev/null +++ b/unified/ql/src/queries/security/CWE-089/SqlInjectionBad.swift @@ -0,0 +1,12 @@ +// with SQLite.swift + +let unsafeQuery = "SELECT * FROM users WHERE username='\(userControlledString)'" + +try db.execute(unsafeQuery) // BAD + +let stmt = try db.prepare(unsafeQuery) // also BAD +try stmt.run() + +// with SQLite3 C API + +let result = sqlite3_exec(db, unsafeQuery, nil, nil, nil) // BAD diff --git a/unified/ql/src/queries/security/CWE-089/SqlInjectionGood.swift b/unified/ql/src/queries/security/CWE-089/SqlInjectionGood.swift new file mode 100644 index 000000000000..8d4228fca50a --- /dev/null +++ b/unified/ql/src/queries/security/CWE-089/SqlInjectionGood.swift @@ -0,0 +1,19 @@ +// with SQLite.swift + +let safeQuery = "SELECT * FROM users WHERE username=?" + +let stmt = try db.prepare(safeQuery, userControlledString) // GOOD +try stmt.run() + +// with sqlite3 C API + +var stmt2: OpaquePointer? + +if (sqlite3_prepare_v2(db, safeQuery, -1, &stmt2, nil) == SQLITE_OK) { + if (sqlite3_bind_text(stmt2, 1, userControlledString, -1, SQLITE_TRANSIENT) == SQLITE_OK) { // GOOD + let result = sqlite3_step(stmt2) + + // ... + } + sqlite3_finalize(stmt2) +} diff --git a/unified/ql/test/query-tests/security/CWE-089/SqlInjection/GRDB.swift b/unified/ql/test/query-tests/security/CWE-089/SqlInjection/GRDB.swift new file mode 100644 index 000000000000..3bdffaa272b4 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-089/SqlInjection/GRDB.swift @@ -0,0 +1,360 @@ +// --- stubs --- + +struct URL +{ + init?(string: String) {} +} + +extension String { + init(contentsOf: URL) throws { + let data = "" + + // ... + + self.init(data) + } +} + +struct StatementArguments {} +class Statement {} +protocol RowAdapter {} +class RowDecoder {} +enum GeneratedColumnQualification { case virtual } +struct QueryInterfaceRequest {} + +class Database { + func allStatements(sql: String, arguments: StatementArguments? = nil) -> SQLStatementCursor { return SQLStatementCursor(database: self, sql: "", arguments: nil) } + func cachedStatement(sql: String) -> Statement { return Statement() } + func internalCachedStatement(sql: String) -> Statement { return Statement() } + func execute(sql: String, arguments: StatementArguments = StatementArguments()) {} + func makeStatement(sql: String) -> Statement { return Statement() } + func makeStatement(sql: String, prepFlags: CUnsignedInt) -> Statement { return Statement() } +} + +struct SQLRequest { + init(stringLiteral: String) {} + init(unicodeScalarLiteral: String) {} + init(extendedGraphemeClusterLiteral: String) {} + init(stringInterpolation: String) {} + init(sql: String, arguments: StatementArguments = StatementArguments(), adapter: (any RowAdapter)? = nil, cached: Bool = false) {} +} + +struct SQL { + init(stringLiteral: String) {} + init(unicodeScalarLiteral: String) {} + init(extendedGraphemeClusterLiteral: String) {} + init(stringInterpolation: String) {} + init(sql: String, arguments: StatementArguments = StatementArguments()) {} + func append(sql: String, arguments: StatementArguments = StatementArguments()) {} +} + +class TableDefinition { + func column(sql: String) {} + func check(sql: String) {} + func constraint(sql: String) {} +} + +class TableAlteration { + func addColumn(sql: String) {} +} + +class ColumnDefinition { + func check(sql: String) -> Self { return self } + func defaults(sql: String) -> Self { return self } + func generatedAs(sql: String, _: GeneratedColumnQualification = .virtual) -> Self { return self } +} + +class TableRecord { + static func select(sql: String, arguments: StatementArguments = StatementArguments()) -> QueryInterfaceRequest { QueryInterfaceRequest() } + static func select(sql: String, arguments: StatementArguments = StatementArguments(), as: RowDecoder.Type = RowDecoder.self) -> QueryInterfaceRequest{ QueryInterfaceRequest() } + static func filter(sql: String, arguments: StatementArguments = StatementArguments()) -> QueryInterfaceRequest { QueryInterfaceRequest() } + static func order(sql: String, arguments: StatementArguments = StatementArguments()) -> QueryInterfaceRequest { QueryInterfaceRequest() } +} + +struct StatementCache { + func statement(_: String) -> Statement { return Statement() } +} + +class Row { + func fetchCursor(_: Statement, sql: String, arguments: StatementArguments? = nil, adapter: (any RowAdapter)? = nil) {} + func fetchAll(_: Statement, sql: String, arguments: StatementArguments? = nil, adapter: (any RowAdapter)? = nil) {} + func fetchSet(_: Statement, sql: String, arguments: StatementArguments? = nil, adapter: (any RowAdapter)? = nil) {} + func fetchOne(_: Statement, sql: String, arguments: StatementArguments? = nil, adapter: (any RowAdapter)? = nil) {} +} + +class DatabaseValueConvertible { + func fetchCursor(_: Statement, sql: String, arguments: StatementArguments? = nil, adapter: (any RowAdapter)? = nil) {} + func fetchAll(_: Statement, sql: String, arguments: StatementArguments? = nil, adapter: (any RowAdapter)? = nil) {} + func fetchSet(_: Statement, sql: String, arguments: StatementArguments? = nil, adapter: (any RowAdapter)? = nil) {} + func fetchOne(_: Statement, sql: String, arguments: StatementArguments? = nil, adapter: (any RowAdapter)? = nil) {} +} + +class SQLStatementCursor { + init(database: Database, sql: String, arguments: StatementArguments?, prepFlags: CUnsignedInt = 0) {} +} + +class CommonTableExpression { + init(recursive: Bool = false, named: String, columns: [String]? = nil, sql: String, arguments: StatementArguments = StatementArguments()) {} +} + +// --- tests --- + +func test(database: Database) throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + let _ = database.allStatements(sql: remoteString) // $ Alert + let _ = database.allStatements(sql: localString) // GOOD + let _ = database.allStatements(sql: remoteString, arguments: nil) // $ Alert + let _ = database.allStatements(sql: localString, arguments: nil) // GOOD + + let _ = database.cachedStatement(sql: remoteString) // $ Alert + let _ = database.cachedStatement(sql: localString) // GOOD + + let _ = database.internalCachedStatement(sql: remoteString) // $ Alert + let _ = database.internalCachedStatement(sql: localString) // GOOD + + database.execute(sql: remoteString) // $ Alert + database.execute(sql: localString) // GOOD + database.execute(sql: remoteString, arguments: StatementArguments()) // $ Alert + database.execute(sql: localString, arguments: StatementArguments()) // GOOD + + let _ = database.makeStatement(sql: remoteString) // $ Alert + let _ = database.makeStatement(sql: localString) // GOOD + let _ = database.makeStatement(sql: remoteString, prepFlags: 0) // $ Alert + let _ = database.makeStatement(sql: localString, prepFlags: 0) // GOOD +} + +func testSqlRequest() throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + let _ = SQLRequest(stringLiteral: remoteString) // $ Alert + let _ = SQLRequest(stringLiteral: localString) // GOOD + + let _ = SQLRequest(unicodeScalarLiteral: remoteString) // $ Alert + let _ = SQLRequest(unicodeScalarLiteral: localString) // GOOD + + let _ = SQLRequest(extendedGraphemeClusterLiteral: remoteString) // $ Alert + let _ = SQLRequest(extendedGraphemeClusterLiteral: localString) // GOOD + + let _ = SQLRequest(stringInterpolation: remoteString) // $ Alert + let _ = SQLRequest(stringInterpolation: localString) // GOOD + + let _ = SQLRequest(sql: remoteString) // $ Alert + let _ = SQLRequest(sql: remoteString, arguments: StatementArguments()) // $ Alert + let _ = SQLRequest(sql: remoteString, arguments: StatementArguments(), adapter: nil) // $ Alert + let _ = SQLRequest(sql: remoteString, arguments: StatementArguments(), cached: false) // $ Alert + let _ = SQLRequest(sql: remoteString, arguments: StatementArguments(), adapter: nil, cached: false) // $ Alert + let _ = SQLRequest(sql: remoteString, adapter: nil) // $ Alert + let _ = SQLRequest(sql: remoteString, adapter: nil, cached: false) // $ Alert + let _ = SQLRequest(sql: remoteString, cached: false) // $ Alert + let _ = SQLRequest(sql: localString) // GOOD + let _ = SQLRequest(sql: localString, arguments: StatementArguments()) // GOOD + let _ = SQLRequest(sql: localString, arguments: StatementArguments(), adapter: nil) // GOOD + let _ = SQLRequest(sql: localString, arguments: StatementArguments(), cached: false) // GOOD + let _ = SQLRequest(sql: localString, arguments: StatementArguments(), adapter: nil, cached: false) // GOOD + let _ = SQLRequest(sql: localString, adapter: nil) // GOOD + let _ = SQLRequest(sql: localString, adapter: nil, cached: false) // GOOD + let _ = SQLRequest(sql: localString, cached: false) // GOOD +} + +func testSql() throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + let _ = SQL(stringLiteral: remoteString) // $ Alert + let _ = SQL(unicodeScalarLiteral: remoteString) // $ Alert + let _ = SQL(extendedGraphemeClusterLiteral: remoteString) // $ Alert + let _ = SQL(stringInterpolation: remoteString) // $ Alert + let _ = SQL(sql: remoteString) // $ Alert + let sql1 = SQL(stringLiteral: "") + sql1.append(sql: remoteString) // $ Alert + + let _ = SQL(stringLiteral: localString) // GOOD + let _ = SQL(unicodeScalarLiteral: localString) // GOOD + let _ = SQL(extendedGraphemeClusterLiteral: localString) // GOOD + let _ = SQL(stringInterpolation: localString) // GOOD + let _ = SQL(sql: localString) // GOOD + let sql2 = SQL(stringLiteral: "") + sql2.append(sql: localString) // GOOD +} + +func test(tableDefinition: TableDefinition) throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + tableDefinition.column(sql: remoteString) // $ Alert + tableDefinition.column(sql: localString) // GOOD + + tableDefinition.check(sql: remoteString) // $ Alert + tableDefinition.check(sql: localString) // GOOD + + tableDefinition.constraint(sql: remoteString) // $ Alert + tableDefinition.constraint(sql: localString) // GOOD +} + +func test(tableAlteration: TableAlteration) throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + tableAlteration.addColumn(sql: remoteString) // $ Alert + tableAlteration.addColumn(sql: localString) // GOOD +} + +func test(columnDefinition: ColumnDefinition) throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + let _ = columnDefinition.check(sql: remoteString) // $ Alert + let _ = columnDefinition.defaults(sql: remoteString) // $ Alert + let _ = columnDefinition.generatedAs(sql: remoteString) // $ Alert + let _ = columnDefinition.generatedAs(sql: remoteString, .virtual) // $ Alert + + let _ = columnDefinition.check(sql: localString) // GOOD + let _ = columnDefinition.defaults(sql: localString) // GOOD + let _ = columnDefinition.generatedAs(sql: localString) // GOOD + let _ = columnDefinition.generatedAs(sql: localString, .virtual) // GOOD +} + +func testTableRecord() throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + let _ = TableRecord.select(sql: remoteString) // $ Alert + let _ = TableRecord.select(sql: remoteString, arguments: StatementArguments()) // $ Alert + let _ = TableRecord.select(sql: localString) // GOOD + let _ = TableRecord.select(sql: localString, arguments: StatementArguments()) // GOOD + + let _ = TableRecord.filter(sql: remoteString) // $ Alert + let _ = TableRecord.filter(sql: remoteString, arguments: StatementArguments()) // $ Alert + let _ = TableRecord.filter(sql: localString) // GOOD + let _ = TableRecord.filter(sql: localString, arguments: StatementArguments()) // GOOD + + let _ = TableRecord.order(sql: remoteString) // $ Alert + let _ = TableRecord.order(sql: remoteString, arguments: StatementArguments()) // $ Alert + let _ = TableRecord.order(sql: localString) // GOOD + let _ = TableRecord.order(sql: localString, arguments: StatementArguments()) // GOOD +} + +func test(statementCache: StatementCache) throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + let _ = statementCache.statement(remoteString) // $ Alert + let _ = statementCache.statement(localString) // GOOD +} + +func test(row: Row, stmt: Statement) throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + row.fetchCursor(stmt, sql: remoteString) // $ Alert + row.fetchCursor(stmt, sql: remoteString, arguments: StatementArguments()) // $ Alert + row.fetchCursor(stmt, sql: remoteString, adapter: nil) // $ Alert + row.fetchCursor(stmt, sql: remoteString, arguments: StatementArguments(), adapter: nil) // $ Alert + row.fetchCursor(stmt, sql: localString) // GOOD + row.fetchCursor(stmt, sql: localString, arguments: StatementArguments()) // GOOD + row.fetchCursor(stmt, sql: localString, adapter: nil) // GOOD + row.fetchCursor(stmt, sql: localString, arguments: StatementArguments(), adapter: nil) // GOOD + + row.fetchAll(stmt, sql: remoteString) // $ Alert + row.fetchAll(stmt, sql: remoteString, arguments: StatementArguments()) // $ Alert + row.fetchAll(stmt, sql: remoteString, adapter: nil) // $ Alert + row.fetchAll(stmt, sql: remoteString, arguments: StatementArguments(), adapter: nil) // $ Alert + row.fetchAll(stmt, sql: localString) // GOOD + row.fetchAll(stmt, sql: localString, arguments: StatementArguments()) // GOOD + row.fetchAll(stmt, sql: localString, adapter: nil) // GOOD + row.fetchAll(stmt, sql: localString, arguments: StatementArguments(), adapter: nil) // GOOD + + row.fetchOne(stmt, sql: remoteString) // $ Alert + row.fetchOne(stmt, sql: remoteString, arguments: StatementArguments()) // $ Alert + row.fetchOne(stmt, sql: remoteString, adapter: nil) // $ Alert + row.fetchOne(stmt, sql: remoteString, arguments: StatementArguments(), adapter: nil) // $ Alert + row.fetchOne(stmt, sql: localString) // GOOD + row.fetchOne(stmt, sql: localString, arguments: StatementArguments()) // GOOD + row.fetchOne(stmt, sql: localString, adapter: nil) // GOOD + row.fetchOne(stmt, sql: localString, arguments: StatementArguments(), adapter: nil) // GOOD + + row.fetchSet(stmt, sql: remoteString) // $ Alert + row.fetchSet(stmt, sql: remoteString, arguments: StatementArguments()) // $ Alert + row.fetchSet(stmt, sql: remoteString, adapter: nil) // $ Alert + row.fetchSet(stmt, sql: remoteString, arguments: StatementArguments(), adapter: nil) // $ Alert + row.fetchSet(stmt, sql: localString) // GOOD + row.fetchSet(stmt, sql: localString, arguments: StatementArguments()) // GOOD + row.fetchSet(stmt, sql: localString, adapter: nil) // GOOD + row.fetchSet(stmt, sql: localString, arguments: StatementArguments(), adapter: nil) // GOOD +} + +func test(databaseValueConvertible: DatabaseValueConvertible, stmt: Statement) throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + databaseValueConvertible.fetchCursor(stmt, sql: remoteString) // $ Alert + databaseValueConvertible.fetchCursor(stmt, sql: remoteString, arguments: StatementArguments()) // $ Alert + databaseValueConvertible.fetchCursor(stmt, sql: remoteString, adapter: nil) // $ Alert + databaseValueConvertible.fetchCursor(stmt, sql: remoteString, arguments: StatementArguments(), adapter: nil) // $ Alert + databaseValueConvertible.fetchCursor(stmt, sql: localString) // GOOD + databaseValueConvertible.fetchCursor(stmt, sql: localString, arguments: StatementArguments()) // GOOD + databaseValueConvertible.fetchCursor(stmt, sql: localString, adapter: nil) // GOOD + databaseValueConvertible.fetchCursor(stmt, sql: localString, arguments: StatementArguments(), adapter: nil) // GOOD + + databaseValueConvertible.fetchAll(stmt, sql: remoteString) // $ Alert + databaseValueConvertible.fetchAll(stmt, sql: remoteString, arguments: StatementArguments()) // $ Alert + databaseValueConvertible.fetchAll(stmt, sql: remoteString, adapter: nil) // $ Alert + databaseValueConvertible.fetchAll(stmt, sql: remoteString, arguments: StatementArguments(), adapter: nil) // $ Alert + databaseValueConvertible.fetchAll(stmt, sql: localString) // GOOD + databaseValueConvertible.fetchAll(stmt, sql: localString, arguments: StatementArguments()) // GOOD + databaseValueConvertible.fetchAll(stmt, sql: localString, adapter: nil) // GOOD + databaseValueConvertible.fetchAll(stmt, sql: localString, arguments: StatementArguments(), adapter: nil) // GOOD + + databaseValueConvertible.fetchOne(stmt, sql: remoteString) // $ Alert + databaseValueConvertible.fetchOne(stmt, sql: remoteString, arguments: StatementArguments()) // $ Alert + databaseValueConvertible.fetchOne(stmt, sql: remoteString, adapter: nil) // $ Alert + databaseValueConvertible.fetchOne(stmt, sql: remoteString, arguments: StatementArguments(), adapter: nil) // $ Alert + databaseValueConvertible.fetchOne(stmt, sql: localString) // GOOD + databaseValueConvertible.fetchOne(stmt, sql: localString, arguments: StatementArguments()) // GOOD + databaseValueConvertible.fetchOne(stmt, sql: localString, adapter: nil) // GOOD + databaseValueConvertible.fetchOne(stmt, sql: localString, arguments: StatementArguments(), adapter: nil) // GOOD + + databaseValueConvertible.fetchSet(stmt, sql: remoteString) // $ Alert + databaseValueConvertible.fetchSet(stmt, sql: remoteString, arguments: StatementArguments()) // $ Alert + databaseValueConvertible.fetchSet(stmt, sql: remoteString, adapter: nil) // $ Alert + databaseValueConvertible.fetchSet(stmt, sql: remoteString, arguments: StatementArguments(), adapter: nil) // $ Alert + databaseValueConvertible.fetchSet(stmt, sql: localString) // GOOD + databaseValueConvertible.fetchSet(stmt, sql: localString, arguments: StatementArguments()) // GOOD + databaseValueConvertible.fetchSet(stmt, sql: localString, adapter: nil) // GOOD + databaseValueConvertible.fetchSet(stmt, sql: localString, arguments: StatementArguments(), adapter: nil) // GOOD +} + +func testSqlStatementCursor(database: Database) throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + let _ = SQLStatementCursor(database: database, sql: remoteString, arguments: StatementArguments()) // $ Alert + let _ = SQLStatementCursor(database: database, sql: remoteString, arguments: StatementArguments(), prepFlags: 0) // $ Alert + let _ = SQLStatementCursor(database: database, sql: localString, arguments: StatementArguments()) // GOOD + let _ = SQLStatementCursor(database: database, sql: localString, arguments: StatementArguments(), prepFlags: 0) // GOOD +} + +func testCommonTableExpression() throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + let _ = CommonTableExpression(named: "", sql: remoteString) // $ Alert + let _ = CommonTableExpression(named: "", sql: remoteString, arguments: StatementArguments()) // $ Alert + let _ = CommonTableExpression(named: "", columns: [""], sql: remoteString) // $ Alert + let _ = CommonTableExpression(named: "", columns: [""], sql: remoteString, arguments: StatementArguments()) // $ Alert + let _ = CommonTableExpression(recursive: false, named: "", sql: remoteString) // $ Alert + let _ = CommonTableExpression(recursive: false, named: "", columns: [""], sql: remoteString) // $ Alert + let _ = CommonTableExpression(recursive: false, named: "", sql: remoteString, arguments: StatementArguments()) // $ Alert + let _ = CommonTableExpression(recursive: false, named: "", columns: [""], sql: remoteString, arguments: StatementArguments()) // $ Alert + let _ = CommonTableExpression(named: "", sql: localString) // GOOD + let _ = CommonTableExpression(named: "", sql: localString, arguments: StatementArguments()) // GOOD + let _ = CommonTableExpression(named: "", columns: [""], sql: localString) // GOOD + let _ = CommonTableExpression(named: "", columns: [""], sql: localString, arguments: StatementArguments()) // GOOD + let _ = CommonTableExpression(recursive: false, named: "", sql: localString) // GOOD + let _ = CommonTableExpression(recursive: false, named: "", columns: [""], sql: localString) // GOOD + let _ = CommonTableExpression(recursive: false, named: "", sql: localString, arguments: StatementArguments()) // GOOD + let _ = CommonTableExpression(recursive: false, named: "", columns: [""], sql: localString, arguments: StatementArguments()) // GOOD +} diff --git a/unified/ql/test/query-tests/security/CWE-089/SqlInjection/SQLite.swift b/unified/ql/test/query-tests/security/CWE-089/SqlInjection/SQLite.swift new file mode 100644 index 000000000000..b58aa304e143 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-089/SqlInjection/SQLite.swift @@ -0,0 +1,144 @@ + +// --- stubs --- + +struct URL +{ + init?(string: String) {} + init?(string: String, relativeTo: URL?) {} +} + +extension String { + init(contentsOf: URL) throws { + var data = "" + + // ... + + self.init(data) + } +} + +public protocol Binding {} + +extension String: Binding {} + +class Statement { + fileprivate let connection: Connection + + init(_ connection: Connection, _ SQL: String) throws { self.connection = connection} + + public func bind(_ values: Binding?...) -> Statement { return self } + public func bind(_ values: [Binding?]) -> Statement { return self } + public func bind(_ values: [String: Binding?]) -> Statement { return self } + + @discardableResult public func run(_ bindings: Binding?...) throws -> Statement { return self } + @discardableResult public func run(_ bindings: [Binding?]) throws -> Statement { return self } + @discardableResult public func run(_ bindings: [String: Binding?]) throws -> Statement { return self } + + public func scalar(_ bindings: Binding?...) throws -> Binding? { return nil } + public func scalar(_ bindings: [Binding?]) throws -> Binding? { return nil } + public func scalar(_ bindings: [String: Binding?]) throws -> Binding? { return nil } +} + +class Connection { + public func execute(_ SQL: String) throws { } + + public func prepare(_ statement: String, _ bindings: Binding?...) throws -> Statement { return try Statement(self, "") } + public func prepare(_ statement: String, _ bindings: [Binding?]) throws -> Statement { return try Statement(self, "") } + public func prepare(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try Statement(self, "") } + + @discardableResult public func run(_ statement: String, _ bindings: Binding?...) throws -> Statement { return try Statement(self, "") } + @discardableResult public func run(_ statement: String, _ bindings: [Binding?]) throws -> Statement { return try Statement(self, "") } + @discardableResult public func run(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement { return try Statement(self, "") } + + public func scalar(_ statement: String, _ bindings: Binding?...) throws -> Binding? { return nil } + public func scalar(_ statement: String, _ bindings: [Binding?]) throws -> Binding? { return nil } + public func scalar(_ statement: String, _ bindings: [String: Binding?]) throws -> Binding? { return nil } +} + +// --- tests --- + +func test_sqlite_swift_api(db: Connection) throws { + let localString = "user" + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + let remoteNumber = Int(remoteString) ?? 0 + + let unsafeQuery1 = remoteString + let unsafeQuery2 = "SELECT * FROM users WHERE username='" + remoteString + "'" + let unsafeQuery3 = "SELECT * FROM users WHERE username='\(remoteString)'" + let safeQuery1 = "SELECT * FROM users WHERE username='\(localString)'" + let safeQuery2 = "SELECT * FROM users WHERE username='\(remoteNumber)'" + + // --- execute --- + + try db.execute(unsafeQuery1) // $ Alert + try db.execute(unsafeQuery2) // $ Alert + try db.execute(unsafeQuery3) // $ Alert + try db.execute(safeQuery1) // GOOD + try db.execute(safeQuery2) // GOOD + + // --- prepared statements --- + + let varQuery = "SELECT * FROM users WHERE username=?" + + let stmt1 = try db.prepare(unsafeQuery3) // $ Alert + try stmt1.run() + + let stmt2 = try db.prepare(varQuery, localString) // GOOD + try stmt2.run() + + let stmt3 = try db.prepare(varQuery, remoteString) // GOOD + try stmt3.run() + + let stmt4 = try Statement(db, localString) // GOOD + try stmt4.run() + + let stmt5 = try Statement(db, remoteString) // $ Alert + try stmt5.run() + + // --- more variants --- + + let stmt6 = try db.prepare(unsafeQuery1, "") // $ Alert + try stmt6.run() + + let stmt7 = try db.prepare(unsafeQuery1, [""]) // $ Alert + try stmt7.run() + + let stmt8 = try db.prepare(unsafeQuery1, ["username": ""]) // $ Alert + try stmt8.run() + + try db.run(unsafeQuery1, "") // $ Alert + + try db.run(unsafeQuery1, [""]) // $ Alert + + try db.run(unsafeQuery1, ["username": ""]) // $ Alert + + try db.scalar(unsafeQuery1, "") // $ Alert + + try db.scalar(unsafeQuery1, [""]) // $ Alert + + try db.scalar(unsafeQuery1, ["username": ""]) // $ Alert + + let stmt9 = try db.prepare(varQuery) // GOOD + try stmt9.bind(remoteString) // GOOD + try stmt9.bind([remoteString]) // GOOD + try stmt9.bind(["username": remoteString]) // GOOD + try stmt9.run(remoteString) // GOOD + try stmt9.run([remoteString]) // GOOD + try stmt9.run(["username": remoteString]) // GOOD + try stmt9.run(localString, remoteString) // GOOD + try stmt9.scalar(remoteString) // GOOD + try stmt9.scalar([remoteString]) // GOOD + try stmt9.scalar(["username": remoteString]) // GOOD + try stmt9.scalar(localString, remoteString) // GOOD + + try Statement(db, remoteString).run() // $ Alert + + try db.run(unsafeQuery1) // $ Alert + try db.run(safeQuery1) // GOOD + try db.run(unsafeQuery1, localString, remoteString) // $ Alert + try db.run(varQuery, localString, remoteString) // GOOD + try db.scalar(unsafeQuery1) // $ Alert + try db.scalar(safeQuery1) // GOOD + try db.scalar(unsafeQuery1, localString, remoteString) // $ Alert + try db.scalar(varQuery, localString, remoteString) // GOOD +} diff --git a/unified/ql/test/query-tests/security/CWE-089/SqlInjection/SqlInjection.expected b/unified/ql/test/query-tests/security/CWE-089/SqlInjection/SqlInjection.expected new file mode 100644 index 000000000000..57ba80442944 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-089/SqlInjection/SqlInjection.expected @@ -0,0 +1,465 @@ +#select +| GRDB.swift:106:41:106:52 | remoteString | GRDB.swift:104:25:104:79 | String(...) | GRDB.swift:106:41:106:52 | remoteString | This query depends on a $@. | GRDB.swift:104:25:104:79 | String(...) | user-provided value | +| GRDB.swift:108:41:108:52 | remoteString | GRDB.swift:104:25:104:79 | String(...) | GRDB.swift:108:41:108:52 | remoteString | This query depends on a $@. | GRDB.swift:104:25:104:79 | String(...) | user-provided value | +| GRDB.swift:111:43:111:54 | remoteString | GRDB.swift:104:25:104:79 | String(...) | GRDB.swift:111:43:111:54 | remoteString | This query depends on a $@. | GRDB.swift:104:25:104:79 | String(...) | user-provided value | +| GRDB.swift:114:51:114:62 | remoteString | GRDB.swift:104:25:104:79 | String(...) | GRDB.swift:114:51:114:62 | remoteString | This query depends on a $@. | GRDB.swift:104:25:104:79 | String(...) | user-provided value | +| GRDB.swift:117:27:117:38 | remoteString | GRDB.swift:104:25:104:79 | String(...) | GRDB.swift:117:27:117:38 | remoteString | This query depends on a $@. | GRDB.swift:104:25:104:79 | String(...) | user-provided value | +| GRDB.swift:119:27:119:38 | remoteString | GRDB.swift:104:25:104:79 | String(...) | GRDB.swift:119:27:119:38 | remoteString | This query depends on a $@. | GRDB.swift:104:25:104:79 | String(...) | user-provided value | +| GRDB.swift:122:41:122:52 | remoteString | GRDB.swift:104:25:104:79 | String(...) | GRDB.swift:122:41:122:52 | remoteString | This query depends on a $@. | GRDB.swift:104:25:104:79 | String(...) | user-provided value | +| GRDB.swift:124:41:124:52 | remoteString | GRDB.swift:104:25:104:79 | String(...) | GRDB.swift:124:41:124:52 | remoteString | This query depends on a $@. | GRDB.swift:104:25:104:79 | String(...) | user-provided value | +| GRDB.swift:132:39:132:50 | remoteString | GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:132:39:132:50 | remoteString | This query depends on a $@. | GRDB.swift:130:26:130:80 | String(...) | user-provided value | +| GRDB.swift:135:46:135:57 | remoteString | GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:135:46:135:57 | remoteString | This query depends on a $@. | GRDB.swift:130:26:130:80 | String(...) | user-provided value | +| GRDB.swift:138:56:138:67 | remoteString | GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:138:56:138:67 | remoteString | This query depends on a $@. | GRDB.swift:130:26:130:80 | String(...) | user-provided value | +| GRDB.swift:141:45:141:56 | remoteString | GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:141:45:141:56 | remoteString | This query depends on a $@. | GRDB.swift:130:26:130:80 | String(...) | user-provided value | +| GRDB.swift:144:29:144:40 | remoteString | GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:144:29:144:40 | remoteString | This query depends on a $@. | GRDB.swift:130:26:130:80 | String(...) | user-provided value | +| GRDB.swift:145:29:145:40 | remoteString | GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:145:29:145:40 | remoteString | This query depends on a $@. | GRDB.swift:130:26:130:80 | String(...) | user-provided value | +| GRDB.swift:146:29:146:40 | remoteString | GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:146:29:146:40 | remoteString | This query depends on a $@. | GRDB.swift:130:26:130:80 | String(...) | user-provided value | +| GRDB.swift:147:29:147:40 | remoteString | GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:147:29:147:40 | remoteString | This query depends on a $@. | GRDB.swift:130:26:130:80 | String(...) | user-provided value | +| GRDB.swift:148:29:148:40 | remoteString | GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:148:29:148:40 | remoteString | This query depends on a $@. | GRDB.swift:130:26:130:80 | String(...) | user-provided value | +| GRDB.swift:149:29:149:40 | remoteString | GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:149:29:149:40 | remoteString | This query depends on a $@. | GRDB.swift:130:26:130:80 | String(...) | user-provided value | +| GRDB.swift:150:29:150:40 | remoteString | GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:150:29:150:40 | remoteString | This query depends on a $@. | GRDB.swift:130:26:130:80 | String(...) | user-provided value | +| GRDB.swift:151:29:151:40 | remoteString | GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:151:29:151:40 | remoteString | This query depends on a $@. | GRDB.swift:130:26:130:80 | String(...) | user-provided value | +| GRDB.swift:166:32:166:43 | remoteString | GRDB.swift:164:26:164:80 | String(...) | GRDB.swift:166:32:166:43 | remoteString | This query depends on a $@. | GRDB.swift:164:26:164:80 | String(...) | user-provided value | +| GRDB.swift:167:39:167:50 | remoteString | GRDB.swift:164:26:164:80 | String(...) | GRDB.swift:167:39:167:50 | remoteString | This query depends on a $@. | GRDB.swift:164:26:164:80 | String(...) | user-provided value | +| GRDB.swift:168:49:168:60 | remoteString | GRDB.swift:164:26:164:80 | String(...) | GRDB.swift:168:49:168:60 | remoteString | This query depends on a $@. | GRDB.swift:164:26:164:80 | String(...) | user-provided value | +| GRDB.swift:169:38:169:49 | remoteString | GRDB.swift:164:26:164:80 | String(...) | GRDB.swift:169:38:169:49 | remoteString | This query depends on a $@. | GRDB.swift:164:26:164:80 | String(...) | user-provided value | +| GRDB.swift:170:22:170:33 | remoteString | GRDB.swift:164:26:164:80 | String(...) | GRDB.swift:170:22:170:33 | remoteString | This query depends on a $@. | GRDB.swift:164:26:164:80 | String(...) | user-provided value | +| GRDB.swift:172:22:172:33 | remoteString | GRDB.swift:164:26:164:80 | String(...) | GRDB.swift:172:22:172:33 | remoteString | This query depends on a $@. | GRDB.swift:164:26:164:80 | String(...) | user-provided value | +| GRDB.swift:187:33:187:44 | remoteString | GRDB.swift:185:26:185:80 | String(...) | GRDB.swift:187:33:187:44 | remoteString | This query depends on a $@. | GRDB.swift:185:26:185:80 | String(...) | user-provided value | +| GRDB.swift:190:32:190:43 | remoteString | GRDB.swift:185:26:185:80 | String(...) | GRDB.swift:190:32:190:43 | remoteString | This query depends on a $@. | GRDB.swift:185:26:185:80 | String(...) | user-provided value | +| GRDB.swift:193:37:193:48 | remoteString | GRDB.swift:185:26:185:80 | String(...) | GRDB.swift:193:37:193:48 | remoteString | This query depends on a $@. | GRDB.swift:185:26:185:80 | String(...) | user-provided value | +| GRDB.swift:201:36:201:47 | remoteString | GRDB.swift:199:26:199:80 | String(...) | GRDB.swift:201:36:201:47 | remoteString | This query depends on a $@. | GRDB.swift:199:26:199:80 | String(...) | user-provided value | +| GRDB.swift:209:41:209:52 | remoteString | GRDB.swift:207:26:207:80 | String(...) | GRDB.swift:209:41:209:52 | remoteString | This query depends on a $@. | GRDB.swift:207:26:207:80 | String(...) | user-provided value | +| GRDB.swift:210:44:210:55 | remoteString | GRDB.swift:207:26:207:80 | String(...) | GRDB.swift:210:44:210:55 | remoteString | This query depends on a $@. | GRDB.swift:207:26:207:80 | String(...) | user-provided value | +| GRDB.swift:211:47:211:58 | remoteString | GRDB.swift:207:26:207:80 | String(...) | GRDB.swift:211:47:211:58 | remoteString | This query depends on a $@. | GRDB.swift:207:26:207:80 | String(...) | user-provided value | +| GRDB.swift:212:47:212:58 | remoteString | GRDB.swift:207:26:207:80 | String(...) | GRDB.swift:212:47:212:58 | remoteString | This query depends on a $@. | GRDB.swift:207:26:207:80 | String(...) | user-provided value | +| GRDB.swift:224:37:224:48 | remoteString | GRDB.swift:222:26:222:80 | String(...) | GRDB.swift:224:37:224:48 | remoteString | This query depends on a $@. | GRDB.swift:222:26:222:80 | String(...) | user-provided value | +| GRDB.swift:225:37:225:48 | remoteString | GRDB.swift:222:26:222:80 | String(...) | GRDB.swift:225:37:225:48 | remoteString | This query depends on a $@. | GRDB.swift:222:26:222:80 | String(...) | user-provided value | +| GRDB.swift:229:37:229:48 | remoteString | GRDB.swift:222:26:222:80 | String(...) | GRDB.swift:229:37:229:48 | remoteString | This query depends on a $@. | GRDB.swift:222:26:222:80 | String(...) | user-provided value | +| GRDB.swift:230:37:230:48 | remoteString | GRDB.swift:222:26:222:80 | String(...) | GRDB.swift:230:37:230:48 | remoteString | This query depends on a $@. | GRDB.swift:222:26:222:80 | String(...) | user-provided value | +| GRDB.swift:234:36:234:47 | remoteString | GRDB.swift:222:26:222:80 | String(...) | GRDB.swift:234:36:234:47 | remoteString | This query depends on a $@. | GRDB.swift:222:26:222:80 | String(...) | user-provided value | +| GRDB.swift:235:36:235:47 | remoteString | GRDB.swift:222:26:222:80 | String(...) | GRDB.swift:235:36:235:47 | remoteString | This query depends on a $@. | GRDB.swift:222:26:222:80 | String(...) | user-provided value | +| GRDB.swift:244:38:244:49 | remoteString | GRDB.swift:242:26:242:80 | String(...) | GRDB.swift:244:38:244:49 | remoteString | This query depends on a $@. | GRDB.swift:242:26:242:80 | String(...) | user-provided value | +| GRDB.swift:252:32:252:43 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:252:32:252:43 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:253:32:253:43 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:253:32:253:43 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:254:32:254:43 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:254:32:254:43 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:255:32:255:43 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:255:32:255:43 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:261:29:261:40 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:261:29:261:40 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:262:29:262:40 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:262:29:262:40 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:263:29:263:40 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:263:29:263:40 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:264:29:264:40 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:264:29:264:40 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:270:29:270:40 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:270:29:270:40 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:271:29:271:40 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:271:29:271:40 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:272:29:272:40 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:272:29:272:40 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:273:29:273:40 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:273:29:273:40 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:279:29:279:40 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:279:29:279:40 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:280:29:280:40 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:280:29:280:40 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:281:29:281:40 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:281:29:281:40 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:282:29:282:40 | remoteString | GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:282:29:282:40 | remoteString | This query depends on a $@. | GRDB.swift:250:26:250:80 | String(...) | user-provided value | +| GRDB.swift:293:53:293:64 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:293:53:293:64 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:294:53:294:64 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:294:53:294:64 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:295:53:295:64 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:295:53:295:64 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:296:53:296:64 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:296:53:296:64 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:302:50:302:61 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:302:50:302:61 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:303:50:303:61 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:303:50:303:61 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:304:50:304:61 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:304:50:304:61 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:305:50:305:61 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:305:50:305:61 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:311:50:311:61 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:311:50:311:61 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:312:50:312:61 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:312:50:312:61 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:313:50:313:61 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:313:50:313:61 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:314:50:314:61 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:314:50:314:61 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:320:50:320:61 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:320:50:320:61 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:321:50:321:61 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:321:50:321:61 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:322:50:322:61 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:322:50:322:61 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:323:50:323:61 | remoteString | GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:323:50:323:61 | remoteString | This query depends on a $@. | GRDB.swift:291:26:291:80 | String(...) | user-provided value | +| GRDB.swift:334:57:334:68 | remoteString | GRDB.swift:332:26:332:80 | String(...) | GRDB.swift:334:57:334:68 | remoteString | This query depends on a $@. | GRDB.swift:332:26:332:80 | String(...) | user-provided value | +| GRDB.swift:335:57:335:68 | remoteString | GRDB.swift:332:26:332:80 | String(...) | GRDB.swift:335:57:335:68 | remoteString | This query depends on a $@. | GRDB.swift:332:26:332:80 | String(...) | user-provided value | +| GRDB.swift:344:51:344:62 | remoteString | GRDB.swift:342:26:342:80 | String(...) | GRDB.swift:344:51:344:62 | remoteString | This query depends on a $@. | GRDB.swift:342:26:342:80 | String(...) | user-provided value | +| GRDB.swift:345:51:345:62 | remoteString | GRDB.swift:342:26:342:80 | String(...) | GRDB.swift:345:51:345:62 | remoteString | This query depends on a $@. | GRDB.swift:342:26:342:80 | String(...) | user-provided value | +| GRDB.swift:346:66:346:77 | remoteString | GRDB.swift:342:26:342:80 | String(...) | GRDB.swift:346:66:346:77 | remoteString | This query depends on a $@. | GRDB.swift:342:26:342:80 | String(...) | user-provided value | +| GRDB.swift:347:66:347:77 | remoteString | GRDB.swift:342:26:342:80 | String(...) | GRDB.swift:347:66:347:77 | remoteString | This query depends on a $@. | GRDB.swift:342:26:342:80 | String(...) | user-provided value | +| GRDB.swift:348:69:348:80 | remoteString | GRDB.swift:342:26:342:80 | String(...) | GRDB.swift:348:69:348:80 | remoteString | This query depends on a $@. | GRDB.swift:342:26:342:80 | String(...) | user-provided value | +| GRDB.swift:349:84:349:95 | remoteString | GRDB.swift:342:26:342:80 | String(...) | GRDB.swift:349:84:349:95 | remoteString | This query depends on a $@. | GRDB.swift:342:26:342:80 | String(...) | user-provided value | +| GRDB.swift:350:69:350:80 | remoteString | GRDB.swift:342:26:342:80 | String(...) | GRDB.swift:350:69:350:80 | remoteString | This query depends on a $@. | GRDB.swift:342:26:342:80 | String(...) | user-provided value | +| GRDB.swift:351:84:351:95 | remoteString | GRDB.swift:342:26:342:80 | String(...) | GRDB.swift:351:84:351:95 | remoteString | This query depends on a $@. | GRDB.swift:342:26:342:80 | String(...) | user-provided value | +| SQLite.swift:73:17:73:28 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:73:17:73:28 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:74:17:74:28 | unsafeQuery2 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:74:17:74:28 | unsafeQuery2 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:75:17:75:28 | unsafeQuery3 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:75:17:75:28 | unsafeQuery3 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:83:29:83:40 | unsafeQuery3 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:83:29:83:40 | unsafeQuery3 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:95:32:95:43 | remoteString | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:95:32:95:43 | remoteString | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:100:29:100:40 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:100:29:100:40 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:103:29:103:40 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:103:29:103:40 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:106:29:106:40 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:106:29:106:40 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:109:13:109:24 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:109:13:109:24 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:111:13:111:24 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:111:13:111:24 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:113:13:113:24 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:113:13:113:24 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:115:16:115:27 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:115:16:115:27 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:117:16:117:27 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:117:16:117:27 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:119:16:119:27 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:119:16:119:27 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:134:20:134:31 | remoteString | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:134:20:134:31 | remoteString | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:136:13:136:24 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:136:13:136:24 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:138:13:138:24 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:138:13:138:24 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:140:16:140:27 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:140:16:140:27 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| SQLite.swift:142:16:142:27 | unsafeQuery1 | SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:142:16:142:27 | unsafeQuery1 | This query depends on a $@. | SQLite.swift:62:25:62:79 | String(...) | user-provided value | +| other.swift:55:22:55:33 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:55:22:55:33 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:57:14:57:25 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:57:14:57:25 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:58:14:58:25 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:58:14:58:25 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:59:14:59:43 | NSString(...) | other.swift:51:25:51:79 | String(...) | other.swift:59:14:59:43 | NSString(...) | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:60:14:60:33 | TypeCastExpr | other.swift:51:25:51:79 | String(...) | other.swift:60:14:60:33 | TypeCastExpr | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:62:25:62:36 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:62:25:62:36 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:64:34:64:45 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:64:34:64:45 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:67:21:67:32 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:67:21:67:32 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:69:21:69:32 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:69:21:69:32 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:72:16:72:27 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:72:16:72:27 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:73:23:73:34 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:73:23:73:34 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:74:25:74:36 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:74:25:74:36 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:75:28:75:39 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:75:28:75:39 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:76:34:76:45 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:76:34:76:45 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| other.swift:77:19:77:30 | remoteString | other.swift:51:25:51:79 | String(...) | other.swift:77:19:77:30 | remoteString | This query depends on a $@. | other.swift:51:25:51:79 | String(...) | user-provided value | +| sqlite3_c_api.swift:133:33:133:44 | unsafeQuery1 | sqlite3_c_api.swift:122:26:122:80 | String(...) | sqlite3_c_api.swift:133:33:133:44 | unsafeQuery1 | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | String(...) | user-provided value | +| sqlite3_c_api.swift:134:33:134:44 | unsafeQuery2 | sqlite3_c_api.swift:122:26:122:80 | String(...) | sqlite3_c_api.swift:134:33:134:44 | unsafeQuery2 | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | String(...) | user-provided value | +| sqlite3_c_api.swift:135:33:135:44 | unsafeQuery3 | sqlite3_c_api.swift:122:26:122:80 | String(...) | sqlite3_c_api.swift:135:33:135:44 | unsafeQuery3 | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | String(...) | user-provided value | +| sqlite3_c_api.swift:145:26:145:37 | unsafeQuery3 | sqlite3_c_api.swift:122:26:122:80 | String(...) | sqlite3_c_api.swift:145:26:145:37 | unsafeQuery3 | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | String(...) | user-provided value | +| sqlite3_c_api.swift:175:29:175:40 | unsafeQuery3 | sqlite3_c_api.swift:122:26:122:80 | String(...) | sqlite3_c_api.swift:175:29:175:40 | unsafeQuery3 | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | String(...) | user-provided value | +| sqlite3_c_api.swift:183:29:183:40 | unsafeQuery3 | sqlite3_c_api.swift:122:26:122:80 | String(...) | sqlite3_c_api.swift:183:29:183:40 | unsafeQuery3 | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | String(...) | user-provided value | +| sqlite3_c_api.swift:194:28:194:33 | buffer | sqlite3_c_api.swift:122:26:122:80 | String(...) | sqlite3_c_api.swift:194:28:194:33 | buffer | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | String(...) | user-provided value | +| sqlite3_c_api.swift:202:31:202:36 | buffer | sqlite3_c_api.swift:122:26:122:80 | String(...) | sqlite3_c_api.swift:202:31:202:36 | buffer | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | String(...) | user-provided value | +| sqlite3_c_api.swift:210:31:210:36 | buffer | sqlite3_c_api.swift:122:26:122:80 | String(...) | sqlite3_c_api.swift:210:31:210:36 | buffer | This query depends on a $@. | sqlite3_c_api.swift:122:26:122:80 | String(...) | user-provided value | +edges +| GRDB.swift:104:6:104:17 | remoteString | GRDB.swift:106:41:106:52 | remoteString | provenance | | +| GRDB.swift:104:6:104:17 | remoteString | GRDB.swift:108:41:108:52 | remoteString | provenance | | +| GRDB.swift:104:6:104:17 | remoteString | GRDB.swift:111:43:111:54 | remoteString | provenance | | +| GRDB.swift:104:6:104:17 | remoteString | GRDB.swift:114:51:114:62 | remoteString | provenance | | +| GRDB.swift:104:6:104:17 | remoteString | GRDB.swift:117:27:117:38 | remoteString | provenance | | +| GRDB.swift:104:6:104:17 | remoteString | GRDB.swift:119:27:119:38 | remoteString | provenance | | +| GRDB.swift:104:6:104:17 | remoteString | GRDB.swift:122:41:122:52 | remoteString | provenance | | +| GRDB.swift:104:6:104:17 | remoteString | GRDB.swift:124:41:124:52 | remoteString | provenance | | +| GRDB.swift:104:25:104:79 | String(...) | GRDB.swift:104:6:104:17 | remoteString | provenance | | +| GRDB.swift:130:6:130:17 | remoteString | GRDB.swift:132:39:132:50 | remoteString | provenance | | +| GRDB.swift:130:6:130:17 | remoteString | GRDB.swift:135:46:135:57 | remoteString | provenance | | +| GRDB.swift:130:6:130:17 | remoteString | GRDB.swift:138:56:138:67 | remoteString | provenance | | +| GRDB.swift:130:6:130:17 | remoteString | GRDB.swift:141:45:141:56 | remoteString | provenance | | +| GRDB.swift:130:6:130:17 | remoteString | GRDB.swift:144:29:144:40 | remoteString | provenance | | +| GRDB.swift:130:6:130:17 | remoteString | GRDB.swift:145:29:145:40 | remoteString | provenance | | +| GRDB.swift:130:6:130:17 | remoteString | GRDB.swift:146:29:146:40 | remoteString | provenance | | +| GRDB.swift:130:6:130:17 | remoteString | GRDB.swift:147:29:147:40 | remoteString | provenance | | +| GRDB.swift:130:6:130:17 | remoteString | GRDB.swift:148:29:148:40 | remoteString | provenance | | +| GRDB.swift:130:6:130:17 | remoteString | GRDB.swift:149:29:149:40 | remoteString | provenance | | +| GRDB.swift:130:6:130:17 | remoteString | GRDB.swift:150:29:150:40 | remoteString | provenance | | +| GRDB.swift:130:6:130:17 | remoteString | GRDB.swift:151:29:151:40 | remoteString | provenance | | +| GRDB.swift:130:26:130:80 | String(...) | GRDB.swift:130:6:130:17 | remoteString | provenance | | +| GRDB.swift:164:6:164:17 | remoteString | GRDB.swift:166:32:166:43 | remoteString | provenance | | +| GRDB.swift:164:6:164:17 | remoteString | GRDB.swift:167:39:167:50 | remoteString | provenance | | +| GRDB.swift:164:6:164:17 | remoteString | GRDB.swift:168:49:168:60 | remoteString | provenance | | +| GRDB.swift:164:6:164:17 | remoteString | GRDB.swift:169:38:169:49 | remoteString | provenance | | +| GRDB.swift:164:6:164:17 | remoteString | GRDB.swift:170:22:170:33 | remoteString | provenance | | +| GRDB.swift:164:6:164:17 | remoteString | GRDB.swift:172:22:172:33 | remoteString | provenance | | +| GRDB.swift:164:26:164:80 | String(...) | GRDB.swift:164:6:164:17 | remoteString | provenance | | +| GRDB.swift:185:6:185:17 | remoteString | GRDB.swift:187:33:187:44 | remoteString | provenance | | +| GRDB.swift:185:6:185:17 | remoteString | GRDB.swift:190:32:190:43 | remoteString | provenance | | +| GRDB.swift:185:6:185:17 | remoteString | GRDB.swift:193:37:193:48 | remoteString | provenance | | +| GRDB.swift:185:26:185:80 | String(...) | GRDB.swift:185:6:185:17 | remoteString | provenance | | +| GRDB.swift:199:6:199:17 | remoteString | GRDB.swift:201:36:201:47 | remoteString | provenance | | +| GRDB.swift:199:26:199:80 | String(...) | GRDB.swift:199:6:199:17 | remoteString | provenance | | +| GRDB.swift:207:6:207:17 | remoteString | GRDB.swift:209:41:209:52 | remoteString | provenance | | +| GRDB.swift:207:6:207:17 | remoteString | GRDB.swift:210:44:210:55 | remoteString | provenance | | +| GRDB.swift:207:6:207:17 | remoteString | GRDB.swift:211:47:211:58 | remoteString | provenance | | +| GRDB.swift:207:6:207:17 | remoteString | GRDB.swift:212:47:212:58 | remoteString | provenance | | +| GRDB.swift:207:26:207:80 | String(...) | GRDB.swift:207:6:207:17 | remoteString | provenance | | +| GRDB.swift:222:6:222:17 | remoteString | GRDB.swift:224:37:224:48 | remoteString | provenance | | +| GRDB.swift:222:6:222:17 | remoteString | GRDB.swift:225:37:225:48 | remoteString | provenance | | +| GRDB.swift:222:6:222:17 | remoteString | GRDB.swift:229:37:229:48 | remoteString | provenance | | +| GRDB.swift:222:6:222:17 | remoteString | GRDB.swift:230:37:230:48 | remoteString | provenance | | +| GRDB.swift:222:6:222:17 | remoteString | GRDB.swift:234:36:234:47 | remoteString | provenance | | +| GRDB.swift:222:6:222:17 | remoteString | GRDB.swift:235:36:235:47 | remoteString | provenance | | +| GRDB.swift:222:26:222:80 | String(...) | GRDB.swift:222:6:222:17 | remoteString | provenance | | +| GRDB.swift:242:6:242:17 | remoteString | GRDB.swift:244:38:244:49 | remoteString | provenance | | +| GRDB.swift:242:26:242:80 | String(...) | GRDB.swift:242:6:242:17 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:252:32:252:43 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:253:32:253:43 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:254:32:254:43 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:255:32:255:43 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:261:29:261:40 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:262:29:262:40 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:263:29:263:40 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:264:29:264:40 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:270:29:270:40 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:271:29:271:40 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:272:29:272:40 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:273:29:273:40 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:279:29:279:40 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:280:29:280:40 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:281:29:281:40 | remoteString | provenance | | +| GRDB.swift:250:6:250:17 | remoteString | GRDB.swift:282:29:282:40 | remoteString | provenance | | +| GRDB.swift:250:26:250:80 | String(...) | GRDB.swift:250:6:250:17 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:293:53:293:64 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:294:53:294:64 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:295:53:295:64 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:296:53:296:64 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:302:50:302:61 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:303:50:303:61 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:304:50:304:61 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:305:50:305:61 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:311:50:311:61 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:312:50:312:61 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:313:50:313:61 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:314:50:314:61 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:320:50:320:61 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:321:50:321:61 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:322:50:322:61 | remoteString | provenance | | +| GRDB.swift:291:6:291:17 | remoteString | GRDB.swift:323:50:323:61 | remoteString | provenance | | +| GRDB.swift:291:26:291:80 | String(...) | GRDB.swift:291:6:291:17 | remoteString | provenance | | +| GRDB.swift:332:6:332:17 | remoteString | GRDB.swift:334:57:334:68 | remoteString | provenance | | +| GRDB.swift:332:6:332:17 | remoteString | GRDB.swift:335:57:335:68 | remoteString | provenance | | +| GRDB.swift:332:26:332:80 | String(...) | GRDB.swift:332:6:332:17 | remoteString | provenance | | +| GRDB.swift:342:6:342:17 | remoteString | GRDB.swift:344:51:344:62 | remoteString | provenance | | +| GRDB.swift:342:6:342:17 | remoteString | GRDB.swift:345:51:345:62 | remoteString | provenance | | +| GRDB.swift:342:6:342:17 | remoteString | GRDB.swift:346:66:346:77 | remoteString | provenance | | +| GRDB.swift:342:6:342:17 | remoteString | GRDB.swift:347:66:347:77 | remoteString | provenance | | +| GRDB.swift:342:6:342:17 | remoteString | GRDB.swift:348:69:348:80 | remoteString | provenance | | +| GRDB.swift:342:6:342:17 | remoteString | GRDB.swift:349:84:349:95 | remoteString | provenance | | +| GRDB.swift:342:6:342:17 | remoteString | GRDB.swift:350:69:350:80 | remoteString | provenance | | +| GRDB.swift:342:6:342:17 | remoteString | GRDB.swift:351:84:351:95 | remoteString | provenance | | +| GRDB.swift:342:26:342:80 | String(...) | GRDB.swift:342:6:342:17 | remoteString | provenance | | +| SQLite.swift:62:6:62:17 | remoteString | SQLite.swift:65:21:65:32 | remoteString | provenance | | +| SQLite.swift:62:6:62:17 | remoteString | SQLite.swift:66:62:66:73 | remoteString | provenance | | +| SQLite.swift:62:6:62:17 | remoteString | SQLite.swift:67:60:67:71 | remoteString | provenance | | +| SQLite.swift:62:6:62:17 | remoteString | SQLite.swift:95:32:95:43 | remoteString | provenance | | +| SQLite.swift:62:6:62:17 | remoteString | SQLite.swift:134:20:134:31 | remoteString | provenance | | +| SQLite.swift:62:25:62:79 | String(...) | SQLite.swift:62:6:62:17 | remoteString | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:73:17:73:28 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:100:29:100:40 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:103:29:103:40 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:106:29:106:40 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:109:13:109:24 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:111:13:111:24 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:113:13:113:24 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:115:16:115:27 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:117:16:117:27 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:119:16:119:27 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:136:13:136:24 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:138:13:138:24 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:140:16:140:27 | unsafeQuery1 | provenance | | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | SQLite.swift:142:16:142:27 | unsafeQuery1 | provenance | | +| SQLite.swift:65:21:65:32 | remoteString | SQLite.swift:65:6:65:17 | unsafeQuery1 | provenance | | +| SQLite.swift:66:6:66:17 | unsafeQuery2 | SQLite.swift:74:17:74:28 | unsafeQuery2 | provenance | | +| SQLite.swift:66:62:66:73 | remoteString | SQLite.swift:66:6:66:17 | unsafeQuery2 | provenance | | +| SQLite.swift:67:6:67:17 | unsafeQuery3 | SQLite.swift:75:17:75:28 | unsafeQuery3 | provenance | | +| SQLite.swift:67:6:67:17 | unsafeQuery3 | SQLite.swift:83:29:83:40 | unsafeQuery3 | provenance | | +| SQLite.swift:67:60:67:71 | remoteString | SQLite.swift:67:6:67:17 | unsafeQuery3 | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:55:22:55:33 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:57:14:57:25 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:58:14:58:25 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:59:31:59:42 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:60:14:60:25 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:62:25:62:36 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:64:34:64:45 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:67:21:67:32 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:69:21:69:32 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:72:16:72:27 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:73:23:73:34 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:74:25:74:36 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:75:28:75:39 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:76:34:76:45 | remoteString | provenance | | +| other.swift:51:6:51:17 | remoteString | other.swift:77:19:77:30 | remoteString | provenance | | +| other.swift:51:25:51:79 | String(...) | other.swift:51:6:51:17 | remoteString | provenance | | +| other.swift:59:31:59:42 | remoteString | other.swift:59:14:59:43 | NSString(...) | provenance | | +| other.swift:60:14:60:25 | remoteString | other.swift:60:14:60:33 | TypeCastExpr | provenance | | +| sqlite3_c_api.swift:122:6:122:17 | remoteString | sqlite3_c_api.swift:125:21:125:32 | remoteString | provenance | | +| sqlite3_c_api.swift:122:6:122:17 | remoteString | sqlite3_c_api.swift:126:62:126:73 | remoteString | provenance | | +| sqlite3_c_api.swift:122:6:122:17 | remoteString | sqlite3_c_api.swift:127:60:127:71 | remoteString | provenance | | +| sqlite3_c_api.swift:122:26:122:80 | String(...) | sqlite3_c_api.swift:122:6:122:17 | remoteString | provenance | | +| sqlite3_c_api.swift:125:6:125:17 | unsafeQuery1 | sqlite3_c_api.swift:133:33:133:44 | unsafeQuery1 | provenance | | +| sqlite3_c_api.swift:125:21:125:32 | remoteString | sqlite3_c_api.swift:125:6:125:17 | unsafeQuery1 | provenance | | +| sqlite3_c_api.swift:126:6:126:17 | unsafeQuery2 | sqlite3_c_api.swift:134:33:134:44 | unsafeQuery2 | provenance | | +| sqlite3_c_api.swift:126:62:126:73 | remoteString | sqlite3_c_api.swift:126:6:126:17 | unsafeQuery2 | provenance | | +| sqlite3_c_api.swift:127:6:127:17 | unsafeQuery3 | sqlite3_c_api.swift:135:33:135:44 | unsafeQuery3 | provenance | | +| sqlite3_c_api.swift:127:6:127:17 | unsafeQuery3 | sqlite3_c_api.swift:145:26:145:37 | unsafeQuery3 | provenance | | +| sqlite3_c_api.swift:127:6:127:17 | unsafeQuery3 | sqlite3_c_api.swift:175:29:175:40 | unsafeQuery3 | provenance | | +| sqlite3_c_api.swift:127:6:127:17 | unsafeQuery3 | sqlite3_c_api.swift:183:29:183:40 | unsafeQuery3 | provenance | | +| sqlite3_c_api.swift:127:6:127:17 | unsafeQuery3 | sqlite3_c_api.swift:189:13:189:24 | unsafeQuery3 | provenance | | +| sqlite3_c_api.swift:127:60:127:71 | remoteString | sqlite3_c_api.swift:127:6:127:17 | unsafeQuery3 | provenance | | +| sqlite3_c_api.swift:189:6:189:9 | data | sqlite3_c_api.swift:190:2:190:5 | data | provenance | | +| sqlite3_c_api.swift:189:13:189:24 | unsafeQuery3 | sqlite3_c_api.swift:189:13:189:58 | ... .data(...) | provenance | | +| sqlite3_c_api.swift:189:13:189:58 | ... .data(...) | sqlite3_c_api.swift:189:6:189:9 | data | provenance | | +| sqlite3_c_api.swift:190:2:190:5 | data | sqlite3_c_api.swift:190:21:190:26 | [post] buffer | provenance | | +| sqlite3_c_api.swift:190:21:190:26 | [post] buffer | sqlite3_c_api.swift:194:28:194:33 | buffer | provenance | | +| sqlite3_c_api.swift:190:21:190:26 | [post] buffer | sqlite3_c_api.swift:202:31:202:36 | buffer | provenance | | +| sqlite3_c_api.swift:190:21:190:26 | [post] buffer | sqlite3_c_api.swift:210:31:210:36 | buffer | provenance | | +nodes +| GRDB.swift:104:6:104:17 | remoteString | semmle.label | remoteString | +| GRDB.swift:104:25:104:79 | String(...) | semmle.label | String(...) | +| GRDB.swift:106:41:106:52 | remoteString | semmle.label | remoteString | +| GRDB.swift:108:41:108:52 | remoteString | semmle.label | remoteString | +| GRDB.swift:111:43:111:54 | remoteString | semmle.label | remoteString | +| GRDB.swift:114:51:114:62 | remoteString | semmle.label | remoteString | +| GRDB.swift:117:27:117:38 | remoteString | semmle.label | remoteString | +| GRDB.swift:119:27:119:38 | remoteString | semmle.label | remoteString | +| GRDB.swift:122:41:122:52 | remoteString | semmle.label | remoteString | +| GRDB.swift:124:41:124:52 | remoteString | semmle.label | remoteString | +| GRDB.swift:130:6:130:17 | remoteString | semmle.label | remoteString | +| GRDB.swift:130:26:130:80 | String(...) | semmle.label | String(...) | +| GRDB.swift:132:39:132:50 | remoteString | semmle.label | remoteString | +| GRDB.swift:135:46:135:57 | remoteString | semmle.label | remoteString | +| GRDB.swift:138:56:138:67 | remoteString | semmle.label | remoteString | +| GRDB.swift:141:45:141:56 | remoteString | semmle.label | remoteString | +| GRDB.swift:144:29:144:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:145:29:145:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:146:29:146:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:147:29:147:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:148:29:148:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:149:29:149:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:150:29:150:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:151:29:151:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:164:6:164:17 | remoteString | semmle.label | remoteString | +| GRDB.swift:164:26:164:80 | String(...) | semmle.label | String(...) | +| GRDB.swift:166:32:166:43 | remoteString | semmle.label | remoteString | +| GRDB.swift:167:39:167:50 | remoteString | semmle.label | remoteString | +| GRDB.swift:168:49:168:60 | remoteString | semmle.label | remoteString | +| GRDB.swift:169:38:169:49 | remoteString | semmle.label | remoteString | +| GRDB.swift:170:22:170:33 | remoteString | semmle.label | remoteString | +| GRDB.swift:172:22:172:33 | remoteString | semmle.label | remoteString | +| GRDB.swift:185:6:185:17 | remoteString | semmle.label | remoteString | +| GRDB.swift:185:26:185:80 | String(...) | semmle.label | String(...) | +| GRDB.swift:187:33:187:44 | remoteString | semmle.label | remoteString | +| GRDB.swift:190:32:190:43 | remoteString | semmle.label | remoteString | +| GRDB.swift:193:37:193:48 | remoteString | semmle.label | remoteString | +| GRDB.swift:199:6:199:17 | remoteString | semmle.label | remoteString | +| GRDB.swift:199:26:199:80 | String(...) | semmle.label | String(...) | +| GRDB.swift:201:36:201:47 | remoteString | semmle.label | remoteString | +| GRDB.swift:207:6:207:17 | remoteString | semmle.label | remoteString | +| GRDB.swift:207:26:207:80 | String(...) | semmle.label | String(...) | +| GRDB.swift:209:41:209:52 | remoteString | semmle.label | remoteString | +| GRDB.swift:210:44:210:55 | remoteString | semmle.label | remoteString | +| GRDB.swift:211:47:211:58 | remoteString | semmle.label | remoteString | +| GRDB.swift:212:47:212:58 | remoteString | semmle.label | remoteString | +| GRDB.swift:222:6:222:17 | remoteString | semmle.label | remoteString | +| GRDB.swift:222:26:222:80 | String(...) | semmle.label | String(...) | +| GRDB.swift:224:37:224:48 | remoteString | semmle.label | remoteString | +| GRDB.swift:225:37:225:48 | remoteString | semmle.label | remoteString | +| GRDB.swift:229:37:229:48 | remoteString | semmle.label | remoteString | +| GRDB.swift:230:37:230:48 | remoteString | semmle.label | remoteString | +| GRDB.swift:234:36:234:47 | remoteString | semmle.label | remoteString | +| GRDB.swift:235:36:235:47 | remoteString | semmle.label | remoteString | +| GRDB.swift:242:6:242:17 | remoteString | semmle.label | remoteString | +| GRDB.swift:242:26:242:80 | String(...) | semmle.label | String(...) | +| GRDB.swift:244:38:244:49 | remoteString | semmle.label | remoteString | +| GRDB.swift:250:6:250:17 | remoteString | semmle.label | remoteString | +| GRDB.swift:250:26:250:80 | String(...) | semmle.label | String(...) | +| GRDB.swift:252:32:252:43 | remoteString | semmle.label | remoteString | +| GRDB.swift:253:32:253:43 | remoteString | semmle.label | remoteString | +| GRDB.swift:254:32:254:43 | remoteString | semmle.label | remoteString | +| GRDB.swift:255:32:255:43 | remoteString | semmle.label | remoteString | +| GRDB.swift:261:29:261:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:262:29:262:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:263:29:263:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:264:29:264:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:270:29:270:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:271:29:271:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:272:29:272:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:273:29:273:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:279:29:279:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:280:29:280:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:281:29:281:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:282:29:282:40 | remoteString | semmle.label | remoteString | +| GRDB.swift:291:6:291:17 | remoteString | semmle.label | remoteString | +| GRDB.swift:291:26:291:80 | String(...) | semmle.label | String(...) | +| GRDB.swift:293:53:293:64 | remoteString | semmle.label | remoteString | +| GRDB.swift:294:53:294:64 | remoteString | semmle.label | remoteString | +| GRDB.swift:295:53:295:64 | remoteString | semmle.label | remoteString | +| GRDB.swift:296:53:296:64 | remoteString | semmle.label | remoteString | +| GRDB.swift:302:50:302:61 | remoteString | semmle.label | remoteString | +| GRDB.swift:303:50:303:61 | remoteString | semmle.label | remoteString | +| GRDB.swift:304:50:304:61 | remoteString | semmle.label | remoteString | +| GRDB.swift:305:50:305:61 | remoteString | semmle.label | remoteString | +| GRDB.swift:311:50:311:61 | remoteString | semmle.label | remoteString | +| GRDB.swift:312:50:312:61 | remoteString | semmle.label | remoteString | +| GRDB.swift:313:50:313:61 | remoteString | semmle.label | remoteString | +| GRDB.swift:314:50:314:61 | remoteString | semmle.label | remoteString | +| GRDB.swift:320:50:320:61 | remoteString | semmle.label | remoteString | +| GRDB.swift:321:50:321:61 | remoteString | semmle.label | remoteString | +| GRDB.swift:322:50:322:61 | remoteString | semmle.label | remoteString | +| GRDB.swift:323:50:323:61 | remoteString | semmle.label | remoteString | +| GRDB.swift:332:6:332:17 | remoteString | semmle.label | remoteString | +| GRDB.swift:332:26:332:80 | String(...) | semmle.label | String(...) | +| GRDB.swift:334:57:334:68 | remoteString | semmle.label | remoteString | +| GRDB.swift:335:57:335:68 | remoteString | semmle.label | remoteString | +| GRDB.swift:342:6:342:17 | remoteString | semmle.label | remoteString | +| GRDB.swift:342:26:342:80 | String(...) | semmle.label | String(...) | +| GRDB.swift:344:51:344:62 | remoteString | semmle.label | remoteString | +| GRDB.swift:345:51:345:62 | remoteString | semmle.label | remoteString | +| GRDB.swift:346:66:346:77 | remoteString | semmle.label | remoteString | +| GRDB.swift:347:66:347:77 | remoteString | semmle.label | remoteString | +| GRDB.swift:348:69:348:80 | remoteString | semmle.label | remoteString | +| GRDB.swift:349:84:349:95 | remoteString | semmle.label | remoteString | +| GRDB.swift:350:69:350:80 | remoteString | semmle.label | remoteString | +| GRDB.swift:351:84:351:95 | remoteString | semmle.label | remoteString | +| SQLite.swift:62:6:62:17 | remoteString | semmle.label | remoteString | +| SQLite.swift:62:25:62:79 | String(...) | semmle.label | String(...) | +| SQLite.swift:65:6:65:17 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:65:21:65:32 | remoteString | semmle.label | remoteString | +| SQLite.swift:66:6:66:17 | unsafeQuery2 | semmle.label | unsafeQuery2 | +| SQLite.swift:66:62:66:73 | remoteString | semmle.label | remoteString | +| SQLite.swift:67:6:67:17 | unsafeQuery3 | semmle.label | unsafeQuery3 | +| SQLite.swift:67:60:67:71 | remoteString | semmle.label | remoteString | +| SQLite.swift:73:17:73:28 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:74:17:74:28 | unsafeQuery2 | semmle.label | unsafeQuery2 | +| SQLite.swift:75:17:75:28 | unsafeQuery3 | semmle.label | unsafeQuery3 | +| SQLite.swift:83:29:83:40 | unsafeQuery3 | semmle.label | unsafeQuery3 | +| SQLite.swift:95:32:95:43 | remoteString | semmle.label | remoteString | +| SQLite.swift:100:29:100:40 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:103:29:103:40 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:106:29:106:40 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:109:13:109:24 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:111:13:111:24 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:113:13:113:24 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:115:16:115:27 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:117:16:117:27 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:119:16:119:27 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:134:20:134:31 | remoteString | semmle.label | remoteString | +| SQLite.swift:136:13:136:24 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:138:13:138:24 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:140:16:140:27 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| SQLite.swift:142:16:142:27 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| other.swift:51:6:51:17 | remoteString | semmle.label | remoteString | +| other.swift:51:25:51:79 | String(...) | semmle.label | String(...) | +| other.swift:55:22:55:33 | remoteString | semmle.label | remoteString | +| other.swift:57:14:57:25 | remoteString | semmle.label | remoteString | +| other.swift:58:14:58:25 | remoteString | semmle.label | remoteString | +| other.swift:59:14:59:43 | NSString(...) | semmle.label | NSString(...) | +| other.swift:59:31:59:42 | remoteString | semmle.label | remoteString | +| other.swift:60:14:60:25 | remoteString | semmle.label | remoteString | +| other.swift:60:14:60:33 | TypeCastExpr | semmle.label | TypeCastExpr | +| other.swift:62:25:62:36 | remoteString | semmle.label | remoteString | +| other.swift:64:34:64:45 | remoteString | semmle.label | remoteString | +| other.swift:67:21:67:32 | remoteString | semmle.label | remoteString | +| other.swift:69:21:69:32 | remoteString | semmle.label | remoteString | +| other.swift:72:16:72:27 | remoteString | semmle.label | remoteString | +| other.swift:73:23:73:34 | remoteString | semmle.label | remoteString | +| other.swift:74:25:74:36 | remoteString | semmle.label | remoteString | +| other.swift:75:28:75:39 | remoteString | semmle.label | remoteString | +| other.swift:76:34:76:45 | remoteString | semmle.label | remoteString | +| other.swift:77:19:77:30 | remoteString | semmle.label | remoteString | +| sqlite3_c_api.swift:122:6:122:17 | remoteString | semmle.label | remoteString | +| sqlite3_c_api.swift:122:26:122:80 | String(...) | semmle.label | String(...) | +| sqlite3_c_api.swift:125:6:125:17 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| sqlite3_c_api.swift:125:21:125:32 | remoteString | semmle.label | remoteString | +| sqlite3_c_api.swift:126:6:126:17 | unsafeQuery2 | semmle.label | unsafeQuery2 | +| sqlite3_c_api.swift:126:62:126:73 | remoteString | semmle.label | remoteString | +| sqlite3_c_api.swift:127:6:127:17 | unsafeQuery3 | semmle.label | unsafeQuery3 | +| sqlite3_c_api.swift:127:60:127:71 | remoteString | semmle.label | remoteString | +| sqlite3_c_api.swift:133:33:133:44 | unsafeQuery1 | semmle.label | unsafeQuery1 | +| sqlite3_c_api.swift:134:33:134:44 | unsafeQuery2 | semmle.label | unsafeQuery2 | +| sqlite3_c_api.swift:135:33:135:44 | unsafeQuery3 | semmle.label | unsafeQuery3 | +| sqlite3_c_api.swift:145:26:145:37 | unsafeQuery3 | semmle.label | unsafeQuery3 | +| sqlite3_c_api.swift:175:29:175:40 | unsafeQuery3 | semmle.label | unsafeQuery3 | +| sqlite3_c_api.swift:183:29:183:40 | unsafeQuery3 | semmle.label | unsafeQuery3 | +| sqlite3_c_api.swift:189:6:189:9 | data | semmle.label | data | +| sqlite3_c_api.swift:189:13:189:24 | unsafeQuery3 | semmle.label | unsafeQuery3 | +| sqlite3_c_api.swift:189:13:189:58 | ... .data(...) | semmle.label | ... .data(...) | +| sqlite3_c_api.swift:190:2:190:5 | data | semmle.label | data | +| sqlite3_c_api.swift:190:21:190:26 | [post] buffer | semmle.label | [post] buffer | +| sqlite3_c_api.swift:194:28:194:33 | buffer | semmle.label | buffer | +| sqlite3_c_api.swift:202:31:202:36 | buffer | semmle.label | buffer | +| sqlite3_c_api.swift:210:31:210:36 | buffer | semmle.label | buffer | +subpaths diff --git a/unified/ql/test/query-tests/security/CWE-089/SqlInjection/SqlInjection.qlref b/unified/ql/test/query-tests/security/CWE-089/SqlInjection/SqlInjection.qlref new file mode 100644 index 000000000000..dcf21a24fc27 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-089/SqlInjection/SqlInjection.qlref @@ -0,0 +1,3 @@ +query: queries/security/CWE-089/SqlInjection.ql +postprocess: + - utils/test/InlineExpectationsTestQuery.ql diff --git a/unified/ql/test/query-tests/security/CWE-089/SqlInjection/other.swift b/unified/ql/test/query-tests/security/CWE-089/SqlInjection/other.swift new file mode 100644 index 000000000000..d4cb09877eb3 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-089/SqlInjection/other.swift @@ -0,0 +1,81 @@ + +// --- stubs --- + +struct URL +{ + init?(string: String) {} +} + +extension String { + init(contentsOf: URL) throws { + self.init("") + } +} + +class NSObject { +} + +class NSString : NSObject { + init(string: String) { } +} + +class Sql { +} + +class MyDatabase { + init(sql code: String? = nil) { } + + func execute1(_ sql: String) { } + func execute2(_ sql: String?) { } + func execute3(_ sql: NSString) { } + func execute4(_ sql: Sql) { } + func execute5(_ context: String, _ sql: String) { } + func execute6(context: String, _ sql: String) { } + func queryCode(code sql: String) { } + func queryCode2(sql code: String) { } + + func query(sql: String) { } + func query(sqlLiteral: String) { } + func query(sqlStatement: String) { } + func query(sqliteStatement: String) { } + func query(config: String, sql: String) { } + + // non-examples + func doSomething(sqlIndex: Int) { } + func doSomething(sqliteContext: Sql) { } +} + +// --- tests --- + +func test_heuristic(db: MyDatabase) throws { + let remoteString = try String(contentsOf: URL(string: "http://example.com/")!) // $ Source + + _ = MyDatabase() // GOOD + _ = MyDatabase(sql: "some_fixed_sql") // GOOD + _ = MyDatabase(sql: remoteString) // $ Alert + + db.execute1(remoteString) // $ Alert + db.execute2(remoteString) // $ Alert + db.execute3(NSString(string: remoteString)) // $ Alert + db.execute4(remoteString as! Sql) // $ Alert + + db.execute5("context", remoteString) // $ Alert + db.execute5(remoteString, "SELECT 1") // GOOD + db.execute6(context: "context", remoteString) // $ Alert + db.execute6(context: remoteString, "SELECT 1") // GOOD + + db.queryCode(code: remoteString) // $ Alert + db.queryCode(code: "SELECT 1") // GOOD + db.queryCode2(sql: remoteString) // $ Alert + db.queryCode2(sql: "SELECT 1") // GOOD + + db.query(sql: remoteString) // $ Alert + db.query(sqlLiteral: remoteString) // $ Alert + db.query(sqlStatement: remoteString) // $ Alert + db.query(sqliteStatement: remoteString) // $ Alert + db.query(config: "config", sql: remoteString) // $ Alert + db.query(config: remoteString, sql: "SELECT 1") // $ SPURIOUS: Alert + + db.doSomething(sqlIndex: Int(remoteString) ?? 0) // GOOD + db.doSomething(sqliteContext: remoteString as! Sql) // GOOD +} diff --git a/unified/ql/test/query-tests/security/CWE-089/SqlInjection/sqlite3_c_api.swift b/unified/ql/test/query-tests/security/CWE-089/SqlInjection/sqlite3_c_api.swift new file mode 100644 index 000000000000..b4e7451b9163 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-089/SqlInjection/sqlite3_c_api.swift @@ -0,0 +1,215 @@ + +// --- stubs --- + +struct URL +{ + init?(string: String) {} + init?(string: String, relativeTo: URL?) {} +} +protocol DataProtocol { } +struct Data : DataProtocol { + init(_ elements: S) { count = 0 } + + var count: Int + + func copyBytes(to pointer: UnsafeMutablePointer, count: Int) {} +} + +extension String { + init(contentsOf: URL) throws { + var data = "" + + // ... + + self.init(data) + } + + struct Encoding { + var rawValue: UInt + + init(rawValue: UInt) { + self.rawValue = rawValue + } + + static let utf16 = Encoding(rawValue: 1) + } + + func data(using encoding: String.Encoding, allowLossyConversion: Bool = false) -> Data? { return nil } +} + +var SQLITE_OK : Int32 = 0 +var SQLITE_TRANSIENT : (@convention(c) (UnsafeMutableRawPointer?) -> Void)? + +func sqlite3_exec( + _ _: OpaquePointer?, + _ sql: UnsafePointer?, + _ callback: (@convention(c) (UnsafeMutableRawPointer?, Int32, UnsafeMutablePointer?>?, UnsafeMutablePointer?>?) -> Int32)?, + _ _: UnsafeMutableRawPointer?, + _ errmsg: UnsafeMutablePointer?>? + ) -> Int32 { return SQLITE_OK } + +func sqlite3_prepare( + _ db: OpaquePointer?, + _ zSql: UnsafePointer?, + _ nByte: Int32, + _ ppStmt: UnsafeMutablePointer?, + _ pzTail: UnsafeMutablePointer?>? + ) -> Int32 { return SQLITE_OK } + +func sqlite3_prepare_v2( + _ db: OpaquePointer?, + _ zSql: UnsafePointer?, + _ nByte: Int32, + _ ppStmt: UnsafeMutablePointer?, + _ pzTail: UnsafeMutablePointer?>? + ) -> Int32 { return SQLITE_OK } + +func sqlite3_prepare_v3( + _ db: OpaquePointer?, + _ zSql: UnsafePointer?, + _ nByte: Int32, + _ prepFlags: UInt32, + _ ppStmt: UnsafeMutablePointer?, + _ pzTail: UnsafeMutablePointer?>? + ) -> Int32 { return SQLITE_OK } + +func sqlite3_prepare16( + _ db: OpaquePointer?, + _ zSql: UnsafeRawPointer?, + _ nByte: Int32, + _ ppStmt: UnsafeMutablePointer?, + _ pzTail: UnsafeMutablePointer? + ) -> Int32 { return SQLITE_OK } + +func sqlite3_prepare16_v2( + _ db: OpaquePointer?, + _ zSql: UnsafeRawPointer?, + _ nByte: Int32, + _ ppStmt: UnsafeMutablePointer?, + _ pzTail: UnsafeMutablePointer? + ) -> Int32 { return SQLITE_OK } + +func sqlite3_prepare16_v3( + _ db: OpaquePointer?, + _ zSql: UnsafeRawPointer?, + _ nByte: Int32, + _ prepFlags: UInt32, + _ ppStmt: UnsafeMutablePointer?, + _ pzTail: UnsafeMutablePointer? + ) -> Int32 { return SQLITE_OK } + +func sqlite3_bind_text( + _ _: OpaquePointer?, + _ _: Int32, + _ _: UnsafePointer?, + _ _: Int32, + _ callback: (@convention(c) (UnsafeMutableRawPointer?) -> Void)? + ) -> Int32 { return SQLITE_OK } +// (many other `sqlite3_bind_*` functions exist) + +func sqlite3_step( + _ _: OpaquePointer? + ) -> Int32 { return SQLITE_OK } + +func sqlite3_finalize( + _ pStmt: OpaquePointer? + ) -> Int32 { return SQLITE_OK } + +// --- tests --- + +func test_sqlite3_c_api(db: OpaquePointer?, buffer: UnsafeMutablePointer) { + let localString = "user" + let remoteString = try! String(contentsOf: URL(string: "http://example.com/")!) // $ Source + let remoteNumber = Int(remoteString) ?? 0 + + let unsafeQuery1 = remoteString + let unsafeQuery2 = "SELECT * FROM users WHERE username='" + remoteString + "'" + let unsafeQuery3 = "SELECT * FROM users WHERE username='\(remoteString)'" + let safeQuery1 = "SELECT * FROM users WHERE username='\(localString)'" + let safeQuery2 = "SELECT * FROM users WHERE username='\(remoteNumber)'" + + // --- exec --- + + let result1 = sqlite3_exec(db, unsafeQuery1, nil, nil, nil) // $ Alert + let result2 = sqlite3_exec(db, unsafeQuery2, nil, nil, nil) // $ Alert + let result3 = sqlite3_exec(db, unsafeQuery3, nil, nil, nil) // $ Alert + let result4 = sqlite3_exec(db, safeQuery1, nil, nil, nil) // GOOD + let result5 = sqlite3_exec(db, safeQuery2, nil, nil, nil) // GOOD + + // --- prepared statements --- + + let varQuery = "SELECT * FROM users WHERE username=?" + + var stmt1: OpaquePointer? + + if (sqlite3_prepare(db, unsafeQuery3, -1, &stmt1, nil) == SQLITE_OK) { // $ Alert + let result = sqlite3_step(stmt1) + // ... + } + sqlite3_finalize(stmt1) + + var stmt2: OpaquePointer? + + if (sqlite3_prepare(db, varQuery, -1, &stmt2, nil) == SQLITE_OK) { // GOOD + if (sqlite3_bind_text(stmt2, 1, localString, -1, SQLITE_TRANSIENT) == SQLITE_OK) { // GOOD + let result = sqlite3_step(stmt2) + // ... + } + } + sqlite3_finalize(stmt2) + + var stmt3: OpaquePointer? + + if (sqlite3_prepare(db, varQuery, -1, &stmt3, nil) == SQLITE_OK) { // GOOD + if (sqlite3_bind_text(stmt3, 1, remoteString, -1, SQLITE_TRANSIENT) == SQLITE_OK) { // GOOD + let result = sqlite3_step(stmt3) + // ... + } + } + sqlite3_finalize(stmt3) + + // --- variant 'prepare' functions --- + + var stmt4: OpaquePointer? + + if (sqlite3_prepare_v2(db, unsafeQuery3, -1, &stmt4, nil) == SQLITE_OK) { // $ Alert + let result = sqlite3_step(stmt4) + // ... + } + sqlite3_finalize(stmt4) + + var stmt5: OpaquePointer? + + if (sqlite3_prepare_v3(db, unsafeQuery3, -1, 0, &stmt5, nil) == SQLITE_OK) { // $ Alert + let result = sqlite3_step(stmt5) + // ... + } + sqlite3_finalize(stmt5) + + let data = unsafeQuery3.data(using:String.Encoding.utf16)! + data.copyBytes(to: buffer, count: data.count) + + var stmt6: OpaquePointer? + + if (sqlite3_prepare16(db, buffer, Int32(data.count), &stmt6, nil) == SQLITE_OK) { // $ Alert + let result = sqlite3_step(stmt6) + // ... + } + sqlite3_finalize(stmt6) + + var stmt7: OpaquePointer? + + if (sqlite3_prepare16_v2(db, buffer, Int32(data.count), &stmt7, nil) == SQLITE_OK) { // $ Alert + let result = sqlite3_step(stmt7) + // ... + } + sqlite3_finalize(stmt7) + + var stmt8: OpaquePointer? + + if (sqlite3_prepare16_v3(db, buffer, Int32(data.count), 0, &stmt8, nil) == SQLITE_OK) { // $ Alert + let result = sqlite3_step(stmt8) + // ... + } + sqlite3_finalize(stmt8) +}