From 629db4f7cff529bcbb41f10512d41121bbbef258 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 24 Sep 2026 13:22:38 +0200 Subject: [PATCH 1/7] unified: Add no-op path injection and port test suite --- .../security/CWE-022/PathInjection.qhelp | 58 ++ .../queries/security/CWE-022/PathInjection.ql | 36 ++ .../security/CWE-022/PathInjectionBad.swift | 10 + .../security/CWE-022/PathInjectionGood.swift | 8 + .../PathInjection/PathInjectionTest.expected | 4 + .../PathInjection/PathInjectionTest.qlref | 3 + .../PathInjection/testPathInjection.swift | 594 ++++++++++++++++++ 7 files changed, 713 insertions(+) create mode 100644 unified/ql/src/queries/security/CWE-022/PathInjection.qhelp create mode 100644 unified/ql/src/queries/security/CWE-022/PathInjection.ql create mode 100644 unified/ql/src/queries/security/CWE-022/PathInjectionBad.swift create mode 100644 unified/ql/src/queries/security/CWE-022/PathInjectionGood.swift create mode 100644 unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected create mode 100644 unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.qlref create mode 100644 unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift diff --git a/unified/ql/src/queries/security/CWE-022/PathInjection.qhelp b/unified/ql/src/queries/security/CWE-022/PathInjection.qhelp new file mode 100644 index 000000000000..a4ec83290a19 --- /dev/null +++ b/unified/ql/src/queries/security/CWE-022/PathInjection.qhelp @@ -0,0 +1,58 @@ + + + + +

Accessing paths controlled by users can expose resources to attackers.

+ +

Paths that are naively constructed from data controlled by a user may contain unexpected special characters, +such as ... Such a path could point to any directory on the file system.

+
+ + + +

Validate user input before using it to construct a file path. Ideally, follow these rules:

+ +
    +
  • Do not allow more than a single . character.
  • +
  • Do not allow directory separators such as / or \ (depending on the file system).
  • +
  • Do not rely on simply replacing problematic sequences such as ../. For example, after applying this filter to +.../...// the resulting string would still be ../.
  • +
  • Use a whitelist of known good patterns.
  • +
+ +
+ + +

+The following code shows two bad examples. +

+ + + +

+In the first, a file name is read from an HTTP request and then used to access a file. In this case, a malicious response could include a file name that is an absolute path, such as +"/Applications/(current_application)/Documents/sensitive.data". +

+ +

+In the second bad example, it appears that the user is restricted to opening a file within the +"/Library/Caches" home directory. In this case, a malicious response could contain a file name containing +special characters. For example, the string "../../Documents/sensitive.data" will result in the code +reading the file located at "/Applications/(current_application)/Library/Caches/../../Documents/sensitive.data", +which contains users' sensitive data. This file may then be made accessible to an attacker, giving them access to all this data. +

+ +

+In the following (good) example, the path used to access the file system is normalized before being checked against a +known prefix. This ensures that regardless of the user input, the resulting path is safe. +

+ + +
+ + +
  • OWASP: Path Traversal.
  • +
    +
    diff --git a/unified/ql/src/queries/security/CWE-022/PathInjection.ql b/unified/ql/src/queries/security/CWE-022/PathInjection.ql new file mode 100644 index 000000000000..5796e8e0b2c7 --- /dev/null +++ b/unified/ql/src/queries/security/CWE-022/PathInjection.ql @@ -0,0 +1,36 @@ +/** + * @name Uncontrolled data used in path expression + * @description Accessing paths influenced by users can allow an attacker to access unexpected resources. + * @kind path-problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id unified/swift/path-injection + * @tags security + * external/cwe/cwe-022 + * external/cwe/cwe-023 + * external/cwe/cwe-036 + * external/cwe/cwe-073 + * external/cwe/cwe-099 + */ + +import unified + +module PathInjectionConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node node) { none() } + + predicate isSink(DataFlow::Node node) { none() } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { none() } + + predicate isBarrier(DataFlow::Node node) { none() } +} + +module PathInjectionFlow = DataFlow::Global; + +import PathInjectionFlow::PathGraph + +from PathInjectionFlow::PathNode source, PathInjectionFlow::PathNode sink +where PathInjectionFlow::flowPath(source, sink) +select sink.getNode(), source, sink, "This path depends on a $@.", source.getNode(), + "user-provided value" diff --git a/unified/ql/src/queries/security/CWE-022/PathInjectionBad.swift b/unified/ql/src/queries/security/CWE-022/PathInjectionBad.swift new file mode 100644 index 000000000000..9ce9d0f42e8b --- /dev/null +++ b/unified/ql/src/queries/security/CWE-022/PathInjectionBad.swift @@ -0,0 +1,10 @@ +let fm = FileManager.default +let path = try String(contentsOf: URL(string: "http://example.com/")!) + +// BAD +return fm.contents(atPath: path) + +// BAD +if (path.hasPrefix(NSHomeDirectory() + "/Library/Caches")) { + return fm.contents(atPath: path) +} diff --git a/unified/ql/src/queries/security/CWE-022/PathInjectionGood.swift b/unified/ql/src/queries/security/CWE-022/PathInjectionGood.swift new file mode 100644 index 000000000000..8e0799fc4862 --- /dev/null +++ b/unified/ql/src/queries/security/CWE-022/PathInjectionGood.swift @@ -0,0 +1,8 @@ +let fm = FileManager.default +let path = try String(contentsOf: URL(string: "http://example.com/")!) + +// GOOD +let filePath = FilePath(stringLiteral: path) +if (filePath.lexicallyNormalized().starts(with: FilePath(stringLiteral: NSHomeDirectory() + "/Library/Caches"))) { + return fm.contents(atPath: path) +} diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected new file mode 100644 index 000000000000..58f42bec0c84 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected @@ -0,0 +1,4 @@ +#select +edges +nodes +subpaths diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.qlref b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.qlref new file mode 100644 index 000000000000..6269075fd961 --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.qlref @@ -0,0 +1,3 @@ +query: queries/Security/CWE-022/PathInjection.ql +postprocess: + - utils/test/InlineExpectationsTestQuery.ql diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift b/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift new file mode 100644 index 000000000000..d1076f47853d --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift @@ -0,0 +1,594 @@ +// --- stubs --- + +struct URL { + enum DirectoryHint { + case inferFromPath + } + + init?(string: String) {} + init(fileURLWithPath path: String, isDirectory: Bool) {} + init( + filePath path: String, directoryHint: URL.DirectoryHint = .inferFromPath, + relativeTo base: URL? = nil + ) {} + + mutating func appendPathComponent(_ pathComponent: String) {} + func appendingPathComponent(_ pathComponent: String) -> URL { return self } +} + +class NSURL { + init?(string: String) {} + + func appendingPathComponent(_ pathComponent: String) -> URL? { return nil } + + var filePathURL: URL? { URL(string: "") } +} + +extension StringProtocol { + func completePath( + into outputName: UnsafeMutablePointer? = nil, + caseSensitive: Bool, + matchesInto outputArray: UnsafeMutablePointer<[String]>? = nil, + filterTypes: [String]? = nil + ) -> Int { 0 } +} + +extension String { + struct Encoding { + static let utf8 = Encoding() + } + + init(contentsOf: URL) { + let data = "" + self.init(data) + } + + init(contentsOfFile path: String) throws { + self.init("") + } + + init(contentsOfFile path: String, encoding enc: String.Encoding) throws { + self.init("") + } + + init(contentsOfFile path: String, usedEncoding: inout String.Encoding) throws { + self.init("") + } +} + +class NSString { + init() {} + init(string aString: String) {} + convenience init(contentsOfFile path: String, encoding enc: UInt) throws { self.init() } + convenience init(contentsOfFile path: String, usedEncoding enc: UnsafeMutablePointer?) + throws + { self.init() } + + func write(toFile: String, atomically: Bool, encoding: UInt) {} + func write(to: URL, atomically: Bool, encoding: UInt) {} + + var utf8String: UnsafePointer? { return nil } +} +protocol DataProtocol {} +class Data: DataProtocol { + struct ReadingOptions: OptionSet { let rawValue: Int } + struct WritingOptions: OptionSet { let rawValue: Int } + + init(_ elements: S) { count = 0 } + init(contentsOf: URL, options: ReadingOptions) { count = 0 } + + func copyBytes(to: UnsafeMutablePointer, count: Int) {} + + func write(to: URL, options: Data.WritingOptions = []) {} + + var count: Int +} + +class NSData { + init() {} + init?(contentsOfFile path: String) {} + init?(contentsOfMappedFile path: String) {} + init?(contentsOf url: URL) {} + class func dataWithContentsOfMappedFile(_ path: String) -> Any? { return nil } + + struct WritingOptions: OptionSet { let rawValue: Int } + func write(to: URL, atomically: Bool) -> Bool { return false } + func write(to: URL, options: NSData.WritingOptions) {} + func write(toFile: String, atomically: Bool) -> Bool { return false } + func write(toFile: String, options: NSData.WritingOptions) {} +} + +class NSKeyedUnarchiver { + func unarchiveObject(withFile: String) -> Any? { return nil } +} + +struct URLResourceKey {} + +struct FileAttributeKey: Hashable {} + +struct ObjCBool {} + +struct AutoreleasingUnsafeMutablePointer {} + +struct FilePath: ExpressibleByStringLiteral { + typealias StringLiteralType = String + init(stringLiteral: String) {} + func lexicallyNormalized() -> FilePath { return "" } + func starts(with: FilePath) -> Bool { return false } +} + +class FileManager { + class DirectoryEnumerator {} + struct DirectoryEnumerationOptions: OptionSet { let rawValue: Int } + struct ItemReplacementOptions: OptionSet { let rawValue: Int } + struct UnmountOptions: OptionSet { let rawValue: Int } + struct SearchPathDomainMask {} + enum SearchPathDirectory: UInt { case none } + enum URLRelationship: Int { case none } + + func contentsOfDirectory( + at: URL, includingPropertiesForKeys: [URLResourceKey]?, + options: FileManager.DirectoryEnumerationOptions + ) -> [URL] { return [] } + func contentsOfDirectory(atPath: String) -> [String] { return [] } + func enumerator( + at: URL, includingPropertiesForKeys: [URLResourceKey]?, + options: FileManager.DirectoryEnumerationOptions, errorHandler: ((URL, Error) -> Bool)? + ) -> FileManager.DirectoryEnumerator? { return nil } + func enumerator(atPath: String) -> FileManager.DirectoryEnumerator? { return nil } + func subpathsOfDirectory(atPath: String) -> [String] { return [] } + func subpaths(atPath: String) -> [String]? { return nil } + func createDirectory( + at: URL, withIntermediateDirectories: Bool, attributes: [FileAttributeKey: Any]? + ) {} + func createDirectory( + atPath: String, withIntermediateDirectories: Bool, attributes: [FileAttributeKey: Any]? + ) {} + func createFile(atPath: String, contents: Data?, attributes: [FileAttributeKey: Any]?) -> Bool { + return false + } + func removeItem(at: URL) {} + func removeItem(atPath: String) {} + func trashItem(at: URL, resultingItemURL: AutoreleasingUnsafeMutablePointer?) {} + func replaceItemAt( + _: URL, withItemAt: URL, backupItemName: String?, + options: FileManager.ItemReplacementOptions + ) -> URL? { return nil } + func replaceItem( + at: URL, withItemAt: URL, backupItemName: String?, + options: FileManager.ItemReplacementOptions, + resultingItemURL: AutoreleasingUnsafeMutablePointer? + ) {} + func copyItem(at: URL, to: URL) {} + func copyItem(atPath: String, toPath: String) {} + func moveItem(at: URL, to: URL) {} + func moveItem(atPath: String, toPath: String) {} + func createSymbolicLink(at: URL, withDestinationURL: URL) {} + func createSymbolicLink(atPath: String, withDestinationPath: String) {} + func linkItem(at: URL, to: URL) {} + func linkItem(atPath: String, toPath: String) {} + func destinationOfSymbolicLink(atPath: String) -> String { return "" } + func fileExists(atPath: String) -> Bool { return false } + func fileExists(atPath: String, isDirectory: UnsafeMutablePointer?) -> Bool { + return false + } + func setAttributes(_: [FileAttributeKey: Any], ofItemAtPath: String) {} + func contents(atPath: String) -> Data? { return nil } + func contentsEqual(atPath: String, andPath: String) -> Bool { return false } + func changeCurrentDirectoryPath(_: String) -> Bool { return false } + func unmountVolume( + at: URL, options: FileManager.UnmountOptions, completionHandler: (Error?) -> Void + ) {} + // Deprecated methods + func fileAttributes(atPath path: String, traverseLink yorn: Bool) -> [AnyHashable: Any]? { nil } + func changeFileAttributes(_: [AnyHashable: Any], atPath: String) -> Bool { return false } + func attributesOfItem(atPath path: String) throws -> [FileAttributeKey: Any] { return [:] } + func directoryContents(atPath: String) -> [Any]? { return nil } + func createDirectory(atPath: String, attributes: [AnyHashable: Any]) -> Bool { return false } + func createSymbolicLink(atPath: String, pathContent: String) -> Bool { return false } + func pathContentOfSymbolicLink(atPath: String) -> String? { return nil } + func replaceItemAtURL( + originalItemURL: NSURL, withItemAtURL: NSURL, backupItemName: String?, + options: FileManager.ItemReplacementOptions + ) -> NSURL? { return nil } +} + +struct FileDescriptor { + struct AccessMode: RawRepresentable { + static let readOnly = AccessMode(rawValue: 0) + let rawValue: UInt8 + init(rawValue: UInt8) { self.rawValue = rawValue } + } + + struct OpenOptions: RawRepresentable { + static let append = OpenOptions(rawValue: 0) + let rawValue: UInt8 + init(rawValue: UInt8) { self.rawValue = rawValue } + } +} + +struct FilePermissions: RawRepresentable { + static let ownerRead = FilePermissions(rawValue: 0) + let rawValue: UInt8 + init(rawValue: UInt8) { self.rawValue = rawValue } +} + +class ArchiveByteStream { + static func fileStream(fd: FileDescriptor, automaticClose: Bool = true) -> ArchiveByteStream? { + return nil + } + static func withFileStream( + fd: FileDescriptor, automaticClose: Bool = true, _ body: (ArchiveByteStream) -> E + ) -> E { return body(ArchiveByteStream()) } + static func fileStream( + path: FilePath, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions, + permissions: FilePermissions + ) -> ArchiveByteStream? { return nil } + static func withFileStream( + path: FilePath, mode: FileDescriptor.AccessMode, options: FileDescriptor.OpenOptions, + permissions: FilePermissions, _ body: (ArchiveByteStream) -> E + ) -> E { return body(ArchiveByteStream()) } +} + +class Bundle { + init?(url: URL) {} + init?(path: String) {} +} + +class KeyPath { +} + +class NSSortDescriptor { + init(key: String?, ascending: Bool) {} + convenience init(keyPath: KeyPath, ascending: Bool) { + self.init(key: nil, ascending: ascending) + } +} + +// GRDB + +struct Configuration {} + +class Database { + init(path: String, description: String, configuration: Configuration) {} +} + +class DatabasePool { + init(path: String, configuration: Configuration) {} +} + +class DatabaseQueue { + init(path: String, configuration: Configuration) {} +} + +class DatabaseSnapshotPool { + init(path: String, configuration: Configuration) {} +} + +class SerializedDatabase { + init( + path: String, configuration: Configuration = Configuration(), defaultLabel: String, + purpose: String? = nil + ) {} +} + +// Realm + +class Realm { +} + +extension Realm { + struct Configuration { + init( + fileURL: URL? = URL(fileURLWithPath: "defaultFile", isDirectory: false), + inMemoryIdentifier: String? = nil, + syncConfiguration: Int = 0, + encryptionKey: Data? = nil, + readOnly: Bool = false, + schemaVersion: UInt64 = 0, + migrationBlock: Int = 0, + deleteRealmIfMigrationNeeded: Bool = false, + shouldCompactOnLaunch: Bool = false, + objectTypes: Int = 0, + seedFilePath: URL? = nil + ) {} + + var fileURL: URL? + var seedFilePath: URL? + } +} + +// sqlite3 + +func sqlite3_open( + _ filename: UnsafePointer?, + _ ppDb: UnsafeMutablePointer? +) -> Int32 { return 0 } + +func sqlite3_open16( + _ filename: UnsafeRawPointer?, + _ ppDb: UnsafeMutablePointer? +) -> Int32 { return 0 } + +func sqlite3_open_v2( + _ filename: UnsafePointer?, + _ ppDb: UnsafeMutablePointer?, + _ flags: Int32, + _ zVfs: UnsafePointer? +) -> Int32 { return 0 } + +var sqlite3_temp_directory: UnsafeMutablePointer? + +// SQLite.swift + +enum URIQueryParameter { +} + +class Connection { + enum Location { + case inMemory + case uri(String, parameters: [URIQueryParameter] = []) + } + + init(_ location: Location = .inMemory, readonly: Bool = false) throws {} + convenience init(_ filename: String, readonly: Bool = false) throws { try self.init() } +} + +// --- tests --- + +func test(buffer1: UnsafeMutablePointer, buffer2: UnsafeMutablePointer) { + let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ MISSING: Source + let remoteUrl = URL(string: remoteString)! + let remoteNsUrl = NSURL(string: remoteString)! + let safeUrl = URL(string: "")! + let safeNsUrl = NSURL(string: "")! + + Data("").write(to: remoteUrl, options: []) // $ MISSING: Alert + + let nsData = NSData() + let _ = nsData.write(to: remoteUrl, atomically: false) // $ MISSING: Alert + nsData.write(to: remoteUrl, options: []) // $ MISSING: Alert + let _ = nsData.write(toFile: remoteString, atomically: false) // $ MISSING: Alert + nsData.write(toFile: remoteString, options: []) // $ MISSING: Alert + + let fm = FileManager() + let _ = fm.contentsOfDirectory(at: remoteUrl, includingPropertiesForKeys: [], options: []) // $ MISSING: Alert + let _ = fm.contentsOfDirectory(atPath: remoteString) // $ MISSING: Alert + let _ = fm.enumerator( + at: remoteUrl, includingPropertiesForKeys: [], options: [], errorHandler: nil) // $ MISSING: Alert + let _ = fm.enumerator(atPath: remoteString) // $ MISSING: Alert + let _ = fm.subpathsOfDirectory(atPath: remoteString) // $ MISSING: Alert + let _ = fm.subpaths(atPath: remoteString) // $ MISSING: Alert + fm.createDirectory(at: remoteUrl, withIntermediateDirectories: false, attributes: [:]) // $ MISSING: Alert + let _ = fm.createDirectory(atPath: remoteString, attributes: [:]) // $ MISSING: Alert + let _ = fm.createFile(atPath: remoteString, contents: nil, attributes: [:]) // $ MISSING: Alert + fm.removeItem(at: remoteUrl) // $ MISSING: Alert + fm.removeItem(atPath: remoteString) // $ MISSING: Alert + fm.trashItem(at: remoteUrl, resultingItemURL: AutoreleasingUnsafeMutablePointer()) // $ MISSING: Alert + let _ = fm.replaceItemAt(remoteUrl, withItemAt: safeUrl, backupItemName: nil, options: []) // $ MISSING: Alert + let _ = fm.replaceItemAt(safeUrl, withItemAt: remoteUrl, backupItemName: nil, options: []) // $ MISSING: Alert + fm.replaceItem( + at: remoteUrl, withItemAt: safeUrl, backupItemName: nil, options: [], + resultingItemURL: AutoreleasingUnsafeMutablePointer()) // $ MISSING: Alert + fm.replaceItem( + at: safeUrl, withItemAt: remoteUrl, backupItemName: nil, options: [], + resultingItemURL: AutoreleasingUnsafeMutablePointer()) // $ MISSING: Alert + fm.copyItem(at: remoteUrl, to: safeUrl) // $ MISSING: Alert + fm.copyItem(at: safeUrl, to: remoteUrl) // $ MISSING: Alert + fm.copyItem(atPath: remoteString, toPath: "") // $ MISSING: Alert + fm.copyItem(atPath: "", toPath: remoteString) // $ MISSING: Alert + fm.moveItem(at: remoteUrl, to: safeUrl) // $ MISSING: Alert + fm.moveItem(at: safeUrl, to: remoteUrl) // $ MISSING: Alert + fm.moveItem(atPath: remoteString, toPath: "") // $ MISSING: Alert + fm.moveItem(atPath: "", toPath: remoteString) // $ MISSING: Alert + fm.createSymbolicLink(at: remoteUrl, withDestinationURL: safeUrl) // $ MISSING: Alert + fm.createSymbolicLink(at: safeUrl, withDestinationURL: remoteUrl) // $ MISSING: Alert + fm.createSymbolicLink(atPath: remoteString, withDestinationPath: "") // $ MISSING: Alert + fm.createSymbolicLink(atPath: "", withDestinationPath: remoteString) // $ MISSING: Alert + fm.linkItem(at: remoteUrl, to: safeUrl) // $ MISSING: Alert + fm.linkItem(at: safeUrl, to: remoteUrl) // $ MISSING: Alert + fm.linkItem(atPath: remoteString, toPath: "") // $ MISSING: Alert + fm.linkItem(atPath: "", toPath: remoteString) // $ MISSING: Alert + let _ = fm.destinationOfSymbolicLink(atPath: remoteString) // $ MISSING: Alert + let _ = fm.fileExists(atPath: remoteString) // $ MISSING: Alert + let _ = fm.fileExists( + atPath: remoteString, isDirectory: UnsafeMutablePointer.init(bitPattern: 0)) // $ MISSING: Alert + fm.setAttributes([:], ofItemAtPath: remoteString) // $ MISSING: Alert + let _ = fm.contents(atPath: remoteString) // $ MISSING: Alert + let _ = fm.contentsEqual(atPath: remoteString, andPath: "") // $ MISSING: Alert + let _ = fm.contentsEqual(atPath: "", andPath: remoteString) // $ MISSING: Alert + let _ = fm.changeCurrentDirectoryPath(remoteString) // $ MISSING: Alert + let _ = fm.unmountVolume(at: remoteUrl, options: [], completionHandler: { _ in }) // $ MISSING: Alert + // Deprecated methods + let _ = fm.changeFileAttributes([:], atPath: remoteString) // $ MISSING: Alert + let _ = fm.directoryContents(atPath: remoteString) // $ MISSING: Alert + let _ = fm.createDirectory(atPath: remoteString, attributes: [:]) // $ MISSING: Alert + let _ = fm.createSymbolicLink(atPath: remoteString, pathContent: "") // $ MISSING: Alert + let _ = fm.createSymbolicLink(atPath: "", pathContent: remoteString) // $ MISSING: Alert + let _ = fm.pathContentOfSymbolicLink(atPath: remoteString) // $ MISSING: Alert + let _ = fm.replaceItemAtURL( + originalItemURL: remoteNsUrl, withItemAtURL: safeNsUrl, backupItemName: nil, options: []) // $ MISSING: Alert + let _ = fm.replaceItemAtURL( + originalItemURL: safeNsUrl, withItemAtURL: remoteNsUrl, backupItemName: nil, options: []) // $ MISSING: Alert + + var encoding = String.Encoding.utf8 + let _ = try! String(contentsOfFile: remoteString) // $ MISSING: Alert + let _ = try! String(contentsOfFile: remoteString, encoding: String.Encoding.utf8) // $ MISSING: Alert + let _ = try! String(contentsOfFile: remoteString, usedEncoding: &encoding) // $ MISSING: Alert + + let _ = try! NSString(contentsOfFile: remoteString, encoding: 0) // $ MISSING: Alert + let _ = try! NSString(contentsOfFile: remoteString, usedEncoding: nil) // $ MISSING: Alert + NSString().write(to: remoteUrl, atomically: true, encoding: 0) // $ MISSING: Alert + NSString().write(toFile: remoteString, atomically: true, encoding: 0) // $ MISSING: Alert + + let _ = NSKeyedUnarchiver().unarchiveObject(withFile: remoteString) // $ MISSING: Alert + let _ = ArchiveByteStream.fileStream(fd: remoteString as! FileDescriptor, automaticClose: true) // $ MISSING: Alert + ArchiveByteStream.withFileStream(fd: remoteString as! FileDescriptor, automaticClose: true) { + _ in + } // $ MISSING: Alert + let _ = ArchiveByteStream.fileStream( + path: FilePath(stringLiteral: remoteString), mode: .readOnly, options: .append, + permissions: .ownerRead) // $ MISSING: Alert + ArchiveByteStream.withFileStream( + path: FilePath(stringLiteral: remoteString), mode: .readOnly, options: .append, + permissions: .ownerRead + ) { _ in } // $ MISSING: Alert + let _ = Bundle(url: remoteUrl) // $ MISSING: Alert + let _ = Bundle(path: remoteString) // $ MISSING: Alert + + // GRDB + + let _ = Database(path: remoteString, description: "", configuration: Configuration()) // $ MISSING: Alert + let _ = Database(path: "", description: "", configuration: Configuration()) // Safe + let _ = DatabasePool(path: remoteString, configuration: Configuration()) // $ MISSING: Alert + let _ = DatabasePool(path: "", configuration: Configuration()) // Safe + let _ = DatabaseQueue(path: remoteString, configuration: Configuration()) // $ MISSING: Alert + let _ = DatabaseQueue(path: "", configuration: Configuration()) // Safe + let _ = DatabaseSnapshotPool(path: remoteString, configuration: Configuration()) // $ MISSING: Alert + let _ = DatabaseSnapshotPool(path: "", configuration: Configuration()) // Safe + let _ = SerializedDatabase(path: remoteString, defaultLabel: "") // $ MISSING: Alert + let _ = SerializedDatabase(path: "", defaultLabel: "") // Safe + let _ = SerializedDatabase(path: remoteString, defaultLabel: "", purpose: nil) // $ MISSING: Alert + let _ = SerializedDatabase(path: "", defaultLabel: "", purpose: nil) // Safe + let _ = SerializedDatabase(path: remoteString, configuration: Configuration(), defaultLabel: "") // $ MISSING: Alert + let _ = SerializedDatabase(path: "", configuration: Configuration(), defaultLabel: "") // Safe + let _ = SerializedDatabase( + path: remoteString, configuration: Configuration(), defaultLabel: "", purpose: nil) // $ MISSING: Alert + let _ = SerializedDatabase( + path: "", configuration: Configuration(), defaultLabel: "", purpose: nil) // Safe + + // Realm + + _ = Realm.Configuration(fileURL: safeUrl) // GOOD + _ = Realm.Configuration(fileURL: remoteUrl) // $ MISSING: Alert + _ = Realm.Configuration(seedFilePath: safeUrl) // GOOD + _ = Realm.Configuration(seedFilePath: remoteUrl) // $ MISSING: Alert + + var config = Realm.Configuration() // GOOD + config.fileURL = safeUrl // GOOD + config.fileURL = remoteUrl // $ MISSING: Alert + config.seedFilePath = safeUrl // GOOD + config.seedFilePath = remoteUrl // $ MISSING: Alert + + // sqlite3 + + var db: OpaquePointer? + let localData = Data(0) + let remoteData = Data(contentsOf: URL(string: "http://example.com/")!, options: []) // $ MISSING: Source + localData.copyBytes(to: buffer1, count: localData.count) + remoteData.copyBytes(to: buffer2, count: remoteData.count) + + _ = sqlite3_open("myFile.sqlite3", &db) // GOOD + _ = sqlite3_open(remoteString, &db) // $ MISSING: Alert + _ = sqlite3_open16(buffer1, &db) // GOOD + _ = sqlite3_open16(buffer2, &db) // $ MISSING: Alert + _ = sqlite3_open_v2("myFile.sqlite3", &db, 0, nil) // GOOD + _ = sqlite3_open_v2(remoteString, &db, 0, nil) // $ MISSING: Alert + + sqlite3_temp_directory = UnsafeMutablePointer( + mutating: NSString(string: "myFile.sqlite3").utf8String) // GOOD + sqlite3_temp_directory = UnsafeMutablePointer( + mutating: NSString(string: remoteString).utf8String) // $ MISSING: Alert // Originally missing + + // SQLite.swift + + try! _ = Connection() + try! _ = Connection(Connection.Location.uri("myFile.sqlite3")) // GOOD + try! _ = Connection(Connection.Location.uri(remoteString)) // $ MISSING: Alert + try! _ = Connection("myFile.sqlite3") // GOOD + try! _ = Connection(remoteString) // $ MISSING: Alert +} + +func testBarriers() { + let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ MISSING: Source + + let fm = FileManager() + + let filePath = FilePath(stringLiteral: remoteString) + if filePath.lexicallyNormalized().starts(with: "/safe") { + let _ = fm.contents(atPath: remoteString) // Safe + } + let _ = fm.contents(atPath: remoteString) // $ MISSING: Alert +} + +func testPathInjection2( + s1: UnsafeMutablePointer, s2: UnsafeMutablePointer, + s3: UnsafeMutablePointer, fm: FileManager +) throws { + let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ MISSING: Source + + var u1 = URL(filePath: "") + _ = NSData(contentsOf: u1) + _ = NSData(contentsOf: u1.appendingPathComponent("")) + _ = NSData(contentsOf: u1.appendingPathComponent(remoteString)) // $ MISSING: Alert + _ = NSData(contentsOf: u1.appendingPathComponent(remoteString).appendingPathComponent("")) // $ MISSING: Alert + u1.appendPathComponent(remoteString) + _ = NSData(contentsOf: u1) // $ MISSING: Alert + + let u2 = URL(filePath: remoteString) // $ MISSING: Alert + _ = NSData(contentsOf: u2) // $ MISSING: Alert + + let u3 = NSURL(string: "")! + Data("").write(to: u3.filePathURL!, options: []) + Data("").write(to: u3.appendingPathComponent("")!, options: []) + Data("").write(to: u3.appendingPathComponent(remoteString)!, options: []) // $ MISSING: Alert + + let u4 = NSURL(string: remoteString)! + Data("").write(to: u4.filePathURL!, options: []) // $ MISSING: Alert + Data("").write(to: u4.appendingPathComponent("")!, options: []) // $ MISSING: Alert + + _ = NSData(contentsOfFile: remoteString)! // $ MISSING: Alert + _ = NSData(contentsOfMappedFile: remoteString)! // $ MISSING: Alert + _ = NSData.dataWithContentsOfMappedFile(remoteString)! // $ MISSING: Alert + + _ = NSData().write(toFile: s1.pointee, atomically: true) + s1.pointee = remoteString + _ = NSData().write(toFile: s1.pointee, atomically: true) // $ MISSING: Alert + _ = NSData().write(toFile: s1[0], atomically: true) // $ MISSING: Alert // Originally missing + + _ = "".completePath(into: s2, caseSensitive: false, matchesInto: nil, filterTypes: nil) + _ = NSData().write(toFile: s2.pointee, atomically: true) + _ = NSData().write(toFile: s2[0], atomically: true) + + _ = remoteString.completePath( + into: s3, caseSensitive: false, matchesInto: nil, filterTypes: nil) + _ = NSData().write(toFile: s3.pointee, atomically: true) // $ MISSING: Alert // Originally missing + _ = NSData().write(toFile: s3[0], atomically: true) // $ MISSING: Alert + + _ = fm.fileAttributes(atPath: remoteString, traverseLink: true) // $ MISSING: Alert + _ = try fm.attributesOfItem(atPath: remoteString) // $ MISSING: Alert +} + +// --- + +func myOpenFile1(atPath path: String) {} +func myOpenFile2(_ filePath: String) {} +func myFindFiles(ofType type: Int, inDirectory dir: String) {} + +class MyClass { + init(contentsOfFile: String) {} + func doSomething(keyPath: String) {} + func write(toFile: String) {} +} + +class MyFile { + init(path: String) {} +} + +func testPathInjectionHeuristics() { + let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ MISSING: Source + + myOpenFile1(atPath: remoteString) // $ MISSING: Alert + myOpenFile2(remoteString) // $ MISSING: Alert + myFindFiles(ofType: 0, inDirectory: remoteString) // $ MISSING: Alert + + let mc = MyClass(contentsOfFile: remoteString) // $ MISSING: Alert + mc.doSomething(keyPath: remoteString) // good - not a path + mc.write(toFile: remoteString) // $ MISSING: Alert + + let mf1 = MyFile(path: "") + let mf2 = MyFile(path: remoteString) // $ MISSING: Alert // Originally missing + + _ = NSSortDescriptor(key: remoteString, ascending: true) // good - not a path + _ = NSSortDescriptor(keyPath: remoteString as! KeyPath, ascending: true) // good - not a path +} From da3c888955561d7ec7fb1a864eb3aa80aac0c2f5 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 24 Sep 2026 13:26:16 +0200 Subject: [PATCH 2/7] unified: Use MaD sources and sinks --- .../queries/security/CWE-022/PathInjection.ql | 4 +- .../PathInjection/PathInjectionTest.expected | 281 ++++++++++++++++++ .../PathInjection/testPathInjection.swift | 158 +++++----- 3 files changed, 362 insertions(+), 81 deletions(-) diff --git a/unified/ql/src/queries/security/CWE-022/PathInjection.ql b/unified/ql/src/queries/security/CWE-022/PathInjection.ql index 5796e8e0b2c7..8d74fd49a3d4 100644 --- a/unified/ql/src/queries/security/CWE-022/PathInjection.ql +++ b/unified/ql/src/queries/security/CWE-022/PathInjection.ql @@ -17,9 +17,9 @@ import unified module PathInjectionConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node node) { none() } + predicate isSource(DataFlow::Node node) { Models::isSource(node, _) } - predicate isSink(DataFlow::Node node) { none() } + predicate isSink(DataFlow::Node node) { Models::isSink(node, "path-injection") } predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { none() } diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected index 58f42bec0c84..38ed905df873 100644 --- a/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected @@ -1,4 +1,285 @@ #select +| testPathInjection.swift:351:34:351:45 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:351:34:351:45 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:352:26:352:37 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:352:26:352:37 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:356:44:356:55 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:356:44:356:55 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:359:35:359:46 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:359:35:359:46 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:360:44:360:55 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:360:44:360:55 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:361:33:361:44 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:361:33:361:44 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:362:28:362:36 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:362:28:362:36 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:363:40:363:51 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:363:40:363:51 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:364:35:364:46 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:364:35:364:46 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:365:23:365:31 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:365:23:365:31 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:366:27:366:38 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:366:27:366:38 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:367:22:367:30 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:367:22:367:30 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:368:30:368:38 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:368:30:368:38 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:369:30:369:36 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:369:30:369:36 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:369:51:369:59 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:369:51:369:59 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:371:13:371:21 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:371:13:371:21 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:371:36:371:42 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:371:36:371:42 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:374:13:374:19 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:374:13:374:19 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:374:34:374:42 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:374:34:374:42 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:376:21:376:29 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:376:21:376:29 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:376:36:376:42 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:376:36:376:42 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:377:21:377:27 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:377:21:377:27 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:377:34:377:42 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:377:34:377:42 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:378:25:378:36 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:378:25:378:36 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:379:37:379:48 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:379:37:379:48 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:380:21:380:29 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:380:21:380:29 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:380:36:380:42 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:380:36:380:42 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:381:21:381:27 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:381:21:381:27 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:381:34:381:42 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:381:34:381:42 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:382:25:382:36 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:382:25:382:36 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:383:37:383:48 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:383:37:383:48 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:384:31:384:39 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:384:31:384:39 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:384:62:384:68 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:384:62:384:68 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:385:31:385:37 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:385:31:385:37 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:385:60:385:68 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:385:60:385:68 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:386:35:386:46 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:386:35:386:46 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:387:60:387:71 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:387:60:387:71 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:388:21:388:29 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:388:21:388:29 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:388:36:388:42 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:388:36:388:42 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:389:21:389:27 | safeUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:389:21:389:27 | safeUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:389:34:389:42 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:389:34:389:42 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:390:25:390:36 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:390:25:390:36 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:391:37:391:48 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:391:37:391:48 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:392:50:392:61 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:392:50:392:61 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:393:35:393:46 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:393:35:393:46 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:395:17:395:28 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:395:17:395:28 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:396:41:396:52 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:396:41:396:52 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:397:33:397:44 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:397:33:397:44 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:398:38:398:49 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:398:38:398:49 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:399:51:399:62 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:399:51:399:62 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:400:43:400:54 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:400:43:400:54 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:401:34:401:42 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:401:34:401:42 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:403:50:403:61 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:403:50:403:61 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:404:42:404:53 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:404:42:404:53 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:405:40:405:51 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:405:40:405:51 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:406:43:406:54 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:406:43:406:54 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:407:60:407:71 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:407:60:407:71 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:408:50:408:61 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:408:50:408:61 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:412:26:412:34 | safeNsUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:412:26:412:34 | safeNsUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:412:52:412:62 | remoteNsUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:412:52:412:62 | remoteNsUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:415:41:415:52 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:415:41:415:52 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:416:41:416:52 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:416:41:416:52 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:417:41:417:52 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:417:41:417:52 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:419:43:419:54 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:419:43:419:54 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:420:43:420:54 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:420:43:420:54 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:421:26:421:34 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:421:26:421:34 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:422:30:422:41 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:422:30:422:41 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:424:59:424:70 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:424:59:424:70 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:436:25:436:33 | remoteUrl | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:436:25:436:33 | remoteUrl | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:437:26:437:37 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:437:26:437:37 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:441:28:441:39 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:441:28:441:39 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:443:32:443:43 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:443:32:443:43 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:445:33:445:44 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:445:33:445:44 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:447:40:447:51 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:447:40:447:51 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:456:15:456:26 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:456:15:456:26 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:482:22:482:33 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:482:22:482:33 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:484:24:484:30 | buffer2 | testPathInjection.swift:477:22:477:87 | Data(...) | testPathInjection.swift:484:24:484:30 | buffer2 | This path depends on a $@. | testPathInjection.swift:477:22:477:87 | Data(...) | user-provided value | +| testPathInjection.swift:486:25:486:36 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:486:25:486:36 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:509:37:509:48 | remoteString | testPathInjection.swift:503:24:503:78 | String(...) | testPathInjection.swift:509:37:509:48 | remoteString | This path depends on a $@. | testPathInjection.swift:503:24:503:78 | String(...) | user-provided value | +| testPathInjection.swift:511:33:511:44 | remoteString | testPathInjection.swift:503:24:503:78 | String(...) | testPathInjection.swift:511:33:511:44 | remoteString | This path depends on a $@. | testPathInjection.swift:503:24:503:78 | String(...) | user-provided value | +| testPathInjection.swift:526:28:526:29 | u1 | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:526:28:526:29 | u1 | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | +| testPathInjection.swift:540:32:540:43 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:540:32:540:43 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | +| testPathInjection.swift:541:38:541:49 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:541:38:541:49 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | +| testPathInjection.swift:542:45:542:56 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:542:45:542:56 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | +| testPathInjection.swift:546:32:546:41 | ... .pointee | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:546:32:546:41 | ... .pointee | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | +| testPathInjection.swift:558:35:558:46 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:558:35:558:46 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | +| testPathInjection.swift:559:41:559:52 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:559:41:559:52 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | edges +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:351:34:351:45 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:352:26:352:37 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:356:44:356:55 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:359:35:359:46 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:360:44:360:55 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:361:33:361:44 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:362:28:362:36 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:363:40:363:51 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:364:35:364:46 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:365:23:365:31 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:366:27:366:38 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:367:22:367:30 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:368:30:368:38 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:369:30:369:36 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:369:51:369:59 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:371:13:371:21 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:371:36:371:42 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:374:13:374:19 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:374:34:374:42 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:376:21:376:29 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:376:36:376:42 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:377:21:377:27 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:377:34:377:42 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:378:25:378:36 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:379:37:379:48 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:380:21:380:29 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:380:36:380:42 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:381:21:381:27 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:381:34:381:42 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:382:25:382:36 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:383:37:383:48 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:384:31:384:39 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:384:62:384:68 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:385:31:385:37 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:385:60:385:68 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:386:35:386:46 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:387:60:387:71 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:388:21:388:29 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:388:36:388:42 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:389:21:389:27 | safeUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:389:34:389:42 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:390:25:390:36 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:391:37:391:48 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:392:50:392:61 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:393:35:393:46 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:395:17:395:28 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:396:41:396:52 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:397:33:397:44 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:398:38:398:49 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:399:51:399:62 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:400:43:400:54 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:401:34:401:42 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:403:50:403:61 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:404:42:404:53 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:405:40:405:51 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:406:43:406:54 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:407:60:407:71 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:408:50:408:61 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:412:26:412:34 | safeNsUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:412:52:412:62 | remoteNsUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:415:41:415:52 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:416:41:416:52 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:417:41:417:52 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:419:43:419:54 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:420:43:420:54 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:421:26:421:34 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:422:30:422:41 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:424:59:424:70 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:436:25:436:33 | remoteUrl | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:437:26:437:37 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:441:28:441:39 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:443:32:443:43 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:445:33:445:44 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:447:40:447:51 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:456:15:456:26 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:482:22:482:33 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:486:25:486:36 | remoteString | provenance | | +| testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:340:9:340:20 | remoteString | provenance | | +| testPathInjection.swift:477:9:477:18 | remoteData | testPathInjection.swift:484:24:484:30 | buffer2 | provenance | | +| testPathInjection.swift:477:22:477:87 | Data(...) | testPathInjection.swift:477:9:477:18 | remoteData | provenance | | +| testPathInjection.swift:503:9:503:20 | remoteString | testPathInjection.swift:509:37:509:48 | remoteString | provenance | | +| testPathInjection.swift:503:9:503:20 | remoteString | testPathInjection.swift:511:33:511:44 | remoteString | provenance | | +| testPathInjection.swift:503:24:503:78 | String(...) | testPathInjection.swift:503:9:503:20 | remoteString | provenance | | +| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:526:28:526:29 | u1 | provenance | | +| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:540:32:540:43 | remoteString | provenance | | +| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:541:38:541:49 | remoteString | provenance | | +| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:542:45:542:56 | remoteString | provenance | | +| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:545:18:545:29 | remoteString | provenance | | +| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:558:35:558:46 | remoteString | provenance | | +| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:559:41:559:52 | remoteString | provenance | | +| testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:518:9:518:20 | remoteString | provenance | | +| testPathInjection.swift:545:5:545:6 | [post] s1 [pointee] | testPathInjection.swift:546:32:546:33 | s1 [pointee] | provenance | | +| testPathInjection.swift:545:5:545:14 | ... .pointee | testPathInjection.swift:545:5:545:6 | [post] s1 [pointee] | provenance | | +| testPathInjection.swift:545:18:545:29 | remoteString | testPathInjection.swift:545:5:545:14 | ... .pointee | provenance | | +| testPathInjection.swift:546:32:546:33 | s1 [pointee] | testPathInjection.swift:546:32:546:41 | ... .pointee | provenance | | nodes +| testPathInjection.swift:340:9:340:20 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:340:24:340:78 | String(...) | semmle.label | String(...) | +| testPathInjection.swift:351:34:351:45 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:352:26:352:37 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:356:44:356:55 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:359:35:359:46 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:360:44:360:55 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:361:33:361:44 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:362:28:362:36 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:363:40:363:51 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:364:35:364:46 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:365:23:365:31 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:366:27:366:38 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:367:22:367:30 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:368:30:368:38 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:369:30:369:36 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:369:51:369:59 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:371:13:371:21 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:371:36:371:42 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:374:13:374:19 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:374:34:374:42 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:376:21:376:29 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:376:36:376:42 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:377:21:377:27 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:377:34:377:42 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:378:25:378:36 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:379:37:379:48 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:380:21:380:29 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:380:36:380:42 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:381:21:381:27 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:381:34:381:42 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:382:25:382:36 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:383:37:383:48 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:384:31:384:39 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:384:62:384:68 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:385:31:385:37 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:385:60:385:68 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:386:35:386:46 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:387:60:387:71 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:388:21:388:29 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:388:36:388:42 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:389:21:389:27 | safeUrl | semmle.label | safeUrl | +| testPathInjection.swift:389:34:389:42 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:390:25:390:36 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:391:37:391:48 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:392:50:392:61 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:393:35:393:46 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:395:17:395:28 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:396:41:396:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:397:33:397:44 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:398:38:398:49 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:399:51:399:62 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:400:43:400:54 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:401:34:401:42 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:403:50:403:61 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:404:42:404:53 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:405:40:405:51 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:406:43:406:54 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:407:60:407:71 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:408:50:408:61 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:412:26:412:34 | safeNsUrl | semmle.label | safeNsUrl | +| testPathInjection.swift:412:52:412:62 | remoteNsUrl | semmle.label | remoteNsUrl | +| testPathInjection.swift:415:41:415:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:416:41:416:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:417:41:417:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:419:43:419:54 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:420:43:420:54 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:421:26:421:34 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:422:30:422:41 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:424:59:424:70 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:436:25:436:33 | remoteUrl | semmle.label | remoteUrl | +| testPathInjection.swift:437:26:437:37 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:441:28:441:39 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:443:32:443:43 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:445:33:445:44 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:447:40:447:51 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:456:15:456:26 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:477:9:477:18 | remoteData | semmle.label | remoteData | +| testPathInjection.swift:477:22:477:87 | Data(...) | semmle.label | Data(...) | +| testPathInjection.swift:482:22:482:33 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:484:24:484:30 | buffer2 | semmle.label | buffer2 | +| testPathInjection.swift:486:25:486:36 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:503:9:503:20 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:503:24:503:78 | String(...) | semmle.label | String(...) | +| testPathInjection.swift:509:37:509:48 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:511:33:511:44 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:518:9:518:20 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:518:24:518:78 | String(...) | semmle.label | String(...) | +| testPathInjection.swift:526:28:526:29 | u1 | semmle.label | u1 | +| testPathInjection.swift:540:32:540:43 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:541:38:541:49 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:542:45:542:56 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:545:5:545:6 | [post] s1 [pointee] | semmle.label | [post] s1 [pointee] | +| testPathInjection.swift:545:5:545:14 | ... .pointee | semmle.label | ... .pointee | +| testPathInjection.swift:545:18:545:29 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:546:32:546:33 | s1 [pointee] | semmle.label | s1 [pointee] | +| testPathInjection.swift:546:32:546:41 | ... .pointee | semmle.label | ... .pointee | +| testPathInjection.swift:558:35:558:46 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:559:41:559:52 | remoteString | semmle.label | remoteString | subpaths diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift b/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift index d1076f47853d..c8ab468892d9 100644 --- a/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift @@ -337,7 +337,7 @@ class Connection { // --- tests --- func test(buffer1: UnsafeMutablePointer, buffer2: UnsafeMutablePointer) { - let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ MISSING: Source + let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ Source let remoteUrl = URL(string: remoteString)! let remoteNsUrl = NSURL(string: remoteString)! let safeUrl = URL(string: "")! @@ -348,80 +348,80 @@ func test(buffer1: UnsafeMutablePointer, buffer2: UnsafeMutablePointer()) // $ MISSING: Alert - let _ = fm.replaceItemAt(remoteUrl, withItemAt: safeUrl, backupItemName: nil, options: []) // $ MISSING: Alert - let _ = fm.replaceItemAt(safeUrl, withItemAt: remoteUrl, backupItemName: nil, options: []) // $ MISSING: Alert + let _ = fm.enumerator(atPath: remoteString) // $ Alert + let _ = fm.subpathsOfDirectory(atPath: remoteString) // $ Alert + let _ = fm.subpaths(atPath: remoteString) // $ Alert + fm.createDirectory(at: remoteUrl, withIntermediateDirectories: false, attributes: [:]) // $ Alert + let _ = fm.createDirectory(atPath: remoteString, attributes: [:]) // $ Alert + let _ = fm.createFile(atPath: remoteString, contents: nil, attributes: [:]) // $ Alert + fm.removeItem(at: remoteUrl) // $ Alert + fm.removeItem(atPath: remoteString) // $ Alert + fm.trashItem(at: remoteUrl, resultingItemURL: AutoreleasingUnsafeMutablePointer()) // $ Alert + let _ = fm.replaceItemAt(remoteUrl, withItemAt: safeUrl, backupItemName: nil, options: []) // $ Alert + let _ = fm.replaceItemAt(safeUrl, withItemAt: remoteUrl, backupItemName: nil, options: []) // $ Alert fm.replaceItem( - at: remoteUrl, withItemAt: safeUrl, backupItemName: nil, options: [], + at: remoteUrl, withItemAt: safeUrl, backupItemName: nil, options: [], // $ SPURIOUS: Alert resultingItemURL: AutoreleasingUnsafeMutablePointer()) // $ MISSING: Alert fm.replaceItem( - at: safeUrl, withItemAt: remoteUrl, backupItemName: nil, options: [], + at: safeUrl, withItemAt: remoteUrl, backupItemName: nil, options: [], // $ SPURIOUS: Alert resultingItemURL: AutoreleasingUnsafeMutablePointer()) // $ MISSING: Alert - fm.copyItem(at: remoteUrl, to: safeUrl) // $ MISSING: Alert - fm.copyItem(at: safeUrl, to: remoteUrl) // $ MISSING: Alert - fm.copyItem(atPath: remoteString, toPath: "") // $ MISSING: Alert - fm.copyItem(atPath: "", toPath: remoteString) // $ MISSING: Alert - fm.moveItem(at: remoteUrl, to: safeUrl) // $ MISSING: Alert - fm.moveItem(at: safeUrl, to: remoteUrl) // $ MISSING: Alert - fm.moveItem(atPath: remoteString, toPath: "") // $ MISSING: Alert - fm.moveItem(atPath: "", toPath: remoteString) // $ MISSING: Alert - fm.createSymbolicLink(at: remoteUrl, withDestinationURL: safeUrl) // $ MISSING: Alert - fm.createSymbolicLink(at: safeUrl, withDestinationURL: remoteUrl) // $ MISSING: Alert - fm.createSymbolicLink(atPath: remoteString, withDestinationPath: "") // $ MISSING: Alert - fm.createSymbolicLink(atPath: "", withDestinationPath: remoteString) // $ MISSING: Alert - fm.linkItem(at: remoteUrl, to: safeUrl) // $ MISSING: Alert - fm.linkItem(at: safeUrl, to: remoteUrl) // $ MISSING: Alert - fm.linkItem(atPath: remoteString, toPath: "") // $ MISSING: Alert - fm.linkItem(atPath: "", toPath: remoteString) // $ MISSING: Alert - let _ = fm.destinationOfSymbolicLink(atPath: remoteString) // $ MISSING: Alert - let _ = fm.fileExists(atPath: remoteString) // $ MISSING: Alert + fm.copyItem(at: remoteUrl, to: safeUrl) // $ Alert + fm.copyItem(at: safeUrl, to: remoteUrl) // $ Alert + fm.copyItem(atPath: remoteString, toPath: "") // $ Alert + fm.copyItem(atPath: "", toPath: remoteString) // $ Alert + fm.moveItem(at: remoteUrl, to: safeUrl) // $ Alert + fm.moveItem(at: safeUrl, to: remoteUrl) // $ Alert + fm.moveItem(atPath: remoteString, toPath: "") // $ Alert + fm.moveItem(atPath: "", toPath: remoteString) // $ Alert + fm.createSymbolicLink(at: remoteUrl, withDestinationURL: safeUrl) // $ Alert + fm.createSymbolicLink(at: safeUrl, withDestinationURL: remoteUrl) // $ Alert + fm.createSymbolicLink(atPath: remoteString, withDestinationPath: "") // $ Alert + fm.createSymbolicLink(atPath: "", withDestinationPath: remoteString) // $ Alert + fm.linkItem(at: remoteUrl, to: safeUrl) // $ Alert + fm.linkItem(at: safeUrl, to: remoteUrl) // $ Alert + fm.linkItem(atPath: remoteString, toPath: "") // $ Alert + fm.linkItem(atPath: "", toPath: remoteString) // $ Alert + let _ = fm.destinationOfSymbolicLink(atPath: remoteString) // $ Alert + let _ = fm.fileExists(atPath: remoteString) // $ Alert let _ = fm.fileExists( - atPath: remoteString, isDirectory: UnsafeMutablePointer.init(bitPattern: 0)) // $ MISSING: Alert - fm.setAttributes([:], ofItemAtPath: remoteString) // $ MISSING: Alert - let _ = fm.contents(atPath: remoteString) // $ MISSING: Alert - let _ = fm.contentsEqual(atPath: remoteString, andPath: "") // $ MISSING: Alert - let _ = fm.contentsEqual(atPath: "", andPath: remoteString) // $ MISSING: Alert - let _ = fm.changeCurrentDirectoryPath(remoteString) // $ MISSING: Alert - let _ = fm.unmountVolume(at: remoteUrl, options: [], completionHandler: { _ in }) // $ MISSING: Alert + atPath: remoteString, isDirectory: UnsafeMutablePointer.init(bitPattern: 0)) // $ Alert + fm.setAttributes([:], ofItemAtPath: remoteString) // $ Alert + let _ = fm.contents(atPath: remoteString) // $ Alert + let _ = fm.contentsEqual(atPath: remoteString, andPath: "") // $ Alert + let _ = fm.contentsEqual(atPath: "", andPath: remoteString) // $ Alert + let _ = fm.changeCurrentDirectoryPath(remoteString) // $ Alert + let _ = fm.unmountVolume(at: remoteUrl, options: [], completionHandler: { _ in }) // $ Alert // Deprecated methods - let _ = fm.changeFileAttributes([:], atPath: remoteString) // $ MISSING: Alert - let _ = fm.directoryContents(atPath: remoteString) // $ MISSING: Alert - let _ = fm.createDirectory(atPath: remoteString, attributes: [:]) // $ MISSING: Alert - let _ = fm.createSymbolicLink(atPath: remoteString, pathContent: "") // $ MISSING: Alert - let _ = fm.createSymbolicLink(atPath: "", pathContent: remoteString) // $ MISSING: Alert - let _ = fm.pathContentOfSymbolicLink(atPath: remoteString) // $ MISSING: Alert + let _ = fm.changeFileAttributes([:], atPath: remoteString) // $ Alert + let _ = fm.directoryContents(atPath: remoteString) // $ Alert + let _ = fm.createDirectory(atPath: remoteString, attributes: [:]) // $ Alert + let _ = fm.createSymbolicLink(atPath: remoteString, pathContent: "") // $ Alert + let _ = fm.createSymbolicLink(atPath: "", pathContent: remoteString) // $ Alert + let _ = fm.pathContentOfSymbolicLink(atPath: remoteString) // $ Alert let _ = fm.replaceItemAtURL( originalItemURL: remoteNsUrl, withItemAtURL: safeNsUrl, backupItemName: nil, options: []) // $ MISSING: Alert let _ = fm.replaceItemAtURL( - originalItemURL: safeNsUrl, withItemAtURL: remoteNsUrl, backupItemName: nil, options: []) // $ MISSING: Alert + originalItemURL: safeNsUrl, withItemAtURL: remoteNsUrl, backupItemName: nil, options: []) // $ Alert var encoding = String.Encoding.utf8 - let _ = try! String(contentsOfFile: remoteString) // $ MISSING: Alert - let _ = try! String(contentsOfFile: remoteString, encoding: String.Encoding.utf8) // $ MISSING: Alert - let _ = try! String(contentsOfFile: remoteString, usedEncoding: &encoding) // $ MISSING: Alert + let _ = try! String(contentsOfFile: remoteString) // $ Alert + let _ = try! String(contentsOfFile: remoteString, encoding: String.Encoding.utf8) // $ Alert + let _ = try! String(contentsOfFile: remoteString, usedEncoding: &encoding) // $ Alert - let _ = try! NSString(contentsOfFile: remoteString, encoding: 0) // $ MISSING: Alert - let _ = try! NSString(contentsOfFile: remoteString, usedEncoding: nil) // $ MISSING: Alert - NSString().write(to: remoteUrl, atomically: true, encoding: 0) // $ MISSING: Alert - NSString().write(toFile: remoteString, atomically: true, encoding: 0) // $ MISSING: Alert + let _ = try! NSString(contentsOfFile: remoteString, encoding: 0) // $ Alert + let _ = try! NSString(contentsOfFile: remoteString, usedEncoding: nil) // $ Alert + NSString().write(to: remoteUrl, atomically: true, encoding: 0) // $ Alert + NSString().write(toFile: remoteString, atomically: true, encoding: 0) // $ Alert - let _ = NSKeyedUnarchiver().unarchiveObject(withFile: remoteString) // $ MISSING: Alert + let _ = NSKeyedUnarchiver().unarchiveObject(withFile: remoteString) // $ Alert let _ = ArchiveByteStream.fileStream(fd: remoteString as! FileDescriptor, automaticClose: true) // $ MISSING: Alert ArchiveByteStream.withFileStream(fd: remoteString as! FileDescriptor, automaticClose: true) { _ in @@ -433,18 +433,18 @@ func test(buffer1: UnsafeMutablePointer, buffer2: UnsafeMutablePointer, buffer2: UnsafeMutablePointer, buffer2: UnsafeMutablePointer( mutating: NSString(string: "myFile.sqlite3").utf8String) // GOOD @@ -500,22 +500,22 @@ func test(buffer1: UnsafeMutablePointer, buffer2: UnsafeMutablePointer, s2: UnsafeMutablePointer, s3: UnsafeMutablePointer, fm: FileManager ) throws { - let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ MISSING: Source + let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ Source var u1 = URL(filePath: "") _ = NSData(contentsOf: u1) @@ -523,7 +523,7 @@ func testPathInjection2( _ = NSData(contentsOf: u1.appendingPathComponent(remoteString)) // $ MISSING: Alert _ = NSData(contentsOf: u1.appendingPathComponent(remoteString).appendingPathComponent("")) // $ MISSING: Alert u1.appendPathComponent(remoteString) - _ = NSData(contentsOf: u1) // $ MISSING: Alert + _ = NSData(contentsOf: u1) // $ Alert let u2 = URL(filePath: remoteString) // $ MISSING: Alert _ = NSData(contentsOf: u2) // $ MISSING: Alert @@ -537,13 +537,13 @@ func testPathInjection2( Data("").write(to: u4.filePathURL!, options: []) // $ MISSING: Alert Data("").write(to: u4.appendingPathComponent("")!, options: []) // $ MISSING: Alert - _ = NSData(contentsOfFile: remoteString)! // $ MISSING: Alert - _ = NSData(contentsOfMappedFile: remoteString)! // $ MISSING: Alert - _ = NSData.dataWithContentsOfMappedFile(remoteString)! // $ MISSING: Alert + _ = NSData(contentsOfFile: remoteString)! // $ Alert + _ = NSData(contentsOfMappedFile: remoteString)! // $ Alert + _ = NSData.dataWithContentsOfMappedFile(remoteString)! // $ Alert _ = NSData().write(toFile: s1.pointee, atomically: true) s1.pointee = remoteString - _ = NSData().write(toFile: s1.pointee, atomically: true) // $ MISSING: Alert + _ = NSData().write(toFile: s1.pointee, atomically: true) // $ Alert _ = NSData().write(toFile: s1[0], atomically: true) // $ MISSING: Alert // Originally missing _ = "".completePath(into: s2, caseSensitive: false, matchesInto: nil, filterTypes: nil) @@ -555,8 +555,8 @@ func testPathInjection2( _ = NSData().write(toFile: s3.pointee, atomically: true) // $ MISSING: Alert // Originally missing _ = NSData().write(toFile: s3[0], atomically: true) // $ MISSING: Alert - _ = fm.fileAttributes(atPath: remoteString, traverseLink: true) // $ MISSING: Alert - _ = try fm.attributesOfItem(atPath: remoteString) // $ MISSING: Alert + _ = fm.fileAttributes(atPath: remoteString, traverseLink: true) // $ Alert + _ = try fm.attributesOfItem(atPath: remoteString) // $ Alert } // --- From 832173a554106cd721af2f34ffada83bc25f8040 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 24 Sep 2026 13:39:52 +0200 Subject: [PATCH 3/7] unified: First round of heuristics --- .../queries/security/CWE-022/PathInjection.ql | 27 ++++++++++++++++++- .../PathInjection/PathInjectionTest.expected | 18 +++++++++++++ .../PathInjection/testPathInjection.swift | 12 ++++----- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/unified/ql/src/queries/security/CWE-022/PathInjection.ql b/unified/ql/src/queries/security/CWE-022/PathInjection.ql index 8d74fd49a3d4..d841bb6404c2 100644 --- a/unified/ql/src/queries/security/CWE-022/PathInjection.ql +++ b/unified/ql/src/queries/security/CWE-022/PathInjection.ql @@ -16,10 +16,35 @@ import unified +/** + * A string that might be a label for a path argument. + */ +pragma[inline] +private predicate pathLikeHeuristic(string label) { + label = + [ + "atFile", "atPath", "atDirectory", "toFile", "toPath", "toDirectory", "inFile", "inPath", + "inDirectory", "contentsOfFile", "contentsOfPath", "contentsOfDirectory", "filePath", + "directory", "directoryPath" + ] +} + +predicate heuristicSink(DataFlow::Node node) { + node.isIncomingValue(any(Identifier id | id.getValue() = "sqlite3_temp_directory")) + or + exists(Argument arg | + pathLikeHeuristic(arg.getName()) and + node.asExpr() = arg.getValue() + ) +} + module PathInjectionConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node node) { Models::isSource(node, _) } - predicate isSink(DataFlow::Node node) { Models::isSink(node, "path-injection") } + predicate isSink(DataFlow::Node node) { + Models::isSink(node, "path-injection") or + heuristicSink(node) + } predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { none() } diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected index 38ed905df873..ec701d3dd017 100644 --- a/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected @@ -80,12 +80,17 @@ | testPathInjection.swift:509:37:509:48 | remoteString | testPathInjection.swift:503:24:503:78 | String(...) | testPathInjection.swift:509:37:509:48 | remoteString | This path depends on a $@. | testPathInjection.swift:503:24:503:78 | String(...) | user-provided value | | testPathInjection.swift:511:33:511:44 | remoteString | testPathInjection.swift:503:24:503:78 | String(...) | testPathInjection.swift:511:33:511:44 | remoteString | This path depends on a $@. | testPathInjection.swift:503:24:503:78 | String(...) | user-provided value | | testPathInjection.swift:526:28:526:29 | u1 | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:526:28:526:29 | u1 | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | +| testPathInjection.swift:528:28:528:39 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:528:28:528:39 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | | testPathInjection.swift:540:32:540:43 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:540:32:540:43 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | | testPathInjection.swift:541:38:541:49 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:541:38:541:49 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | | testPathInjection.swift:542:45:542:56 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:542:45:542:56 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | | testPathInjection.swift:546:32:546:41 | ... .pointee | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:546:32:546:41 | ... .pointee | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | | testPathInjection.swift:558:35:558:46 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:558:35:558:46 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | | testPathInjection.swift:559:41:559:52 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:559:41:559:52 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | +| testPathInjection.swift:581:25:581:36 | remoteString | testPathInjection.swift:579:24:579:78 | String(...) | testPathInjection.swift:581:25:581:36 | remoteString | This path depends on a $@. | testPathInjection.swift:579:24:579:78 | String(...) | user-provided value | +| testPathInjection.swift:583:41:583:52 | remoteString | testPathInjection.swift:579:24:579:78 | String(...) | testPathInjection.swift:583:41:583:52 | remoteString | This path depends on a $@. | testPathInjection.swift:579:24:579:78 | String(...) | user-provided value | +| testPathInjection.swift:585:38:585:49 | remoteString | testPathInjection.swift:579:24:579:78 | String(...) | testPathInjection.swift:585:38:585:49 | remoteString | This path depends on a $@. | testPathInjection.swift:579:24:579:78 | String(...) | user-provided value | +| testPathInjection.swift:587:22:587:33 | remoteString | testPathInjection.swift:579:24:579:78 | String(...) | testPathInjection.swift:587:22:587:33 | remoteString | This path depends on a $@. | testPathInjection.swift:579:24:579:78 | String(...) | user-provided value | edges | testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:351:34:351:45 | remoteString | provenance | | | testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:352:26:352:37 | remoteString | provenance | | @@ -171,6 +176,7 @@ edges | testPathInjection.swift:503:9:503:20 | remoteString | testPathInjection.swift:511:33:511:44 | remoteString | provenance | | | testPathInjection.swift:503:24:503:78 | String(...) | testPathInjection.swift:503:9:503:20 | remoteString | provenance | | | testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:526:28:526:29 | u1 | provenance | | +| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:528:28:528:39 | remoteString | provenance | | | testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:540:32:540:43 | remoteString | provenance | | | testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:541:38:541:49 | remoteString | provenance | | | testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:542:45:542:56 | remoteString | provenance | | @@ -182,6 +188,11 @@ edges | testPathInjection.swift:545:5:545:14 | ... .pointee | testPathInjection.swift:545:5:545:6 | [post] s1 [pointee] | provenance | | | testPathInjection.swift:545:18:545:29 | remoteString | testPathInjection.swift:545:5:545:14 | ... .pointee | provenance | | | testPathInjection.swift:546:32:546:33 | s1 [pointee] | testPathInjection.swift:546:32:546:41 | ... .pointee | provenance | | +| testPathInjection.swift:579:9:579:20 | remoteString | testPathInjection.swift:581:25:581:36 | remoteString | provenance | | +| testPathInjection.swift:579:9:579:20 | remoteString | testPathInjection.swift:583:41:583:52 | remoteString | provenance | | +| testPathInjection.swift:579:9:579:20 | remoteString | testPathInjection.swift:585:38:585:49 | remoteString | provenance | | +| testPathInjection.swift:579:9:579:20 | remoteString | testPathInjection.swift:587:22:587:33 | remoteString | provenance | | +| testPathInjection.swift:579:24:579:78 | String(...) | testPathInjection.swift:579:9:579:20 | remoteString | provenance | | nodes | testPathInjection.swift:340:9:340:20 | remoteString | semmle.label | remoteString | | testPathInjection.swift:340:24:340:78 | String(...) | semmle.label | String(...) | @@ -272,6 +283,7 @@ nodes | testPathInjection.swift:518:9:518:20 | remoteString | semmle.label | remoteString | | testPathInjection.swift:518:24:518:78 | String(...) | semmle.label | String(...) | | testPathInjection.swift:526:28:526:29 | u1 | semmle.label | u1 | +| testPathInjection.swift:528:28:528:39 | remoteString | semmle.label | remoteString | | testPathInjection.swift:540:32:540:43 | remoteString | semmle.label | remoteString | | testPathInjection.swift:541:38:541:49 | remoteString | semmle.label | remoteString | | testPathInjection.swift:542:45:542:56 | remoteString | semmle.label | remoteString | @@ -282,4 +294,10 @@ nodes | testPathInjection.swift:546:32:546:41 | ... .pointee | semmle.label | ... .pointee | | testPathInjection.swift:558:35:558:46 | remoteString | semmle.label | remoteString | | testPathInjection.swift:559:41:559:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:579:9:579:20 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:579:24:579:78 | String(...) | semmle.label | String(...) | +| testPathInjection.swift:581:25:581:36 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:583:41:583:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:585:38:585:49 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:587:22:587:33 | remoteString | semmle.label | remoteString | subpaths diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift b/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift index c8ab468892d9..7f5684b1f653 100644 --- a/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift @@ -525,7 +525,7 @@ func testPathInjection2( u1.appendPathComponent(remoteString) _ = NSData(contentsOf: u1) // $ Alert - let u2 = URL(filePath: remoteString) // $ MISSING: Alert + let u2 = URL(filePath: remoteString) // $ Alert _ = NSData(contentsOf: u2) // $ MISSING: Alert let u3 = NSURL(string: "")! @@ -576,15 +576,15 @@ class MyFile { } func testPathInjectionHeuristics() { - let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ MISSING: Source + let remoteString = String(contentsOf: URL(string: "http://example.com/")!) // $ Source - myOpenFile1(atPath: remoteString) // $ MISSING: Alert + myOpenFile1(atPath: remoteString) // $ Alert myOpenFile2(remoteString) // $ MISSING: Alert - myFindFiles(ofType: 0, inDirectory: remoteString) // $ MISSING: Alert + myFindFiles(ofType: 0, inDirectory: remoteString) // $ Alert - let mc = MyClass(contentsOfFile: remoteString) // $ MISSING: Alert + let mc = MyClass(contentsOfFile: remoteString) // $ Alert mc.doSomething(keyPath: remoteString) // good - not a path - mc.write(toFile: remoteString) // $ MISSING: Alert + mc.write(toFile: remoteString) // $ Alert let mf1 = MyFile(path: "") let mf2 = MyFile(path: remoteString) // $ MISSING: Alert // Originally missing From bf5e9d7fd075e8c209e26e41b2eea56c2d53ccba Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 24 Sep 2026 13:41:17 +0200 Subject: [PATCH 4/7] Support Connection.Location.uri sink --- unified/ql/lib/ext/legacy-swift.model.yml | 1 + .../ql/test/library-tests/mad/test.expected | 1 + unified/ql/test/library-tests/mad/test.swift | 10 ++ .../PathInjection/PathInjectionTest.expected | 121 +++++++++--------- .../PathInjection/testPathInjection.swift | 5 +- 5 files changed, 77 insertions(+), 61 deletions(-) diff --git a/unified/ql/lib/ext/legacy-swift.model.yml b/unified/ql/lib/ext/legacy-swift.model.yml index a44a48adada5..d44d441bcea7 100644 --- a/unified/ql/lib/ext/legacy-swift.model.yml +++ b/unified/ql/lib/ext/legacy-swift.model.yml @@ -235,6 +235,7 @@ extensions: - ["", "Connection", true, "scalar(_:_:)", "", "", "Argument[0]", "database-store", "manual"] - ["", "Connection", true, "scalar(_:_:)", "", "", "Argument[1]", "database-store", "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"] - ["", "Database", true, "usePassphrase(_:)", "", "", "Argument[0]", "encryption-key", "manual"] - ["", "Database", true, "allStatements(arguments:sql:)", "", "", "Argument[arguments:]", "database-store", "manual"] diff --git a/unified/ql/test/library-tests/mad/test.expected b/unified/ql/test/library-tests/mad/test.expected index cac0df439216..15c67b01654f 100644 --- a/unified/ql/test/library-tests/mad/test.expected +++ b/unified/ql/test/library-tests/mad/test.expected @@ -15,6 +15,7 @@ isSink | test.swift:100:24:100:36 | encryptionKey | encryption-key | | test.swift:101:18:101:24 | fileURL | path-injection | | test.swift:107:23:107:34 | seedFilePath | path-injection | +| test.swift:119:29:119:34 | string | path-injection | isSource | test.swift:4:5:4:27 | String(...) | remote | | test.swift:5:5:5:44 | String(...) | remote | diff --git a/unified/ql/test/library-tests/mad/test.swift b/unified/ql/test/library-tests/mad/test.swift index bf895e5e9274..fd1af8f38351 100644 --- a/unified/ql/test/library-tests/mad/test.swift +++ b/unified/ql/test/library-tests/mad/test.swift @@ -108,3 +108,13 @@ func testQualifiedConstructors( shouldCompactOnLaunch: nil, syncConfiguration: nil) } + +class Connection { + enum Location { + static func uri(_ path: String, parameters: String) {} + } +} + +func testConnectionLocation(string: String) { + Connection.Location.uri(string, parameters: "") // $ isSink=path-injection +} diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected index ec701d3dd017..542a755d4f04 100644 --- a/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.expected @@ -77,20 +77,21 @@ | testPathInjection.swift:482:22:482:33 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:482:22:482:33 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | | testPathInjection.swift:484:24:484:30 | buffer2 | testPathInjection.swift:477:22:477:87 | Data(...) | testPathInjection.swift:484:24:484:30 | buffer2 | This path depends on a $@. | testPathInjection.swift:477:22:477:87 | Data(...) | user-provided value | | testPathInjection.swift:486:25:486:36 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:486:25:486:36 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | -| testPathInjection.swift:509:37:509:48 | remoteString | testPathInjection.swift:503:24:503:78 | String(...) | testPathInjection.swift:509:37:509:48 | remoteString | This path depends on a $@. | testPathInjection.swift:503:24:503:78 | String(...) | user-provided value | -| testPathInjection.swift:511:33:511:44 | remoteString | testPathInjection.swift:503:24:503:78 | String(...) | testPathInjection.swift:511:33:511:44 | remoteString | This path depends on a $@. | testPathInjection.swift:503:24:503:78 | String(...) | user-provided value | -| testPathInjection.swift:526:28:526:29 | u1 | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:526:28:526:29 | u1 | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | -| testPathInjection.swift:528:28:528:39 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:528:28:528:39 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | -| testPathInjection.swift:540:32:540:43 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:540:32:540:43 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | -| testPathInjection.swift:541:38:541:49 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:541:38:541:49 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | -| testPathInjection.swift:542:45:542:56 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:542:45:542:56 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | -| testPathInjection.swift:546:32:546:41 | ... .pointee | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:546:32:546:41 | ... .pointee | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | -| testPathInjection.swift:558:35:558:46 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:558:35:558:46 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | -| testPathInjection.swift:559:41:559:52 | remoteString | testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:559:41:559:52 | remoteString | This path depends on a $@. | testPathInjection.swift:518:24:518:78 | String(...) | user-provided value | -| testPathInjection.swift:581:25:581:36 | remoteString | testPathInjection.swift:579:24:579:78 | String(...) | testPathInjection.swift:581:25:581:36 | remoteString | This path depends on a $@. | testPathInjection.swift:579:24:579:78 | String(...) | user-provided value | -| testPathInjection.swift:583:41:583:52 | remoteString | testPathInjection.swift:579:24:579:78 | String(...) | testPathInjection.swift:583:41:583:52 | remoteString | This path depends on a $@. | testPathInjection.swift:579:24:579:78 | String(...) | user-provided value | -| testPathInjection.swift:585:38:585:49 | remoteString | testPathInjection.swift:579:24:579:78 | String(...) | testPathInjection.swift:585:38:585:49 | remoteString | This path depends on a $@. | testPathInjection.swift:579:24:579:78 | String(...) | user-provided value | -| testPathInjection.swift:587:22:587:33 | remoteString | testPathInjection.swift:579:24:579:78 | String(...) | testPathInjection.swift:587:22:587:33 | remoteString | This path depends on a $@. | testPathInjection.swift:579:24:579:78 | String(...) | user-provided value | +| testPathInjection.swift:498:49:498:60 | remoteString | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:498:49:498:60 | remoteString | This path depends on a $@. | testPathInjection.swift:340:24:340:78 | String(...) | user-provided value | +| testPathInjection.swift:510:37:510:48 | remoteString | testPathInjection.swift:504:24:504:78 | String(...) | testPathInjection.swift:510:37:510:48 | remoteString | This path depends on a $@. | testPathInjection.swift:504:24:504:78 | String(...) | user-provided value | +| testPathInjection.swift:512:33:512:44 | remoteString | testPathInjection.swift:504:24:504:78 | String(...) | testPathInjection.swift:512:33:512:44 | remoteString | This path depends on a $@. | testPathInjection.swift:504:24:504:78 | String(...) | user-provided value | +| testPathInjection.swift:527:28:527:29 | u1 | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:527:28:527:29 | u1 | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:529:28:529:39 | remoteString | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:529:28:529:39 | remoteString | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:541:32:541:43 | remoteString | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:541:32:541:43 | remoteString | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:542:38:542:49 | remoteString | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:542:38:542:49 | remoteString | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:543:45:543:56 | remoteString | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:543:45:543:56 | remoteString | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:547:32:547:41 | ... .pointee | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:547:32:547:41 | ... .pointee | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:559:35:559:46 | remoteString | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:559:35:559:46 | remoteString | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:560:41:560:52 | remoteString | testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:560:41:560:52 | remoteString | This path depends on a $@. | testPathInjection.swift:519:24:519:78 | String(...) | user-provided value | +| testPathInjection.swift:582:25:582:36 | remoteString | testPathInjection.swift:580:24:580:78 | String(...) | testPathInjection.swift:582:25:582:36 | remoteString | This path depends on a $@. | testPathInjection.swift:580:24:580:78 | String(...) | user-provided value | +| testPathInjection.swift:584:41:584:52 | remoteString | testPathInjection.swift:580:24:580:78 | String(...) | testPathInjection.swift:584:41:584:52 | remoteString | This path depends on a $@. | testPathInjection.swift:580:24:580:78 | String(...) | user-provided value | +| testPathInjection.swift:586:38:586:49 | remoteString | testPathInjection.swift:580:24:580:78 | String(...) | testPathInjection.swift:586:38:586:49 | remoteString | This path depends on a $@. | testPathInjection.swift:580:24:580:78 | String(...) | user-provided value | +| testPathInjection.swift:588:22:588:33 | remoteString | testPathInjection.swift:580:24:580:78 | String(...) | testPathInjection.swift:588:22:588:33 | remoteString | This path depends on a $@. | testPathInjection.swift:580:24:580:78 | String(...) | user-provided value | edges | testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:351:34:351:45 | remoteString | provenance | | | testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:352:26:352:37 | remoteString | provenance | | @@ -169,30 +170,31 @@ edges | testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:456:15:456:26 | remoteString | provenance | | | testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:482:22:482:33 | remoteString | provenance | | | testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:486:25:486:36 | remoteString | provenance | | +| testPathInjection.swift:340:9:340:20 | remoteString | testPathInjection.swift:498:49:498:60 | remoteString | provenance | | | testPathInjection.swift:340:24:340:78 | String(...) | testPathInjection.swift:340:9:340:20 | remoteString | provenance | | | testPathInjection.swift:477:9:477:18 | remoteData | testPathInjection.swift:484:24:484:30 | buffer2 | provenance | | | testPathInjection.swift:477:22:477:87 | Data(...) | testPathInjection.swift:477:9:477:18 | remoteData | provenance | | -| testPathInjection.swift:503:9:503:20 | remoteString | testPathInjection.swift:509:37:509:48 | remoteString | provenance | | -| testPathInjection.swift:503:9:503:20 | remoteString | testPathInjection.swift:511:33:511:44 | remoteString | provenance | | -| testPathInjection.swift:503:24:503:78 | String(...) | testPathInjection.swift:503:9:503:20 | remoteString | provenance | | -| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:526:28:526:29 | u1 | provenance | | -| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:528:28:528:39 | remoteString | provenance | | -| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:540:32:540:43 | remoteString | provenance | | -| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:541:38:541:49 | remoteString | provenance | | -| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:542:45:542:56 | remoteString | provenance | | -| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:545:18:545:29 | remoteString | provenance | | -| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:558:35:558:46 | remoteString | provenance | | -| testPathInjection.swift:518:9:518:20 | remoteString | testPathInjection.swift:559:41:559:52 | remoteString | provenance | | -| testPathInjection.swift:518:24:518:78 | String(...) | testPathInjection.swift:518:9:518:20 | remoteString | provenance | | -| testPathInjection.swift:545:5:545:6 | [post] s1 [pointee] | testPathInjection.swift:546:32:546:33 | s1 [pointee] | provenance | | -| testPathInjection.swift:545:5:545:14 | ... .pointee | testPathInjection.swift:545:5:545:6 | [post] s1 [pointee] | provenance | | -| testPathInjection.swift:545:18:545:29 | remoteString | testPathInjection.swift:545:5:545:14 | ... .pointee | provenance | | -| testPathInjection.swift:546:32:546:33 | s1 [pointee] | testPathInjection.swift:546:32:546:41 | ... .pointee | provenance | | -| testPathInjection.swift:579:9:579:20 | remoteString | testPathInjection.swift:581:25:581:36 | remoteString | provenance | | -| testPathInjection.swift:579:9:579:20 | remoteString | testPathInjection.swift:583:41:583:52 | remoteString | provenance | | -| testPathInjection.swift:579:9:579:20 | remoteString | testPathInjection.swift:585:38:585:49 | remoteString | provenance | | -| testPathInjection.swift:579:9:579:20 | remoteString | testPathInjection.swift:587:22:587:33 | remoteString | provenance | | -| testPathInjection.swift:579:24:579:78 | String(...) | testPathInjection.swift:579:9:579:20 | remoteString | provenance | | +| testPathInjection.swift:504:9:504:20 | remoteString | testPathInjection.swift:510:37:510:48 | remoteString | provenance | | +| testPathInjection.swift:504:9:504:20 | remoteString | testPathInjection.swift:512:33:512:44 | remoteString | provenance | | +| testPathInjection.swift:504:24:504:78 | String(...) | testPathInjection.swift:504:9:504:20 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:527:28:527:29 | u1 | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:529:28:529:39 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:541:32:541:43 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:542:38:542:49 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:543:45:543:56 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:546:18:546:29 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:559:35:559:46 | remoteString | provenance | | +| testPathInjection.swift:519:9:519:20 | remoteString | testPathInjection.swift:560:41:560:52 | remoteString | provenance | | +| testPathInjection.swift:519:24:519:78 | String(...) | testPathInjection.swift:519:9:519:20 | remoteString | provenance | | +| testPathInjection.swift:546:5:546:6 | [post] s1 [pointee] | testPathInjection.swift:547:32:547:33 | s1 [pointee] | provenance | | +| testPathInjection.swift:546:5:546:14 | ... .pointee | testPathInjection.swift:546:5:546:6 | [post] s1 [pointee] | provenance | | +| testPathInjection.swift:546:18:546:29 | remoteString | testPathInjection.swift:546:5:546:14 | ... .pointee | provenance | | +| testPathInjection.swift:547:32:547:33 | s1 [pointee] | testPathInjection.swift:547:32:547:41 | ... .pointee | provenance | | +| testPathInjection.swift:580:9:580:20 | remoteString | testPathInjection.swift:582:25:582:36 | remoteString | provenance | | +| testPathInjection.swift:580:9:580:20 | remoteString | testPathInjection.swift:584:41:584:52 | remoteString | provenance | | +| testPathInjection.swift:580:9:580:20 | remoteString | testPathInjection.swift:586:38:586:49 | remoteString | provenance | | +| testPathInjection.swift:580:9:580:20 | remoteString | testPathInjection.swift:588:22:588:33 | remoteString | provenance | | +| testPathInjection.swift:580:24:580:78 | String(...) | testPathInjection.swift:580:9:580:20 | remoteString | provenance | | nodes | testPathInjection.swift:340:9:340:20 | remoteString | semmle.label | remoteString | | testPathInjection.swift:340:24:340:78 | String(...) | semmle.label | String(...) | @@ -276,28 +278,29 @@ nodes | testPathInjection.swift:482:22:482:33 | remoteString | semmle.label | remoteString | | testPathInjection.swift:484:24:484:30 | buffer2 | semmle.label | buffer2 | | testPathInjection.swift:486:25:486:36 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:503:9:503:20 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:503:24:503:78 | String(...) | semmle.label | String(...) | -| testPathInjection.swift:509:37:509:48 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:511:33:511:44 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:518:9:518:20 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:518:24:518:78 | String(...) | semmle.label | String(...) | -| testPathInjection.swift:526:28:526:29 | u1 | semmle.label | u1 | -| testPathInjection.swift:528:28:528:39 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:540:32:540:43 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:541:38:541:49 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:542:45:542:56 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:545:5:545:6 | [post] s1 [pointee] | semmle.label | [post] s1 [pointee] | -| testPathInjection.swift:545:5:545:14 | ... .pointee | semmle.label | ... .pointee | -| testPathInjection.swift:545:18:545:29 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:546:32:546:33 | s1 [pointee] | semmle.label | s1 [pointee] | -| testPathInjection.swift:546:32:546:41 | ... .pointee | semmle.label | ... .pointee | -| testPathInjection.swift:558:35:558:46 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:559:41:559:52 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:579:9:579:20 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:579:24:579:78 | String(...) | semmle.label | String(...) | -| testPathInjection.swift:581:25:581:36 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:583:41:583:52 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:585:38:585:49 | remoteString | semmle.label | remoteString | -| testPathInjection.swift:587:22:587:33 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:498:49:498:60 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:504:9:504:20 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:504:24:504:78 | String(...) | semmle.label | String(...) | +| testPathInjection.swift:510:37:510:48 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:512:33:512:44 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:519:9:519:20 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:519:24:519:78 | String(...) | semmle.label | String(...) | +| testPathInjection.swift:527:28:527:29 | u1 | semmle.label | u1 | +| testPathInjection.swift:529:28:529:39 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:541:32:541:43 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:542:38:542:49 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:543:45:543:56 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:546:5:546:6 | [post] s1 [pointee] | semmle.label | [post] s1 [pointee] | +| testPathInjection.swift:546:5:546:14 | ... .pointee | semmle.label | ... .pointee | +| testPathInjection.swift:546:18:546:29 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:547:32:547:33 | s1 [pointee] | semmle.label | s1 [pointee] | +| testPathInjection.swift:547:32:547:41 | ... .pointee | semmle.label | ... .pointee | +| testPathInjection.swift:559:35:559:46 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:560:41:560:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:580:9:580:20 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:580:24:580:78 | String(...) | semmle.label | String(...) | +| testPathInjection.swift:582:25:582:36 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:584:41:584:52 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:586:38:586:49 | remoteString | semmle.label | remoteString | +| testPathInjection.swift:588:22:588:33 | remoteString | semmle.label | remoteString | subpaths diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift b/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift index 7f5684b1f653..238646a61565 100644 --- a/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/testPathInjection.swift @@ -368,10 +368,10 @@ func test(buffer1: UnsafeMutablePointer, buffer2: UnsafeMutablePointer()) // $ MISSING: Alert fm.replaceItem( - at: safeUrl, withItemAt: remoteUrl, backupItemName: nil, options: [], // $ SPURIOUS: Alert + at: safeUrl, withItemAt: remoteUrl, backupItemName: nil, options: [], // $ SPURIOUS: Alert resultingItemURL: AutoreleasingUnsafeMutablePointer()) // $ MISSING: Alert fm.copyItem(at: remoteUrl, to: safeUrl) // $ Alert fm.copyItem(at: safeUrl, to: remoteUrl) // $ Alert @@ -495,6 +495,7 @@ func test(buffer1: UnsafeMutablePointer, buffer2: UnsafeMutablePointer Date: Thu, 24 Sep 2026 13:56:24 +0200 Subject: [PATCH 5/7] unified: Add TODO for barriers --- unified/ql/src/queries/security/CWE-022/PathInjection.ql | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/unified/ql/src/queries/security/CWE-022/PathInjection.ql b/unified/ql/src/queries/security/CWE-022/PathInjection.ql index d841bb6404c2..3ae09d533f1e 100644 --- a/unified/ql/src/queries/security/CWE-022/PathInjection.ql +++ b/unified/ql/src/queries/security/CWE-022/PathInjection.ql @@ -48,7 +48,10 @@ module PathInjectionConfig implements DataFlow::ConfigSig { predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { none() } - predicate isBarrier(DataFlow::Node node) { none() } + predicate isBarrier(DataFlow::Node node) { + // TODO: add barriers + none() + } } module PathInjectionFlow = DataFlow::Global; From ce5fb70ea33f32396844dea053d66d0abe428747 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 24 Sep 2026 14:01:51 +0200 Subject: [PATCH 6/7] unified: Record CFG consistency errors --- .../CWE-022/PathInjection/CONSISTENCY/CfgConsistency.expected | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 unified/ql/test/query-tests/security/CWE-022/PathInjection/CONSISTENCY/CfgConsistency.expected diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/CONSISTENCY/CfgConsistency.expected b/unified/ql/test/query-tests/security/CWE-022/PathInjection/CONSISTENCY/CfgConsistency.expected new file mode 100644 index 000000000000..b7d75c0421ae --- /dev/null +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/CONSISTENCY/CfgConsistency.expected @@ -0,0 +1,4 @@ +consistencyOverview +| deadEnd | 1 | +deadEnd +| testPathInjection.swift:330:14:330:62 | Entry | From 3426bb58f6870a4ee3ea7e3fb8501e1c2abbe6d2 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 24 Sep 2026 20:35:44 +0200 Subject: [PATCH 7/7] unified: Fix case of query path --- .../security/CWE-022/PathInjection/PathInjectionTest.qlref | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.qlref b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.qlref index 6269075fd961..1fab38d532b7 100644 --- a/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.qlref +++ b/unified/ql/test/query-tests/security/CWE-022/PathInjection/PathInjectionTest.qlref @@ -1,3 +1,3 @@ -query: queries/Security/CWE-022/PathInjection.ql +query: queries/security/CWE-022/PathInjection.ql postprocess: - utils/test/InlineExpectationsTestQuery.ql