Experimental dev diary for tracking interesting things

February 2026

Stripped vs Unstripped: What Survives

Let’s see what stripping actually removes.

Source:

class SecretManager {
    let apiKey = "sk_live_9a8b7c6d5e4f"
    let endpoint = "https://api.internal.company.com/v2"

    func validateLicense(_ license: String) -> Bool {
        return license == "XXXX-YYYY-ZZZZ"
    }
}

Unstripped - full symbol table:

$ nm MachODemo | xcrun swift-demangle | grep SecretManager
MachODemo.SecretManager.validateLicense(Swift.String) -> Swift.Bool
MachODemo.SecretManager.apiKey.getter : Swift.String
MachODemo.SecretManager.endpoint.getter : Swift.String
MachODemo.SecretManager.__allocating_init() -> MachODemo.SecretManager
...

Stripped - symbol table removed:

$ strip MachODemo -o MachODemo_stripped
$ nm MachODemo_stripped | grep SecretManager
(nothing)

But strings still finds everything:

$ strings MachODemo_stripped | grep -E "(sk_live|SecretManager|apiKey)"
sk_live_9a8b7c6d5e4f
https://api.internal.company.com/v2
XXXX-YYYY-ZZZZ
SecretManager
apiKey
endpoint

Stripping removes the symbol table (function addresses), but hardcoded strings and Swift reflection metadata stay embedded in __TEXT. The secrets survive.

What Mangled Swift Names Actually Leak

Let’s compare what the same class looks like in a compiled binary.

Swift source:

class UserController {
    func authenticate(with password: String) -> Bool {
        return password == "secret"
    }
}

ObjC source:

@interface UserController : NSObject
- (BOOL)authenticateWithPassword:(NSString *)password;
@end

ObjC in binary - plaintext:

$ nm ObjCAuth | grep -i user
0000000100000928 t -[UserController authenticateWithPassword:]
00000001000080c8 S _OBJC_CLASS_$_UserController

Swift in binary - mangled but decodable:

$ nm SwiftAuth | grep -i user
0000000100000cfc t _$s9SwiftAuth14UserControllerC12authenticate4withSbSS_tF
...

$ nm SwiftAuth | grep -i user | xcrun swift-demangle
0000000100000cfc t SwiftAuth.UserController.authenticate(with: Swift.String) -> Swift.Bool

The mangled symbol _$s9SwiftAuth14UserControllerC12authenticate4withSbSS_tF encodes:

  • Module: SwiftAuth (9 chars)
  • Class: UserController (14 chars, C = class)
  • Method: authenticate
  • Label: with
  • Types: Sb = Bool, SS = String

Both expose your API surface. Swift just requires one extra step.

The Compilation Pipeline: Where Obfuscation Lives

       ObjC                     Swift
         ↓                        ↓
    Preprocessor                  │
         ↓                        ↓
        AST                      AST
         │                        ↓
         │                    Raw SIL
         │                        ↓
         │                  Canonical SIL
         ↓                        ↓
      LLVM IR                  LLVM IR
         ↓                        ↓
     Assembly                 Assembly
         ↓                        ↓
    Object (.o)              Object (.o)
         └────────┬───────────────┘
                  ↓ linker
            Executable

View each stage:

# ObjC
clang -E file.m                    # preprocessed
clang -Xclang -ast-dump file.m     # AST
clang -S -emit-llvm file.m         # LLVM IR
clang -S file.m                    # assembly
clang -c file.m                    # object
clang file.o -o file               # executable

# Swift
swiftc -dump-ast file.swift        # AST
swiftc -emit-silgen file.swift     # raw SIL
swiftc -emit-sil file.swift        # canonical SIL
swiftc -emit-ir file.swift         # LLVM IR
swiftc -S file.swift               # assembly
swiftc -c file.swift               # object
swiftc file.o -o file              # executable

Where can obfuscation happen?

Level Tools Notes
Source Swift Shield Renames symbols before compile
LLVM IR OLLVM, Hikari Language agnostic, most common

Stock compilers don’t obfuscate - you need additional tooling.

Binary Spelunking 101: nm, otool, strings

Some tools for peeking inside compiled binaries. Let’s compile a simple Swift file:

class UserAuthenticator {
    private let apiKey = "sk_live_abc123secret"
    private let apiEndpoint = "https://api.myapp.com/v1/auth"

    func authenticate(username: String, password: String) -> Bool {
        return user == "admin" && pass == "supersecret123"
    }
}

class PaymentProcessor {
    let merchantId = "merchant_prod_xyz789"
    func processPayment(amount: Double) -> Bool { ... }
}

strings - extract readable text:

$ strings BinaryDemo | grep -iE "(secret|http|merchant|admin)"
sk_live_abc123secret
https://api.myapp.com/v1/auth
admin
supersecret123
merchant_prod_xyz789

nm - list symbols (functions, classes, globals):

$ nm BinaryDemo | grep Payment | head -5
00000001000013ec t _$s10BinaryDemo16PaymentProcessorC07processC06amountSbSd_tF
00000001000013b8 t _$s10BinaryDemo16PaymentProcessorC10merchantIdSSvg
...

$ nm BinaryDemo | xcrun swift-demangle | grep Payment | head -3
00000001000013ec t BinaryDemo.PaymentProcessor.processPayment(amount: Swift.Double) -> Swift.Bool
00000001000013b8 t BinaryDemo.PaymentProcessor.merchantId.getter : Swift.String

otool -L - linked libraries:

$ otool -L BinaryDemo
BinaryDemo:
  /usr/lib/libSystem.B.dylib (...)
  /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (...)
  /usr/lib/swift/libswiftCore.dylib (...)

Note: This binary was compiled with plain swiftc - no symbol stripping, obfuscation, or App Store encryption (FairPlay).

January 2026

#require in Swift Testing

The #require macro in Swift Testing safely unwraps optionals and throws if nil, stopping the test immediately. It replaces the old XCTUnwrap pattern.

Before (XCTest):

func testUserName() throws {
    let user = try XCTUnwrap(fetchUser())
    XCTAssertEqual(user.name, "John")
}

After (Swift Testing):

@Test func userName() throws {
    let user = try #require(fetchUser())
    #expect(user.name == "John")
}

It also works with boolean conditions - the test fails if the condition is false:

@Test func adminAccess() throws {
    let user = try #require(fetchUser())
    try #require(user.isAdmin)  // Fails test if not admin
    // Continue with admin-only tests...
}

The key difference from #expect: #require stops execution on failure, while #expect records the failure but continues. Use #require when subsequent code depends on the condition being true.

Tart: macOS VMs like containers

Tart from Cirrus Labs uses Apple’s Virtualization.framework to run macOS/Linux VMs on Apple Silicon. The killer feature: it distributes VM images via OCI registries, so you can pull/push them like Docker images.

tart clone ghcr.io/cirruslabs/macos-sequoia-xcode:latest my-vm
tart run my-vm

Combine with Packer to automate image creation - install Xcode, dependencies, snapshot, and push to your registry:

tart push my-vm ghcr.io/myorg/macos-ci:v1

OCI is only the distribution format - it’s not containerizing macOS (that would violate licensing). It just chunks the disk image into layers for efficient transfer and versioning. Great for CI runners.

Swift ownership: borrowing, consuming, inout

Swift 5.9 introduced ownership modifiers for non-copyable types (~Copyable). Here’s the difference:

struct DBConnection: ~Copyable {
    mutating func open() { /* ... */ }
    mutating func close() { /* ... */ }
    func query(_ sql: String) { /* ... */ }
}

borrowing - read-only access, caller keeps ownership:

func inspect(_ connection: borrowing DBConnection) {
    connection.query("SELECT 1")  // OK - read only
    // connection.open()          // Error - can't mutate
}

consuming - takes ownership, caller loses the value:

func runAndClose(_ connection: consuming DBConnection) {
    connection.open()
    connection.close()
    // connection is destroyed here
}

runAndClose(db)
// db.query("...")  // Error - db was consumed

inout - mutable borrow, caller keeps ownership:

func reopen(_ connection: inout DBConnection) {
    connection.open()  // OK - can mutate
}
// db still usable after call

Useful for modeling resources like DB connections, file handles, or locks where you want compile-time guarantees against use-after-close bugs.

rss facebook twitter github youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora