[GH-ISSUE #4305] [Feature] Report stapled notarization tickets and quarantine attributes without contacting Apple #1710

Open
opened 2026-10-16 21:09:18 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 16, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4305

Before submitting

  • I searched the open and closed issues and pull requests for a related proposal.
  • I understand the contribution scope must be reviewed before integration; open questions are welcome here.
  • This request does not include credentials, private samples, or sensitive target data.

Area

Evidence, artifacts, or reconstruction

Current limitation

Before running or triaging a downloaded Mac app, disk image or installer package, an analyst wants two local facts. Does the item carry a stapled notarization ticket? Is it marked with the com.apple.quarantine or com.apple.provenance extended attributes that Gatekeeper uses? REA reports neither, and the only Apple tool that checks a staple (stapler validate) contacts Apple's ticketing service. This request asks REA to report staple state as stapled, not stapled, not applicable or unknown, and to report those extended attributes as observed. Nothing is sent over the network, and REA never concludes "notarized" or "not notarized" from a staple alone.

Observations use REA 6.5.0 (npm rea-agents@6.5.0, tag rea-agents-6.5.0, commit 3b79af28e80785afce9f5998d8f03289402a1847) on macOS 27.0.1 (26A434) arm64 with Xcode 27.0 (27A266a). The cited source files are unchanged on main at d52a488.

  • A self-built disk image (mkdir dmgsrc && cp ./lc dmgsrc/ && hdiutil create -srcfolder dmgsrc -format UDZO fx.dmg, where lc is any clang output) marked with xattr -w com.apple.quarantine "0081;00000000;rea-fixture;" fx.dmg:
    • rea inspect-signature ./fx.dmg fails with capability_unavailable: "Active artifact is not Mach-O; its container-level signature layer is not evaluated by codesign." (NativeMacOSProvider.ts L573-L580).
    • rea inspect-artifact ./fx.dmg --json returns a DMG inventory with no staple or extended-attribute information. No REA source at the tag reads extended attributes (no xattr or getxattr hits under src/).
  • For an app bundle, inspect_signature inspects a byte copy of the main executable and always states "filesystem-bound signing context and trust validation are not examined" (L632). A ticket stapled to the bundle is therefore never seen.
  • The manual route has its own traps. On the test host:
    • xcrun stapler validate printed "does not have a ticket stapled to it" (exit 65) for stock /System/Applications/Stickies.app, for stock /bin/ls and for the self-built disk image. For /bin/ls, Apple says stapling is impossible, so "not applicable" is the accurate state.
    • For a ZIP it printed "Stapler is incapable of working with ZIP archive files." (exit 66).
    • man stapler says "stapler requires internet access to retrieve tickets when stapling or validating", and that validate compares the stapled ticket "to the latest ticket retrieved from the ticketing service".
  • A third-party app bundle that stapler validate accepted on the test host contains a top-level Contents/CodeResources file, separate from Contents/_CodeSignature/CodeResources, whose bytes begin with ASCII s8ch. Stickies.app has no such file. Apple's documentation in the cited pages does not describe where or how a ticket is stored, so this location and prefix are observations, not a documented format.
  • Files created from the shell on the test host (compiler output and the disk image) carried an 11-byte com.apple.provenance attribute. xattr -d and -wx did not change it.

Use cases

No response

Desired outcome and success criteria

For an app bundle, a disk image, a flat installer package, a ZIP or a bare Mach-O selected as the target, REA reports:

  • Staple state.
    • stapled: a ticket was found; report its location in the target, size and SHA-256.
    • not stapled: a format that supports stapling, with no ticket found.
    • not applicable: a format Apple says cannot carry a staple (ZIP archives and standalone binaries).
    • unknown: REA cannot establish ticket detection for the format, or the read failed; the reason is given.
    • Detection rules are established against real stapled samples. For a container whose ticket location the implementation has not verified, the state is unknown, not not stapled.
  • No verdicts beyond the staple. The result never says "notarized" or "not notarized". A limitation states that a missing staple does not show the item was not notarized (the ticket may exist only on Apple's servers), and that a present staple's validity and revocation state were not checked.
  • No network by default. No stapler validate, CloudKit, notary API or other network request runs by default. Any network check would be a separate, explicitly requested operation and is not part of this request.
  • Extended attributes. On the target root (and, for a bundle, also the main executable), report the presence and exact raw value of com.apple.quarantine (as text and bytes) and com.apple.provenance (as bytes). absent means the attribute is not set; unknown means it could not be read.
  • Quarantine decoding. Decode quarantine only through Apple's documented quarantine-properties interface (agent name, agent bundle identifier, timestamp, type, origin URL and data URL keys). Undocumented parts of the raw string, and the whole provenance value, are kept raw and labelled undocumented.
  • Untrusted content. Quarantine and provenance values, including any origin URL, are marked as untrusted target content.
  • Identity. Reads are bound to the same target identity REA already verifies for the bytes it inspects, so attributes and tickets are attributed to the analyzed item.

Acceptance criteria

  • Self-built disk image fx.dmg (above), after xattr -w com.apple.quarantine "0081;00000000;rea-fixture;" fx.dmg: staple state is not stapled; com.apple.quarantine is reported with raw value 0081;00000000;rea-fixture; and decoded agent name rea-fixture (matching URLResourceValues.quarantineProperties on the same file); provenance matches xattr -px com.apple.provenance fx.dmg (present with the same bytes, or absent).
  • Self-built Mach-O from clang: staple state is not applicable with the reason "tickets cannot be stapled to standalone binaries", not not stapled.
  • ditto -c -k --keepParent dmgsrc fx.zip: staple state is not applicable (ZIP).
  • Stock /System/Applications/Stickies.app: staple state is not stapled, and the result contains no "not notarized" claim.
  • A Developer ID app that xcrun stapler validate accepts (any notarized, stapled app the verifier owns): staple state is stapled, with a location, size and SHA-256 equal to the ticket file's path, stat size and shasum -a 256, and a limitation that validity and revocation were not checked. On the one stapled sample examined while drafting, that file was Contents/CodeResources; this is an observation from one sample, not a documented location.
  • A file with no quarantine attribute reports com.apple.quarantine as absent.
  • A run with networking disabled produces the same results, and provenance records no network-capable command.

Expected interaction with targets

Read existing files or captured data

Target and scope details (optional)

Affected area

Apple target inspection on macOS: the operation that reports staple state and extended attributes for .app, .dmg, .pkg, .zip and Mach-O targets (a new field in an existing tool or a new operation), its schema, generated tool documentation, and tests with self-built fixtures.

Non-goals

  • Contacting Apple (CloudKit, notary API) to confirm notarization, fetch tickets or check revocation.
  • Gatekeeper or system policy assessment (spctl, syspolicy_check, gktool), XProtect, and App Translocation.
  • Stapling, removing a staple, or adding, changing or removing extended attributes.
  • CodeDirectory posture, entitlement meaning, launch constraints and provisioning profiles (each requested separately).
  • Extracting disk images or packages (open issue #3095 covers DMG extraction).

Verification available so far

I can suggest a sample or manual check

Verification details (optional)

No response

Alternatives considered

  • Run stapler validate. This is authoritative but contacts Apple, conflates "unsupported format" with "not stapled" for bare binaries, and needs macOS.
  • Report only the quarantine attribute. This is simpler, but the staple question is the one analysts most often answer wrongly from a missing file.

Possible direction

No response

References

Apple documentation:

  • Customizing the notarization workflow: "To attach a ticket to your app, bundle, disk image, or flat installer package, use the stapler tool". "While you can notarize a ZIP archive, you can't staple to it directly." "Although tickets are created for standalone binaries, it's not currently possible to staple tickets to them." stapler "uses CloudKit to download tickets".
  • App code signing process (macOS): "The output of Notarization is a ticket, which is stored on Apple servers and can be optionally stapled to the app (by anyone) without invalidating the signature of the developer."
  • Gatekeeper and runtime protection: "Gatekeeper also requests user approval before opening downloaded software for the first time" and "Gatekeeper also tracks the provenance of files written by downloaded software."
  • quarantineProperties: "The quarantine properties as defined in LSQuarantine.h." The keys are documented in Core Services, for example kLSQuarantineAgentNameKey ("The app name of the quarantining agent") and kLSQuarantineOriginURLKey.
  • macOS 27 release notes: "launchd no longer supports loading launchd property list files with the quarantine extended attribute."
  • kSecCodeInfoStapledNotarizationTicket exists in Code Signing Services, with no description in the documentation.
  • The Apple pages cited do not document the raw format of com.apple.quarantine, any part of com.apple.provenance, or where a ticket is stored in a bundle, disk image or package.

REA history:

  • Closed, unmerged PR #934 detected a stapled ticket at Contents/CodeResources (SHA-256 and size; not-applicable for a bare Mach-O) as part of a larger signing-posture change. It recorded that no CI fixture can hold a real staple without Apple notarization. Maintainers: is staple and quarantine reporting still wanted, and should it live in inspect_signature or in artifact inspection?
  • Open issue #382 covers declared quarantine states of the test environment, not of the analysis target.
Originally created by @coygeek on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4305 ### Before submitting - [X] I searched the open and closed issues and pull requests for a related proposal. - [X] I understand the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope) must be reviewed before integration; open questions are welcome here. - [X] This request does not include credentials, private samples, or sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Current limitation Before running or triaging a downloaded Mac app, disk image or installer package, an analyst wants two local facts. Does the item carry a stapled notarization ticket? Is it marked with the `com.apple.quarantine` or `com.apple.provenance` extended attributes that Gatekeeper uses? REA reports neither, and the only Apple tool that checks a staple (`stapler validate`) contacts Apple's ticketing service. This request asks REA to report staple state as `stapled`, `not stapled`, `not applicable` or `unknown`, and to report those extended attributes as observed. Nothing is sent over the network, and REA never concludes "notarized" or "not notarized" from a staple alone. Observations use REA 6.5.0 (npm `rea-agents@6.5.0`, tag `rea-agents-6.5.0`, commit `3b79af28e80785afce9f5998d8f03289402a1847`) on macOS 27.0.1 (26A434) arm64 with Xcode 27.0 (27A266a). The cited source files are unchanged on main at `d52a488`. - A self-built disk image (`mkdir dmgsrc && cp ./lc dmgsrc/ && hdiutil create -srcfolder dmgsrc -format UDZO fx.dmg`, where `lc` is any `clang` output) marked with `xattr -w com.apple.quarantine "0081;00000000;rea-fixture;" fx.dmg`: - `rea inspect-signature ./fx.dmg` fails with `capability_unavailable`: "Active artifact is not Mach-O; its container-level signature layer is not evaluated by codesign." ([NativeMacOSProvider.ts L573-L580](https://github.com/morluto/rea/blob/3b79af28e80785afce9f5998d8f03289402a1847/src/native/NativeMacOSProvider.ts#L573-L580)). - `rea inspect-artifact ./fx.dmg --json` returns a DMG inventory with no staple or extended-attribute information. No REA source at the tag reads extended attributes (no `xattr` or `getxattr` hits under `src/`). - For an app bundle, `inspect_signature` inspects a byte copy of the main executable and always states "filesystem-bound signing context and trust validation are not examined" ([L632](https://github.com/morluto/rea/blob/3b79af28e80785afce9f5998d8f03289402a1847/src/native/NativeMacOSProvider.ts#L632)). A ticket stapled to the bundle is therefore never seen. - The manual route has its own traps. On the test host: - `xcrun stapler validate` printed "does not have a ticket stapled to it" (exit 65) for stock `/System/Applications/Stickies.app`, for stock `/bin/ls` and for the self-built disk image. For `/bin/ls`, Apple says stapling is impossible, so "not applicable" is the accurate state. - For a ZIP it printed "Stapler is incapable of working with ZIP archive files." (exit 66). - `man stapler` says "stapler requires internet access to retrieve tickets when stapling or validating", and that `validate` compares the stapled ticket "to the latest ticket retrieved from the ticketing service". - A third-party app bundle that `stapler validate` accepted on the test host contains a top-level `Contents/CodeResources` file, separate from `Contents/_CodeSignature/CodeResources`, whose bytes begin with ASCII `s8ch`. Stickies.app has no such file. Apple's documentation in the cited pages does not describe where or how a ticket is stored, so this location and prefix are observations, not a documented format. - Files created from the shell on the test host (compiler output and the disk image) carried an 11-byte `com.apple.provenance` attribute. `xattr -d` and `-wx` did not change it. ### Use cases _No response_ ### Desired outcome and success criteria For an app bundle, a disk image, a flat installer package, a ZIP or a bare Mach-O selected as the target, REA reports: - **Staple state.** - `stapled`: a ticket was found; report its location in the target, size and SHA-256. - `not stapled`: a format that supports stapling, with no ticket found. - `not applicable`: a format Apple says cannot carry a staple (ZIP archives and standalone binaries). - `unknown`: REA cannot establish ticket detection for the format, or the read failed; the reason is given. - Detection rules are established against real stapled samples. For a container whose ticket location the implementation has not verified, the state is `unknown`, not `not stapled`. - **No verdicts beyond the staple.** The result never says "notarized" or "not notarized". A limitation states that a missing staple does not show the item was not notarized (the ticket may exist only on Apple's servers), and that a present staple's validity and revocation state were not checked. - **No network by default.** No `stapler validate`, CloudKit, notary API or other network request runs by default. Any network check would be a separate, explicitly requested operation and is not part of this request. - **Extended attributes.** On the target root (and, for a bundle, also the main executable), report the presence and exact raw value of `com.apple.quarantine` (as text and bytes) and `com.apple.provenance` (as bytes). `absent` means the attribute is not set; `unknown` means it could not be read. - **Quarantine decoding.** Decode quarantine only through Apple's documented quarantine-properties interface (agent name, agent bundle identifier, timestamp, type, origin URL and data URL keys). Undocumented parts of the raw string, and the whole provenance value, are kept raw and labelled undocumented. - **Untrusted content.** Quarantine and provenance values, including any origin URL, are marked as untrusted target content. - **Identity.** Reads are bound to the same target identity REA already verifies for the bytes it inspects, so attributes and tickets are attributed to the analyzed item. **Acceptance criteria** - [ ] Self-built disk image `fx.dmg` (above), after `xattr -w com.apple.quarantine "0081;00000000;rea-fixture;" fx.dmg`: staple state is `not stapled`; `com.apple.quarantine` is reported with raw value `0081;00000000;rea-fixture;` and decoded agent name `rea-fixture` (matching `URLResourceValues.quarantineProperties` on the same file); provenance matches `xattr -px com.apple.provenance fx.dmg` (present with the same bytes, or absent). - [ ] Self-built Mach-O from `clang`: staple state is `not applicable` with the reason "tickets cannot be stapled to standalone binaries", not `not stapled`. - [ ] `ditto -c -k --keepParent dmgsrc fx.zip`: staple state is `not applicable` (ZIP). - [ ] Stock `/System/Applications/Stickies.app`: staple state is `not stapled`, and the result contains no "not notarized" claim. - [ ] A Developer ID app that `xcrun stapler validate` accepts (any notarized, stapled app the verifier owns): staple state is `stapled`, with a location, size and SHA-256 equal to the ticket file's path, `stat` size and `shasum -a 256`, and a limitation that validity and revocation were not checked. On the one stapled sample examined while drafting, that file was `Contents/CodeResources`; this is an observation from one sample, not a documented location. - [ ] A file with no quarantine attribute reports `com.apple.quarantine` as `absent`. - [ ] A run with networking disabled produces the same results, and provenance records no network-capable command. ### Expected interaction with targets Read existing files or captured data ### Target and scope details (optional) **Affected area** Apple target inspection on macOS: the operation that reports staple state and extended attributes for `.app`, `.dmg`, `.pkg`, `.zip` and Mach-O targets (a new field in an existing tool or a new operation), its schema, generated tool documentation, and tests with self-built fixtures. **Non-goals** - Contacting Apple (CloudKit, notary API) to confirm notarization, fetch tickets or check revocation. - Gatekeeper or system policy assessment (`spctl`, `syspolicy_check`, `gktool`), XProtect, and App Translocation. - Stapling, removing a staple, or adding, changing or removing extended attributes. - CodeDirectory posture, entitlement meaning, launch constraints and provisioning profiles (each requested separately). - Extracting disk images or packages (open issue #3095 covers DMG extraction). ### Verification available so far I can suggest a sample or manual check ### Verification details (optional) _No response_ ### Alternatives considered - **Run `stapler validate`.** This is authoritative but contacts Apple, conflates "unsupported format" with "not stapled" for bare binaries, and needs macOS. - **Report only the quarantine attribute.** This is simpler, but the staple question is the one analysts most often answer wrongly from a missing file. ### Possible direction _No response_ ### References Apple documentation: - [Customizing the notarization workflow](https://developer.apple.com/documentation/security/customizing-the-notarization-workflow): "To attach a ticket to your app, bundle, disk image, or flat installer package, use the `stapler` tool". "While you can notarize a ZIP archive, you can't staple to it directly." "Although tickets are created for standalone binaries, it's not currently possible to staple tickets to them." `stapler` "uses CloudKit to download tickets". - [App code signing process (macOS)](https://support.apple.com/guide/security/app-code-signing-process-sec3ad8e6e53/1/web/1): "The output of Notarization is a ticket, which is stored on Apple servers and can be optionally stapled to the app (by anyone) without invalidating the signature of the developer." - [Gatekeeper and runtime protection](https://support.apple.com/guide/security/gatekeeper-and-runtime-protection-sec5599b66df/1/web/1): "Gatekeeper also requests user approval before opening downloaded software for the first time" and "Gatekeeper also tracks the provenance of files written by downloaded software." - [quarantineProperties](https://developer.apple.com/documentation/foundation/urlresourcevalues/quarantineproperties): "The quarantine properties as defined in LSQuarantine.h." The keys are documented in Core Services, for example [kLSQuarantineAgentNameKey](https://developer.apple.com/documentation/coreservices/klsquarantineagentnamekey) ("The app name of the quarantining agent") and [kLSQuarantineOriginURLKey](https://developer.apple.com/documentation/coreservices/klsquarantineoriginurlkey). - [macOS 27 release notes](https://developer.apple.com/documentation/macos-release-notes/macos-27-release-notes): "`launchd` no longer supports loading `launchd` property list files with the quarantine extended attribute." - [kSecCodeInfoStapledNotarizationTicket](https://developer.apple.com/documentation/security/kseccodeinfostaplednotarizationticket) exists in Code Signing Services, with no description in the documentation. - The Apple pages cited do not document the raw format of `com.apple.quarantine`, any part of `com.apple.provenance`, or where a ticket is stored in a bundle, disk image or package. REA history: - Closed, unmerged PR #934 detected a stapled ticket at `Contents/CodeResources` (SHA-256 and size; `not-applicable` for a bare Mach-O) as part of a larger signing-posture change. It recorded that no CI fixture can hold a real staple without Apple notarization. Maintainers: is staple and quarantine reporting still wanted, and should it live in `inspect_signature` or in artifact inspection? - Open issue #382 covers declared quarantine states of the test environment, not of the analysis target.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1710
No description provided.