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

unarchiveObject(with:)

UNCONDITIONALLY_UNSAFE

yes

unarchiveTopLevelObjectWithData(_:)

UNCONDITIONALLY_UNSAFE

yes

setClass(_:forClassName:)

UNCONDITIONALLY_UNSAFE

yes

unarchivedObject(ofClass:from:)

WEAK_TYPE_CONTROL

no

unarchivedObject(ofClasses:from:)

CONTROLLED_EXPECTED_TYPE

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)

Limitations

Only calls the metadata resolves to the Foundation API are seen. A decoder reached through a wrapper of your own is not recognized unless the wrapper itself is described in the library metadata.

© 2026 Xygeni. All rights reserved