Sonoma’s Web Apps in the log

Yesterday, I explained how Sonoma’s new Web Apps work, and looked briefly at the mechanisms involved and their security. That account was based largely on examination of the log during the creation and launching of a simple example Web App. This article follows that up by providing log extracts and analysis to support that account, and provide further insights.

Log records quoted here are selected from many thousands gathered on a macOS 14.0 virtual machine running on a 14.0 host (Apple silicon), with log privacy disabled. Despite that, there are still many entries that have been censored using <private>, although far fewer than there would have been without it being disabled. To make these extracts more readable, I have adopted the following conventions:

  • Almost all < and > symbols have been removed, and most replaced with [].
  • […] in the entry means that some expansive content has been omitted.
  • [cdhash] and [3 x cdhash] represent cdhashes.
  • [UUID] is the Web App’s UUID, in this case 25CC8ABE-4AFC-43A3-804F-3361ED24BE26.
  • [private] represents still-censored private data.

Each entry is given as the time in decimal seconds (arbitrary, but with 0.0 as the approximate start of that activity), followed by the main process or subsystem reporting that entry, and the entry itself.

Creating the Web App

This starts with entries from ShareKit handling the Share menu
0.743129 com.apple.sharekit Asked to perform share transport with identifier: [private]
0.744675 com.apple.sharekit Perform share transport with identifier: [private]
0.745758 com.apple.sharekit Did choose sharing service: [private]

LaunchServices then starts making a TemplateApp
3.655639 LaunchServices _LSTemplateApplicationCreate([private],[private],com.apple.Safari.WebApp,com.apple.Safari.WebApp, args=[private] keys=(null) option={teamidentifier = "";}
3.669841 LaunchServices doCreateTemplateApp: success, appUUID=[UUID] url=[private]

When that’s reported, the app bundle has been created and is ready for its ad hoc signature.

The next entry is a puzzle, as no quarantine extended attribute is attached to the app
3.671367 Safari Unable to remove quarantine attribution from the web app.

LaunchServices completes creation of the Web App by computing the hashes for its ‘code directory’, hence the name cdhashes, although in this case there’s no code in the app bundle, and the cdhashes are primarily responsible for ensuring that the app’s Info.plist hasn’t been tampered with.
3.695379 LaunchServices Marking URL [private] as mutable
3.697782 LaunchServices Computed cdHashes for [UUID] [private] is {length = 20, bytes = [cdhash]}
3.697803 LaunchServices Marking URL [private] as immutable
3.698817 LaunchServices doFinalizeTemplateApp: success, appUUID=[UUID] url=[private]
3.712387 LaunchServices copyCDHashes, arch=arm64 codeRef=[private]
3.712802 LaunchServices copyCDHashes, arch=arm64e codeRef=[private]
3.713202 LaunchServices copyCDHashes, arch=x86_64 codeRef=[private]
3.713290 LaunchServices Bundle: Unexpectedly didn't have this for bundle, so looking ut ip [private], cdHashes=([3 x cdhash])

I suspect that ‘looking ut ip’ should read ‘looking it up’, and that this isn’t really unexpected!

The Web App is then installed
3.716414 MobileInstallation (null)/Unknown Persona:5:4:1:0:_LSServerRegisterItemInfo begin
3.716558 MobileInstallation com.apple.Safari.WebApp.[UUID]/Unknown Persona:5:5:1:1:Building bundle record for app
3.716639 LaunchServices Bundle [private], templateApp=1
3.716653 MobileInstallation com.apple.Safari.WebApp.[UUID]/Unknown Persona:5:5:2:1:Built bundle record for app

With this complete, LaunchServices then registers the TemplateApp complete with its UUID
3.716968 LaunchServices cdHashes for [private] = ([3 x cdhash])
3.716989 LaunchServices bundle record for [private] will be registered launch-disabled.
3.717252 MobileInstallation com.apple.Safari.WebApp.[UUID]/Unknown Persona:5:4:2:1:_LSServerRegisterItemInfo result = 0
3.722528 LaunchServices Registering template-app [private] with info {CFBundleIdentifier = "com.apple.Safari.WebApp"; TemplateAppUUID = "[UUID]"; defaultarguments = 1; teamIdentifier = 0000000000;}

It repeats that process, then reports the app as being registered
3.726489 LaunchServices registered [private] (status 0, old id 0x0) as unit 0x914 on behalf of pid 371

Running the Web App

LaunchServices handles this initially, as is usual, and registers the app to be launched as a TemplateApp
0.832421 LaunchServices LAUNCH: inURLs.count=1 roles=E options=(null) launcher=launcher:([pid=371/916 uid=501/501/501 20/20 100003] platform=1 sdkVersion=E0000 canSetEnv canSetArgs)
0.837069 LaunchServices Registering template-app [private] with info {CFBundleIdentifier = "com.apple.Safari.WebApp"; TemplateAppUUID = “[UUID]”; defaultarguments = 1; teamIdentifier = 0000000000;}

The app’s cdhashes are retrieved and checked
0.840520 LaunchServices copyCDHashes, arch=arm64 codeRef=[private]
0.841187 LaunchServices copyCDHashes, arch=arm64e codeRef=[private]
0.841804 LaunchServices copyCDHashes, arch=x86_64 codeRef=[private]
0.841935 LaunchServices Bundle: Unexpectedly didn't have this for bundle, so looking ut ip [private], cdHashes=([3 x cdhash])

The bundle record for the app is built next, it’s registered as being trusted by LaunchServices, and installed with the NetworkServiceProxy server
0.842160 MobileInstallation (null)/Unknown Persona:5:4:1:0:_LSServerRegisterItemInfo begin
0.842607 MobileInstallation com.apple.Safari.WebApp.[UUID]/Unknown Persona:5:5:1:1:Building bundle record for app
0.842744 LaunchServices Bundle [private], templateApp=1
0.842773 MobileInstallation com.apple.Safari.WebApp.[UUID]/Unknown Persona:5:5:2:1:Built bundle record for app
0.843176 LaunchServices cdHashes for [private] = ([3 x cdhash])
0.843210 LaunchServices bundle record for [private] will be registered trusted.
0.843777 MobileInstallation com.apple.Safari.WebApp.[UUID]/Unknown Persona:5:4:2:1:_LSServerRegisterItemInfo result = 0
0.846069 networkserviceproxy NSPServer: 0x13a7069e0 received app installation event
0.846102 networkserviceproxy NSPServer: 0x13a7069e0 apps installation/uninstallation event was received with user info: { bundleIDs = ("com.apple.Safari.WebApp.[UUID]"); isPlaceholder = 0;}

As this is a TemplateApp and lacks its own executable code, LaunchServices then substitutes the executable code in Web App.app contained in the Cryptex
0.849391 LaunchServices LAUNCH: Substituting executable for [private], new executable bundleID is com.apple.Safari.WebApp path=[private]

RunningBoard now takes charge of the app launch, and builds the dictionary that forms the app’s job description. That is long and detailed, so I only include a short excerpt of the more interesting contents, including the arguments passed to run the app
0.851739 RunningBoardServices Sending launch request: RBSLaunchRequest| app[application.com.apple.Safari.WebApp.[UUID].15943.15948(501); "LS launch com.apple.Safari.WebApp.[UUID]”]
0.852735 RunningBoard Checking PreventLaunch: global:0 exPath:/System/Volumes/Preboot/Cryptexes/App/System/Library/CoreServices/Web App.app/Contents/MacOS/Web App predicates:(null) allow:(null)
0.852886 RunningBoard 'app[application.com.apple.Safari.WebApp.[UUID].15943.15948(501)]’ Constructed job description:
dictionary: 0x1461723e0 { count = 23, transaction: 0, voucher = 0x0, contents =
"CFBundleIdentifier" => string: 0x146173eb0 { length = 60, contents = "com.apple.Safari.WebApp.[UUID]" }
"_ResourceCoalition" => string: 0x146153010 { length = 94, contents = "app[application.com.apple.Safari.WebApp.[UUID].15943.15948(501)]” }
[…]
"ProgramArguments" => array: 0x146115320 { count = 8, capacity = 8, contents =
0: string: 0x14612dc20 { length = 100, contents = "/System/Volumes/Preboot/Cryptexes/App/System/Library/CoreServices/Web App.app/Contents/MacOS/Web App" }
1: string: 0x14616af30 { length = 12, contents = "--bundlepath" }
2: string: 0x1461738b0 { length = 92, contents = "/Users/howardoakley/Applications/The Eclectic Light Company – Macs, painting, and more.app" }
3: string: 0x14611b990 { length = 18, contents = "--sandboxextension" }
4: string: 0x146176930 { length = 263, contents = “[…];com.apple.app-sandbox.read;[…];/users/howardoakley/applications/the eclectic light company – macs, painting, and more.app" }
5: string: 0x14616c410 { length = 18, contents = "--bundleidentifier" }
6: string: 0x14610da00 { length = 60, contents = "com.apple.Safari.WebApp.[UUID]" }
7: string: 0x1461519f0 { length = 2, contents = "--" }
}
"Program" => string: 0x14612e600 { length = 100, contents = "/System/Volumes/Preboot/Cryptexes/App/System/Library/CoreServices/Web App.app/Contents/MacOS/Web App" }
}

A key part of the security system, AMFI (Apple Mobile File Integrity), notes this launch violates its constraints, but allows it to proceed
0.858931 AMFI: Launch Constraint Violation (not enforcing), error info: c[6]p[2]m[3]e[255], (System process was launched as an application unexpectedly) launching proc[vc: 1 pid: 856]: /System/Volumes/Preboot/Cryptexes/App/System/Library/CoreServices/Web App.app/Contents/MacOS/Web App, launch type 3, failure proc [vc: 1 pid: 1]: /sbin/launchd

LaunchService then proceeds with the launch, reporting the app info
0.861904 launchservicesd -- Creating new LSApplication object for pending application (which we've gotten notified to expect), asn=[ 0x0-0x43043] pid=856 info={ "ApplicationType"="Foreground", , "CFBundleExecutablePath"="/System/Volumes/Preboot/Cryptexes/App/System/Library/CoreServices/Web App.app/Contents/MacOS/Web App", , "CFBundleExecutablePathDeviceID"=16777233, , "CFBundleExecutablePathINode"=15948, , "CFBundleIdentifier"="com.apple.Safari.WebApp.[UUID]", , "CFBundleName"="Web App", , "CFBundlePackageType"="APPL", , "LSASN"=ASN:0x0-43043:, , "LSAuditToken"=[…], , "LSBundlePath"="/Users/howardoakley/Applications/The Eclectic Light Company – Macs, painting, and more.app", , "LSBundlePathDeviceID"=16777233, , "LSBundlePathINode"=56188, , "LSDisplayName"="The Eclectic Light Company – Macs, painting, and more", , "LSExecutableFormat"="LSExecutableMachOFormat", , "LSFrontApplicationSeed"=112, , "LSLaunchDoNotBringFrontmost"=false, , "LSLaunchEventRecordTime"=799371050000, , "LSLaunchTime"=now-ish 2023/10/02 19:35:00, , "LSLaunchedByLau[…]

So far, there has been little involvement by security systems, and no sign of Gatekeeper scans, for instance. The launching web app is now given its app sandbox
0.952533 secinitd Web App[856]: AppSandbox request
0.952771 secinitd Web App[856]: root path for bundle "[private]" of main executable "[private]"
0.958104 AppSandbox initializing ACL for container ~/Library/Containers/com.apple.Safari.WebApp/Data with identifier "com.apple.Safari.WebApp" and anchor apple

Once the web app is running, though, it appears to violate two of Bastion’s security behavioural rules, 1 and 4. Those report processes that access ~/Library/Safari/ (rule 1) and may sniff network packets (rule 4)
1.210392 syspolicyd Got bastion violation 1 path [private] responsible path: [private]
Although those might just be coincidental, because the key information from this log entry has been censored.

Finally, AppKit decides to write the app’s persistent state to the com.apple.Safari.WebApp container in the user’s Home Library
1.278612 AppKit ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to /Users/howardoakley/Library/Containers/com.apple.Safari.WebApp/Data/tmp/com.apple.Safari.WebApp.savedState