Archive decoded with an API that reconstructs the class it names
ID |
swift.insecure_deserialization |
Severity |
low |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Misconfiguration |
Language |
Swift |
Tags |
ASVS50:v1.5.2, CWE:502, MASWE:0006, NIST.SP.800-53, OWASP:2025:A08, PCI-DSS:6.5.1 |
Description
An archive is decoded with an API that reconstructs whatever class the archive names, so the archive
decides which type is instantiated and init(coder:) runs on it.
Rationale
swift.code_injection_deserialization reports decoding when a data path can be followed from an external
input to the call. That needs the input to be visible: an NSKeyedUnarchiver.unarchiveObject(with:) in a
codec or a helper, where the caller lives elsewhere, has no source to follow and is reported by nothing.
Its severity is low because the finding says nothing about whether an attacker-controlled archive
actually reaches the call — when that can be shown, swift.code_injection_deserialization reports it as
critical, and this finding is dropped as a duplicate.
The sinks come from the metadata, not from this rule
The shipped Foundation metadata already grades the NSKeyedUnarchiver API, and this detector reports
exactly what it grades as unconditional:
| API | Metadata grading | Reported |
|---|---|---|
|
|
yes |
|
|
yes |
|
|
yes |
|
|
no |
|
|
no |
Driving off the metadata rather than a name list keeps this rule and the tainting rule agreed on what is dangerous, and means a correction to the metadata reaches both at once.
The two secure-coding forms are deliberately left alone. unarchivedObject(ofClasses:from:) is the API
Apple recommends, and reporting the migration target would push developers away from the fix.
Examples
Reported:
static func restore(from data: Data) -> Any? {
return NSKeyedUnarchiver.unarchiveObject(with: data) // reported
}
Not reported — secure coding with an allowlist:
static func restore(from data: Data) throws -> Any? {
return try NSKeyedUnarchiver.unarchivedObject(
ofClasses: [Order.self, Customer.self], from: data)
}
Remediation
Adopt NSSecureCoding on the types you archive and decode through the allowlisting API:
final class Order: NSObject, NSSecureCoding {
static var supportsSecureCoding: Bool { true }
// ...
}
let order = try NSKeyedUnarchiver.unarchivedObject(ofClass: Order.self, from: data)
Better still, where the archive only needs to carry data, use Codable with JSONDecoder, which names
no classes:
let order = try JSONDecoder().decode(Order.self, from: data)