Keep an iOS app's storage, links and web bridge in bounds
Requires a Mac with Xcode and an iOS Simulator runtime. The project uses Xcode's
folder-synchronized groups and Swift 6 language mode, so it needs Xcode 16 or later.
Verified with Xcode 27.0 (Swift 6.4) on the iOS 27.0 and iOS 26.5 Simulators
(iPhone 17) on 11 October 2026. The Simulator needs no signing identity, team
or Apple developer account.

Reference solution: not included here, so the exercise does not hand you the answer.
Attempt the repair first. Then download ios-platform-boundaries-solution.zip from the
project card on https://softwaresecurity.com/labs/#project-ios-platform-boundaries and
extract it into this folder; it adds a reference/ folder without changing your files,
and the reference commands below need it.

Objective: a small SwiftUI banking app keeps its refresh token in UserDefaults,
turns App Transport Security off, pays invoices straight from a link and lets any
web page drive its WKWebView bridge. Repair the four files in exercise/ until the
XCTest suite passes on a Simulator.
Prerequisites: Swift, Info.plist keys, and how iOS delivers URLs to an app.

Files
  BankBoundaries.xcodeproj  the app target and its hosted XCTest target
  App/                      app shell: SwiftUI views, AppModel, PaymentClient (do not edit)
  exercise/                 what you repair: SessionStore.swift, LinkHandling.swift,
                            HelpBridge.swift and Info.plist
  Tests/                    the XCTest suite (19 tests)
  run-tests.sh              builds, tests and prints each failure
The build setting EXERCISE_MODULE (default exercise) picks exercise/ or reference/;
everything else is shared. The tests run inside the built app, so they read its real
Info.plist, Keychain and WebKit.

1. Pick a Simulator and run the suite:
     xcrun simctl list devices available
     sh run-tests.sh
   The default destination is 'platform=iOS Simulator,name=iPhone 17,OS=27.0'. For
   another one, set DESTINATION, for example:
     DESTINATION='platform=iOS Simulator,name=iPhone 17,OS=26.5' sh run-tests.sh
   Expect "exercise: 7 passed, 11 failed, 1 skipped" and the failing tests with their
   messages. The first run builds the app and boots the Simulator; allow a few minutes.
   You can also open BankBoundaries.xcodeproj in Xcode and press Command-U.

2. Storage (exercise/SessionStore.swift, StorageTests). Apple's UserDefaults page:
   "The defaults system stores information on disk in an unencrypted format. Store
   personal or sensitive information in the person's Keychain instead." The file also
   goes into backups and onto a new phone with them. Store the token as a
   kSecClassGenericPassword item with kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
   which is readable only while the device is unlocked and never leaves this device,
   and don't make it synchronizable. Update the item if it exists, otherwise add it,
   and delete it at sign-out. Write the cached statement to Application Support with
   .completeFileProtection and set isExcludedFromBackup.

3. Transport (exercise/Info.plist, TransportTests). NSAllowsArbitraryLoads turns ATS
   off for every host. Remove the NSAppTransportSecurity dictionary. The test reads the
   Info.plist inside the built app, then sends a plain http:// request, which ATS must
   refuse with NSURLErrorAppTransportSecurityRequiresSecureConnection (-1022) before
   any connection is made. If one server truly can't serve HTTPS yet, add an
   NSExceptionDomains entry for that host alone, never a global switch.

4. Links (exercise/LinkHandling.swift, LinkTests). Any app can register bankapp://,
   and anyone can send a universal link. Parse with URLComponents and compare fields:
   accept exactly https://bank.example.com/invoices/<id> and bankapp://invoices/<id>,
   with an id of 8 to 20 characters A-Z or 0-9, and no user, port, query or fragment.
   Match the percent-encoded path, so %2F and %0A can't become separators or newlines.
   A link may only open the review sheet; never pay from a link. Paying happens when
   the user taps Pay there (AppModel.confirmReviewedPayment), and the server must still
   check that this user may pay this invoice.

5. Web bridge (exercise/HelpBridge.swift, WebBridgeTests). Every frame can post to a
   script message handler, including an embedded third-party frame and any page the
   web view navigates to. Act only when message.frameInfo.isMainFrame is true and
   message.frameInfo.securityOrigin is https, help.bank.example.com, port 0 (WebKit's
   value for the scheme's default port). Accept a fixed set of actions; never open a
   URL that the page chose. The tests load pages with loadHTMLString(_:baseURL:), so
   each page gets the base URL's origin without any network access.

6. Run sh run-tests.sh until it reports "exercise: 18 passed, 0 failed, 1 skipped".
   Then extract the solution and compare:
     EXERCISE_MODULE=reference sh run-tests.sh
   Expect "reference: 18 passed, 0 failed, 1 skipped". A different repair can still
   be correct: the tests define the required behavior.

The skipped test: the Simulator ignores Data Protection. A file written with
.completeFileProtection still reports completeUntilFirstUserAuthentication there, so
testCachedStatementHasCompleteFileProtectionOnADevice runs only on a device. To run
it, set your team under Signing & Capabilities for both targets, connect a device
that has a passcode, choose it as the run destination and press Command-U. It was not
run on a device for this project.

MASVS v2.1.0 controls and MASTG v2.0.0 tests (IDs checked on mas.owasp.org)
  StorageTests    MASVS-STORAGE-1, MASVS-STORAGE-2; MASTG-TEST-0300, 0301 and 0302
                  (MASWE-0001), MASTG-TEST-0299 (Data Protection classes) and
                  MASTG-TEST-0215 (MASWE-0006, backups)
  TransportTests  MASVS-NETWORK-1; MASTG-TEST-0322 (MASWE-0026), reading the built
                  Info.plist as in MASTG-TECH-0153 and MASTG-TECH-0155
  LinkTests       MASVS-PLATFORM-1, MASVS-CODE-4; MASTG-TEST-0395 (universal link
                  handlers) and MASTG-TEST-0370 (custom URL scheme handlers), MASWE-0029
  WebBridgeTests  MASVS-PLATFORM-2; MASTG-TEST-0376 (MASWE-0034) and MASTG-BEST-0058
                  (check the message's frame and origin)

Keep: the failing output from step 1, your diff of exercise/, and the passing output.

Scope: real Keychain, App Transport Security, URL parsing and WebKit behavior in the
Simulator. Not covered: universal link association (the Associated Domains
entitlement and apple-app-site-association need a signed build and a real domain),
App Attest, identity pinning, Data Protection enforcement (device only) and Keychain
items that survive deleting the app. PaymentClient stands in for a server that must
authorize every payment itself; a check in the app never replaces it.

Sources (checked 11 October 2026):
https://developer.apple.com/documentation/security/restricting-keychain-item-accessibility
https://developer.apple.com/documentation/security/ksecattraccessiblewhenunlockedthisdeviceonly
https://developer.apple.com/documentation/foundation/userdefaults
https://developer.apple.com/documentation/security/preventing-insecure-network-connections
https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app
https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app
https://developer.apple.com/documentation/webkit/wkscriptmessage/frameinfo
https://developer.apple.com/documentation/webkit/wksecurityorigin
https://support.apple.com/guide/security/data-protection-classes-secb010e978a/web
https://mas.owasp.org/MASVS/
https://mas.owasp.org/MASTG/tests/ios/MASVS-STORAGE/MASTG-TEST-0300/
https://mas.owasp.org/MASTG/tests/ios/MASVS-NETWORK/MASTG-TEST-0322/
https://mas.owasp.org/MASTG/tests/ios/MASVS-PLATFORM/MASTG-TEST-0395/
https://mas.owasp.org/MASTG/tests/ios/MASVS-PLATFORM/MASTG-TEST-0376/
