core-nfc — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited core-nfc (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 2 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Read and write NFC tags on iPhone using the CoreNFC framework. Covers NDEF reader sessions, tag reader sessions, NDEF message construction, entitlements, and background tag reading. Targets Swift 6.3 / iOS 26+.
NFCReaderUsageDescription to Info.plist with a user-facing reason stringcom.apple.developer.nfc.readersession.formats entitlement with the current TAG value; do not add legacy NDEFcom.apple.developer.nfc.readersession.iso7816.select-identifiers in Info.plistcom.apple.developer.nfc.readersession.felica.systemcodes; do not use wildcard system codesNFC reading requires iPhone 7 or later. Always check for reader session availability before creating NFC UI or sessions. Use the concrete reader session type you are about to create.
import CoreNFC
guard NFCNDEFReaderSession.readingAvailable else {
// Device does not support NFC or feature is restricted
showUnsupportedMessage()
return
}| Type | Role |
|---|---|
NFCNDEFReaderSession | Scans for NDEF-formatted tags |
NFCTagReaderSession | Scans for ISO7816, ISO15693, FeliCa, MIFARE tags |
NFCNDEFMessage | Collection of NDEF payload records |
NFCNDEFPayload | Single record within an NDEF message |
NFCNDEFTag | Protocol for interacting with an NDEF-capable tag |
Use NFCNDEFReaderSession to read NDEF-formatted data from tags. This is the simplest path for reading standard tag content like URLs, text, and MIME data.
import CoreNFC
final class NDEFReader: NSObject, NFCNDEFReaderSessionDelegate {
private var session: NFCNDEFReaderSession?
func beginScanning() {
guard NFCNDEFReaderSession.readingAvailable else { return }
session = NFCNDEFReaderSession(
delegate: self,
queue: nil,
invalidateAfterFirstRead: false
)
session?.alertMessage = "Hold your iPhone near an NFC tag."
session?.begin()
}
// MARK: - NFCNDEFReaderSessionDelegate
func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
// Session is scanning
}
func readerSession(
_ session: NFCNDEFReaderSession,
didDetectNDEFs messages: [NFCNDEFMessage]
) {
for message in messages {
for record in message.records {
processRecord(record)
}
}
}
func readerSession(
_ session: NFCNDEFReaderSession,
didInvalidateWithError error: Error
) {
let nfcError = error as? NFCReaderError
if nfcError?.code != .readerSessionInvalidationErrorFirstNDEFTagRead,
nfcError?.code != .readerSessionInvalidationErrorUserCanceled {
print("Session invalidated: \(error.localizedDescription)")
}
self.session = nil
}
}For read-write operations, use the tag-detection delegate method to connect to individual tags:
func readerSession(
_ session: NFCNDEFReaderSession,
didDetect tags: [any NFCNDEFTag]
) {
guard let tag = tags.first else {
session.restartPolling()
return
}
session.connect(to: tag) { error in
if let error {
session.invalidate(errorMessage: "Connection failed: \(error)")
return
}
tag.queryNDEFStatus { status, capacity, error in
guard error == nil else {
session.invalidate(errorMessage: "Query failed.")
return
}
switch status {
case .notSupported:
session.invalidate(errorMessage: "Tag is not NDEF compliant.")
case .readOnly:
tag.readNDEF { message, error in
if let message {
self.processMessage(message)
}
session.invalidate()
}
case .readWrite:
tag.readNDEF { message, error in
if let message {
self.processMessage(message)
}
session.alertMessage = "Tag read successfully."
session.invalidate()
}
@unknown default:
session.invalidate()
}
}
}
}Use NFCTagReaderSession when you need direct access to the native tag protocol (ISO 7816, ISO 15693, FeliCa, or MIFARE).
Polling options are protocol-specific: .iso14443 detects ISO 7816-compatible and MIFARE tags, .iso15693 detects ISO 15693 tags, and .iso18092 detects FeliCa tags. Do not use NFCTagReaderSession for payment-related AIDs; Apple documents NFCPaymentTagReaderSession for eligible EU payment use cases.
final class TagReader: NSObject, NFCTagReaderSessionDelegate {
private var session: NFCTagReaderSession?
func beginScanning() {
guard NFCTagReaderSession.readingAvailable else { return }
session = NFCTagReaderSession(
pollingOption: [.iso14443, .iso15693, .iso18092],
delegate: self,
queue: nil
)
session?.alertMessage = "Hold your iPhone near a tag."
session?.begin()
}
func tagReaderSessionDidBecomeActive(
_ session: NFCTagReaderSession
) { }
func tagReaderSession(
_ session: NFCTagReaderSession,
didDetect tags: [NFCTag]
) {
guard let tag = tags.first else { return }
session.connect(to: tag) { error in
guard error == nil else {
session.invalidate(
errorMessage: "Connection failed."
)
return
}
switch tag {
case .iso7816(let iso7816Tag):
self.readISO7816(tag: iso7816Tag, session: session)
case .miFare(let miFareTag):
self.readMiFare(tag: miFareTag, session: session)
case .iso15693(let iso15693Tag):
self.readISO15693(tag: iso15693Tag, session: session)
case .feliCa(let feliCaTag):
self.readFeliCa(tag: feliCaTag, session: session)
@unknown default:
session.invalidate(errorMessage: "Unsupported tag type.")
}
}
}
func tagReaderSession(
_ session: NFCTagReaderSession,
didInvalidateWithError error: Error
) {
self.session = nil
}
}Write NDEF data to a connected tag. Always check readWrite status first.
func writeToTag(
tag: any NFCNDEFTag,
session: NFCNDEFReaderSession,
url: URL
) {
tag.queryNDEFStatus { status, capacity, error in
guard status == .readWrite else {
session.invalidate(errorMessage: "Tag is read-only.")
return
}
guard let payload = NFCNDEFPayload.wellKnownTypeURIPayload(
url: url
) else {
session.invalidate(errorMessage: "Invalid URL.")
return
}
let message = NFCNDEFMessage(records: [payload])
tag.writeNDEF(message) { error in
if let error {
session.invalidate(
errorMessage: "Write failed: \(error.localizedDescription)"
)
} else {
session.alertMessage = "Tag written successfully."
session.invalidate()
}
}
}
}// URL payload
let urlPayload = NFCNDEFPayload.wellKnownTypeURIPayload(
url: URL(string: "https://example.com")!
)
// Text payload
let textPayload = NFCNDEFPayload.wellKnownTypeTextPayload(
string: "Hello NFC",
locale: Locale(identifier: "en")
)
// Custom payload
let customPayload = NFCNDEFPayload(
format: .nfcExternal,
type: "com.example:mytype".data(using: .utf8)!,
identifier: Data(),
payload: "custom-data".data(using: .utf8)!
)func processRecord(_ record: NFCNDEFPayload) {
switch record.typeNameFormat {
case .nfcWellKnown:
if let url = record.wellKnownTypeURIPayload() {
print("URL: \(url)")
} else if let (text, locale) = record.wellKnownTypeTextPayload() {
print("Text (\(locale)): \(text)")
}
case .absoluteURI:
if let uri = String(data: record.payload, encoding: .utf8) {
print("Absolute URI: \(uri)")
}
case .media:
let mimeType = String(data: record.type, encoding: .utf8) ?? ""
print("MIME type: \(mimeType), size: \(record.payload.count)")
case .nfcExternal:
let type = String(data: record.type, encoding: .utf8) ?? ""
print("External type: \(type)")
case .empty, .unknown, .unchanged:
break
@unknown default:
break
}
}On iPhone XS and later, iOS can read NFC tags in the background without opening your app. The NDEF message must contain a URI record (typeNameFormat == .nfcWellKnown, type U). If there are multiple URI records, the system uses the first one.
For app-specific routing, write a universal link to the tag and configure the Associated Domains capability for that domain. Background tag reading also supports specific system URL schemes such as web, email, SMS, telephone, FaceTime, Maps, and HomeKit setup. It does not support custom URL schemes, and the system does not route by bundle ID or arbitrary NDEF content type.
When a user taps a compatible tag, iOS displays a notification that opens your app. Handle the tag data via NSUserActivity:
func scene(
_ scene: UIScene,
continue userActivity: NSUserActivity
) {
guard userActivity.activityType ==
NSUserActivityTypeBrowsingWeb else { return }
let message = userActivity.ndefMessagePayload
guard message.records.first?.typeNameFormat != .empty else { return }
for record in message.records {
processRecord(record)
}
}Without the com.apple.developer.nfc.readersession.formats entitlement, reader sessions cannot access NFC hardware. Use the current TAG value for Core NFC reader sessions; do not copy older examples that add NDEF.
Creating an NFC session on an unsupported or restricted device fails before the scan UI can do useful work.
Check NFCNDEFReaderSession.readingAvailable or NFCTagReaderSession.readingAvailable before creating the matching session.
The session invalidates for multiple reasons. Distinguishing user cancellation from real errors prevents false error alerts.
// WRONG -- shows error when user cancels
func readerSession(
_ session: NFCNDEFReaderSession,
didInvalidateWithError error: Error
) {
showAlert("NFC Error: \(error.localizedDescription)")
}
// CORRECT -- filter expected invalidation reasons
func readerSession(
_ session: NFCNDEFReaderSession,
didInvalidateWithError error: Error
) {
let nfcError = error as? NFCReaderError
switch nfcError?.code {
case .readerSessionInvalidationErrorUserCanceled,
.readerSessionInvalidationErrorFirstNDEFTagRead:
break // Normal termination
default:
showAlert("NFC Error: \(error.localizedDescription)")
}
self.session = nil
}Once a session is invalidated, it cannot be restarted. Nil out your reference and create a new session for the next scan.
// WRONG -- reusing invalidated session
func scanAgain() {
session?.begin() // Does nothing, session is dead
}
// CORRECT -- create a new session
func scanAgain() {
session = NFCNDEFReaderSession(
delegate: self, queue: nil, invalidateAfterFirstRead: false
)
session?.begin()
}Writing to a read-only tag silently fails or produces confusing errors.
// WRONG -- writes without checking status
tag.writeNDEF(message) { error in
// May fail on read-only tags
}
// CORRECT -- check status first
tag.queryNDEFStatus { status, capacity, error in
guard status == .readWrite else {
session.invalidate(errorMessage: "Tag is read-only.")
return
}
tag.writeNDEF(message) { error in
// Handle result
}
}NFCReaderUsageDescription set in Info.plistcom.apple.developer.nfc.readersession.formats entitlement uses TAG, not legacy NDEFNFCNDEFReaderSession.readingAvailable or NFCTagReaderSession.readingAvailable checked before creating sessionsbegin()didInvalidateWithError distinguishes user cancellation from actual errorsNFCTagReaderSession.iso18092NFCTagReaderSession~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.