Series · 1 partsiOS Security BoundariesPart 1 · You are here
  1. 1The Extension Was Sandboxed. The Shared Container Still Crossed the Boundary.You are here

The iOS boundary in 60 seconds

An iOS application can contain several independently executed targets: the visible application, a Share extension, a notification service extension, a widget, an App Clip, a File Provider extension, or another system-defined extension point. These targets are signed together and delivered as one product, but they do not become one runtime process or one private container.

That separation is a strong default. The system launches an extension only for its defined task and mediates the request from the host application. The containing app and the extension cannot simply open each other’s private application containers.

App Groups deliberately create an exception. Targets that carry the same App Group entitlement can read and write a shared on-device container, shared preferences, and—in supported configurations— shared keychain items. The design is useful, but its security meaning is often overstated:

The entitlement proves that a signed target may reach the shared namespace. It does not prove that every value written there is trustworthy for every consumer.

A pentest therefore needs to map four things together: who can produce shared state, what can be stored, when another target consumes it, and which effect that consumer can produce. The bug is usually not “the sandbox failed.” The bug is that a valid sharing mechanism silently became an authorization mechanism.

One product, several security principals

Apple’s extension model treats each extension as a separate binary. A host app invokes the extension through a system-defined extension point; the containing app may not even be running. If the developer needs indirect data exchange, the product explicitly opts into an App Group.

That creates a graph rather than a hierarchy:

host applicationSYSTEM REQUEST app extensionSEPARATE SANDBOX containing appSEPARATE SANDBOX App GroupFILES · DEFAULTS · IPCSHARED WRITE SURFACE shared keychain itemONE ACCESS GROUP consumer operationNETWORK · ACCOUNT · FILE Signing grants membership. The consumer still owns semantic validation and authorization.
The extension boundary remains intact. The App Group is an intentional bridge whose data still needs a trust model.

The host application, containing application, and extension are different roles. “Host” means the application currently presenting or invoking the extension. “Containing app” means the application bundle used to deliver it. Confusing those roles leads to bad test cases because the extension receives host-controlled input while sharing selected state with its containing application.

What App Groups provide—and what they do not

The com.apple.security.application-groups entitlement lists the groups a target may join. Apple registers iOS App Group identifiers so they are unique. Membership can provide:

  • a shared on-volume container;
  • a shared UserDefaults suite;
  • a location for extension-aware background transfer artifacts;
  • supported interprocess communication mechanisms;
  • use of the App Group identifier as a keychain access group.

Those guarantees are meaningful. A random third-party application cannot invent the entitlement at runtime and enter another team’s registered group. Code signing and provisioning establish which targets may join.

But group membership is intentionally coarse. If five targets share the same group, the platform does not know that only the main app should update account.json, only the notification extension should write pending-message.plist, or the widget should receive a read-only projection. At the file-system level, each entitled member may have read/write access to the shared container.

This is where the application must add a semantic policy:

Platform factApplication question that remains
The target is signed and provisioned for the groupIs this target an accepted producer for this object?
The shared file is inside the group containerIs its format, owner, version, size, and state transition valid?
The keychain item is in a reachable access groupShould this target receive the same credential or key material?
Data Protection encrypts the file at restIs it available in the correct device-lock state and to the correct workflow?
A background transfer wrote into the groupWas the response authenticated and bound to the initiating account and request?

Encryption, sandboxing, and code signing remain effective even when the application makes the wrong decision about shared data. A report should identify the failed application invariant instead of claiming that the platform control was bypassed.

Begin with the signed target inventory

The most useful first artifact is not a list of URL schemes. It is a target-to-entitlement matrix. For an authorized review of a customer-provided .ipa, extract the archive in an isolated workspace and inventory every executable before launching the app.

Read-only examples:

unzip -q CustomerApp.ipa -d extracted
find extracted/Payload -type d -name '*.appex' -print
find extracted/Payload -type f -perm -111 -print
codesign -d --entitlements :- extracted/Payload/CustomerApp.app
codesign -dvvv extracted/Payload/CustomerApp.app

Repeat entitlement inspection for each .appex and embedded executable. Preserve the original IPA hash, the extracted target path, and the signing output. Do not infer an extension’s entitlements from the main application.

Record at least:

TargetExtension pointApp GroupsKeychain groupsProtected resourcesInputsOutputs
Main appapplicationexact identifiersexact identifiersdeclared capabilitiesuser, links, networkfiles, secrets, requests
Share extensionshareexact identifiersexact identifiershost-provided itemsextension contextshared drafts
Notification servicenotification serviceexact identifiersexact identifiersnetwork if usedAPNs payloadrendered content, cache
Widgetwidgetexact identifiersexact identifierstimeline datashared snapshotUI projection
File Providerfile providerexact identifiersexact identifiersdocument domainremote/local itemsshared file state

The goal is to reveal asymmetry. A small extension may parse hostile content but share a writable container with a main app that holds authenticated network sessions, private user data, or stronger business capabilities.

Evidence plate showing a synthetic IPA subset with a main app, Share extension, and Notification Service as separate signed targets under one App Group.
Evidence / 01 Three executables join one App Group namespace. Inventory each target’s entitlements and effects separately.

Treat every shared object as a protocol

A shared plist or SQLite row may look like storage, but between independently scheduled processes it is a protocol. It has producers, consumers, a schema, ordering rules, and failure behavior.

A trustworthy shared object normally needs:

  • a strict schema and explicit version;
  • bounds on strings, collections, files, and decoded payloads;
  • an account or tenant binding that is not taken only from the object itself;
  • a state-machine transition the consumer can verify;
  • safe path construction rooted in the expected container;
  • atomic replacement or transaction semantics;
  • rejection of symlinks or file types the workflow does not require;
  • a freshness or replay rule where old intent must not be processed twice;
  • provenance strong enough for the consequence of the operation.

Consider an extension that writes this conceptual job:

{
  "operation": "upload",
  "account": "A-104",
  "relativePath": "Exports/report.pdf",
  "destination": "case-781"
}

The main app must not interpret “the file is in our App Group” as authorization to perform the upload. It should derive the active account from trusted session state, resolve the path beneath an expected root, reject unsupported object types, bind the destination to policy, and make replay visible. The shared object may carry intent; it should not manufacture authority.

Keychain sharing is a capability decision

On iOS, an app can reach its own keychain items and items in access groups granted through its signed entitlements. A keychain item belongs to one access group. If a target does not possess the required entitlement, the platform rejects access.

This is a strong boundary, but the group design decides its breadth. Adding the same access group to a widget, notification extension, App Clip, and main application may give every target the technical ability to query items in that group. The review should ask whether they need the same secret—not only whether Xcode made the capability available.

Separate these questions:

  1. Reachability: which signed targets can address the access group?
  2. Item placement: which specific items were written into that group rather than a private group?
  3. Accessibility: under which device and authentication conditions can the item be returned?
  4. Purpose: does the target need the raw credential, or only a narrow derived result?
  5. Revocation: what happens to shared credentials after logout, account switch, app removal, or extension disablement?

kSecAttrAccessible and access-control flags govern when an item is available relative to device state and user authentication. They do not reduce an unnecessarily broad set of entitled targets. Conversely, a narrow access group does not repair an item that remains available in an inappropriate lock state. Both dimensions belong in the evidence.

Evidence plate comparing App Group membership and keychain reachability across a main app, Share extension, and Notification Service.
Evidence / 02 App Group membership is shared; keychain and session strength are not. Map producers and privileged consumers separately.

Lifecycle is part of the attack surface

Extensions are short-lived and can be terminated after they complete a request. The containing app and extension can also access the shared container at different times. That creates states that a purely interactive test misses:

  • the extension writes while the main app is suspended;
  • the main app upgrades a schema while an older extension artifact remains;
  • an account changes between production and consumption of a queued job;
  • a background URL session completes after logout;
  • two processes update the same shared preferences or database record;
  • one app in the group is removed while another remains installed;
  • protected data becomes unavailable or available across device lock transitions.

Test these as state transitions, not as random race attempts. Use synthetic accounts and non-sensitive fixtures on a customer-approved test device. Capture the precondition, producer, object hash, timestamps, lock state, consuming process, and resulting effect.

extension inputUNTRUSTED INTENT shared objectVERSION · HASHATOMIC STATE consumer gatesSCHEMA · ACCOUNTPATH · FRESHNESSSTATE · POLICY bounded effectAUTHORIZED reject + recordNO SIDE EFFECT producer · object hash · reason · effect
The consumer—not the shared container—must convert shared intent into a bounded, attributable effect.
Evidence plate showing ALLOW when a shared upload job matches the active session account and DENY when the account field is spoofed.
Evidence / 03 Same App Group queue, different consumer decisions: session-bound account allows a bounded effect; spoofed account is denied with no upload.

A defensible test sequence

1. Establish scope and test planes

Use only a customer-provided build, approved bundle identifiers, test accounts, and a designated test device. Decide which observations are possible through static review, Simulator, a normal physical device, and instrumented or research devices. Do not present Simulator behavior as proof of a hardware- backed keychain or physical-device Data Protection outcome.

2. Inventory targets and effective entitlements

Build the target matrix from signed artifacts. Compare provisioning intent with the entitlements on each executable. Highlight every shared App Group and keychain access group, then list all members.

3. Classify shared objects

For each shared file, database, preference suite, background session, and keychain item, record:

  • producer targets and consumer targets;
  • whether the data is secret, authoritative, or merely a cache;
  • expected schema and size;
  • account and device-state binding;
  • the effect of corruption, replacement, replay, deletion, or delay.

4. Exercise one controlled mutation at a time

Begin with harmless synthetic values: malformed versions, oversized but bounded fields, a stale account identifier, an unexpected relative path, or replayed job identifier. Never test destructive operations against real user data. Observe whether the consumer rejects before producing a network, account, file, or cryptographic effect.

5. Repeat across lifecycle boundaries

Run the same bounded case after suspension, relaunch, device lock/unlock, account switch, and background completion where relevant. The conclusion should name the exact state transition that changed behavior.

6. Prove the effect and the fix

A writable shared file alone is expected platform behavior, not a vulnerability. Evidence must connect the controlled mutation to an unauthorized or unsafe consumer effect. After remediation, repeat the same case and show rejection, attribution, and absence of the prior side effect.

Common false positives

“The extension can read the App Group container”

That is the declared purpose of group membership. It becomes a finding only when membership is broader than required, sensitive objects are placed in the wrong namespace, or a consumer trusts another member’s data beyond its intended authority.

“The shared file is not encrypted by the app”

iOS Data Protection already provides platform encryption classes for files and databases. The useful question is which class applies, when data is available, whether backups or synchronization matter, and whether an authorized target can misuse plaintext after the system returns it.

“A keychain query succeeds from two targets”

Success may be the designed effect of a shared access group. Establish the item, group, target set, accessibility, user-presence requirement, and business need before assigning impact.

“The extension bundle is inside the app, so it is trusted input”

Its code is part of the signed product, but the extension may parse data supplied by another host app, a notification payload, a document provider, or network content. Signed code can still become a producer of attacker-influenced state.

“Jailbreak-only access proves production exploitability”

A research device can reveal implementation details and help create hypotheses. It does not prove that an unprivileged production application can cross the same boundary. Report the observation plane and the missing prerequisite explicitly.

Evidence matrix

ClaimMinimum evidenceStronger evidenceDo not claim from this alone
A target belongs to an App GroupEffective signed entitlement from that targetProvisioning comparison plus successful controlled container accessThat every shared object is sensitive or unsafe
An extension controls a shared fieldControlled synthetic write and object diffProducer trace with object hash and timestampsThat a privileged effect occurred
A consumer trusts unsafe shared stateRepeatable mutation changes a consumer decisionTrace joining producer, object, consumer, and effectSandbox escape or arbitrary code execution
Keychain scope is broader than requiredAccess-group membership and reachable test itemTarget-by-item necessity analysis and lock-state resultsExtraction by unrelated third-party apps
Data Protection behavior is weakPhysical-device result with documented lock state and file classRepeated cold/locked/unlocked measurementsHardware-backed behavior from Simulator alone
Background completion crosses logoutSynthetic transfer initiated before logout and consumed afterwardAccount-bound request/response trace and remediation replayExposure of real customer data without evidence
The fix worksOriginal case rejected before side effectNegative tests, telemetry, and regression coverageThat all shared-container risks are eliminated

What remediation should look like

The strongest fix usually reduces both membership and meaning:

  1. Give each target only the App Groups and keychain groups it needs.
  2. Keep credentials private unless another target truly needs the raw item.
  3. Split shared namespaces by purpose instead of placing every target in one universal group.
  4. Treat group files as untrusted protocol messages: strict schema, bounds, version, account binding, path confinement, freshness, and atomic state transitions.
  5. Let a higher-authority consumer derive authorization from its own trusted state.
  6. Store the smallest possible projection for widgets and extensions.
  7. Clear or rotate shared state on logout, account switch, and membership changes.
  8. Log enough identifiers to join producer, object, decision, and effect without recording secrets.
  9. Test lock-state and background-session behavior on a physical device.
  10. Preserve a regression fixture for every confirmed boundary failure.

Sometimes the correct design is not another validation check. It is removing the shared secret, replacing a writable job file with a narrow brokered operation, or giving an extension a read-only projection that cannot authorize anything.

My conclusion

iOS sandboxing answers an important question: what resources may this executable reach? App Groups and keychain access groups answer another: which signed targets may intentionally share selected resources? Neither mechanism knows the application’s business invariant.

The defensible security question is therefore:

Can a lower-context producer place data into a shared namespace that a different target converts into a higher-consequence effect without independently validating provenance, state, and authorization?

That question scales beyond one extension type. It applies to widgets, notifications, App Clips, File Providers, background transfers, and future Apple platform targets. It also gives this research series a stable method: map the executable, map its entitlements, follow the shared object, and prove the final effect.

Sources

Sources & freshness

How current is this note?

Sources checkedAugust 29, 2026

The latest source-review, content-update, or publication date is shown.

ReviewPublic sources reviewed

Primary public records were checked. Environment-specific behavior remains outside the claim unless separately reproduced.