cheat sheet
xattr
Deep-dive on macOS extended attributes — listing, reading, writing, and removing xattrs with xattr(1); the Gatekeeper com.apple.quarantine flag; Spotlight metadata attributes; ACLs via ls -le and chmod +a; preserving attributes on copy with cp -p and rsync -X.
xattr — Extended Attributes on macOS
What it is
xattr(1) is the macOS command-line interface to extended attributes — arbitrary name = value pairs that the filesystem stores alongside a file's data, independently of permissions, ownership, or content. macOS uses xattrs heavily: Gatekeeper attaches com.apple.quarantine to every file downloaded by a browser or AirDrop; Spotlight stores com.apple.metadata:* attributes carrying tags, where-from URLs, and finder labels; resource forks live in com.apple.ResourceFork; and POSIX-extended ACLs are exposed as com.apple.system.Security under the hood. The xattr binary is part of base macOS (/usr/bin/xattr) so no install is needed. Reach for it when downloads won't open ("damaged or unidentified developer"), when you need to inspect Finder tags from a script, or when a cp between filesystems silently dropped metadata your app needs. The Linux counterpart is getfattr / setfattr from the attr package; the underlying syscalls (getxattr, setxattr) are similar but the namespaces differ.
Install
xattr ships with macOS at /usr/bin/xattr — there is nothing to install. The command is a thin Python wrapper around the getxattr(2) / setxattr(2) / removexattr(2) syscalls. On Linux, the closest analogue is getfattr / setfattr from the attr package, but the attribute namespaces (user.*, security.*, trusted.*) differ from macOS's (com.apple.*).
which xattr
xattr -h | head -5
Output:
/usr/bin/xattr
usage: xattr [-lrsvx] file [file ...]
xattr -p [-lrsvx] attr_name file [file ...]
xattr -w [-rsx] attr_name attr_value file [file ...]
xattr -d [-rsv] attr_name file [file ...]
Syntax
The four primary modes are: list (no flag, just a file path), print one attribute (-p), write (-w), and delete (-d). Modifiers like -l (long form: name + value), -r (recurse into directories), -s (follow symlinks), and -x (hex output) layer on top.
xattr [MODIFIERS] FILE [FILE...] # list attribute names
xattr -p [MODIFIERS] ATTR_NAME FILE # print one attribute
xattr -w [MODIFIERS] ATTR_NAME ATTR_VALUE FILE # write
xattr -d [MODIFIERS] ATTR_NAME FILE # delete one
xattr -c FILE # clear all
Output: (none — exits 0 on success)
Essential options
| Option | Meaning |
|---|---|
(no flag) | List attribute names on each file |
-l | Long output — show name and value for each attribute |
-p ATTR | Print the value of a single named attribute |
-w ATTR VAL | Write VAL as the value of ATTR |
-d ATTR | Delete a single named attribute |
-c | Clear all extended attributes on the file |
-r | Recurse into directories |
-s | Operate on the symlink itself, not its target |
-x | Read or write the value as a hex string (for binary data) |
-v | Print the file name before each line (useful with multiple files) |
-h | Show usage |
Why extended attributes exist
Extended attributes are out-of-band metadata: bytes that the filesystem associates with an inode but that are not part of the file's regular content stream. They survive cp -p, rsync -X, and most archive formats that target macOS (zip created by Finder, ditto -ck, tar with the --xattrs flag on GNU systems). They do not survive plain cp, tar, scp/sftp, transfer through email attachments, or upload-then-download via cloud storage without dedicated metadata support.
# Create a file, attach a custom attribute, copy it two ways
touch ~/scratch/demo.txt
xattr -w com.example.author "Alice Dev" ~/scratch/demo.txt
xattr ~/scratch/demo.txt
Output:
com.example.author
# Plain cp drops the xattr by default on some macOS versions; cp -p preserves
cp ~/scratch/demo.txt ~/scratch/copy-plain.txt
cp -p ~/scratch/demo.txt ~/scratch/copy-preserve.txt
xattr ~/scratch/copy-plain.txt; echo "---"; xattr ~/scratch/copy-preserve.txt
Output:
---
com.example.author
Modern macOS (
cpfrom 10.12+) preserves xattrs by default on the same APFS volume but drops them when copying across filesystem types (e.g. APFS → FAT32 USB stick). Always usecp -porrsync -Xif you depend on attribute preservation.
Listing and reading attributes
The bare xattr FILE form lists names only; add -l (lowercase L) for the long form that shows each attribute's value next to its name. For one specific attribute, -p ATTR FILE prints just the value, suitable for capturing into a shell variable.
# A typical downloaded DMG carries quarantine + WhereFrom metadata
xattr ~/Downloads/installer.dmg
Output:
com.apple.metadata:kMDItemWhereFroms
com.apple.quarantine
# Long form — name and value together
xattr -l ~/Downloads/installer.dmg
Output:
com.apple.metadata:kMDItemWhereFroms:
00000000 62 70 6C 69 73 74 30 30 A2 01 02 5F 10 23 68 74 |bplist00..._.#ht|
00000010 74 70 73 3A 2F 2F 65 78 61 6D 70 6C 65 2E 63 6F |tps://example.co|
00000020 6D 2F 64 6F 77 6E 6C 6F 61 64 2F 69 6E 73 74 61 |m/download/insta|
00000030 6C 6C 65 72 2E 64 6D 67 5F 10 1B 68 74 74 70 73 |ller.dmg_..https|
...
com.apple.quarantine: 0083;6650a4c0;Safari;F1A2C8E3-9D4B-4F1E-A2B3-C4D5E6F70811
# Print a single attribute's value
xattr -p com.apple.quarantine ~/Downloads/installer.dmg
Output:
0083;6650a4c0;Safari;F1A2C8E3-9D4B-4F1E-A2B3-C4D5E6F70811
# Hex form — useful for binary plist values like kMDItemWhereFroms
xattr -px com.apple.metadata:kMDItemWhereFroms ~/Downloads/installer.dmg | head -3
Output:
62 70 6C 69 73 74 30 30 A2 01 02 5F 10 23 68 74
74 70 73 3A 2F 2F 65 78 61 6D 70 6C 65 2E 63 6F
6D 2F 64 6F 77 6E 6C 6F 61 64 2F 69 6E 73 74 61
Writing custom attributes
xattr -w ATTR VALUE FILE creates or replaces an attribute. Names should be in reverse-DNS form (com.example.tag, com.acme.build-id) to avoid colliding with Apple's reserved namespaces; macOS will write any name you give it but the system tools assume com.apple.* is owned by the OS.
# Mark a file with build metadata
xattr -w com.example.build-id "v2026.05.25-r42" ~/scratch/demo.txt
xattr -w com.example.author "Alice Dev" ~/scratch/demo.txt
xattr -l ~/scratch/demo.txt
Output:
com.example.build-id: v2026.05.25-r42
com.example.author: Alice Dev
# Binary values — write hex
xattr -wx com.example.checksum "DE AD BE EF 12 34" ~/scratch/demo.txt
xattr -px com.example.checksum ~/scratch/demo.txt
Output:
DE AD BE EF 12 34
[!WARN] Maximum xattr size on macOS APFS is approximately 128 KB per attribute and there is a soft cap on the total xattr storage per inode. Don't use xattrs to stash large blobs; use a sidecar file.
com.apple.quarantine — Gatekeeper's footprint
com.apple.quarantine is the most operationally important xattr on macOS. The OS attaches it to every file that arrives from an "untrusted" source — Safari/Chrome downloads, AirDrop transfers, attachments from Mail, files extracted from a downloaded zip. When you double-click a quarantined .app, .dmg, or .pkg, Gatekeeper intercepts the launch and runs signature/notarization checks; if they fail, you see "macOS cannot verify that this app is free from malware" or "is damaged and cannot be opened". The fix for a binary you know to trust is to remove the attribute.
# Check whether quarantine is set
xattr ~/Downloads/MyApp.dmg
Output:
com.apple.metadata:kMDItemWhereFroms
com.apple.quarantine
# Decode the quarantine value
xattr -p com.apple.quarantine ~/Downloads/MyApp.dmg
Output:
0083;6650a4c0;Safari;F1A2C8E3-9D4B-4F1E-A2B3-C4D5E6F70811
The four semicolon-separated fields are: flags (0083 = "user opened it once"), timestamp (hex epoch seconds — 6650a4c0 = 2024-05-24 UTC), agent name (Safari, Chrome, Mail, Finder), and a UUID that joins to a row in ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2 (a sqlite database).
# Remove quarantine from one file
xattr -d com.apple.quarantine ~/Downloads/MyApp.dmg
# Recursively from an entire .app bundle (common fix for "damaged" apps)
xattr -dr com.apple.quarantine /Applications/MyApp.app
# After removal, Gatekeeper still inspects signature/notarization on first
# launch via spctl; clearing quarantine bypasses *the secondary* prompt only.
spctl --assess --type exec /Applications/MyApp.app
Output:
/Applications/MyApp.app: accepted
[!WARN] Clearing
com.apple.quarantinedoes not disable Gatekeeper —codesign --verifyand notarization checks still run on first launch. If those fail (unsigned binary, broken signature), the app still won't open and the right fix is signing/notarizing it. Seecodesign.
com.apple.macl and com.apple.provenance — SIP-protected xattrs
Modern macOS (Ventura 13+, hardened further in Sequoia 15 and Tahoe 26) attaches two additional security-related xattrs that you'll see in xattr -l output but cannot remove, even as root. Knowing what they are saves a round of "why won't this attribute go away".
| xattr | What it is | Removable? |
|---|---|---|
com.apple.macl | Mandatory Access Control List — a per-file authorization record introduced for TCC (the Privacy framework). Records which apps have been granted access to a file. The most prevalent xattr on a modern Mac; attached to virtually every file, even ones you created locally. | ❌ SIP-protected — xattr -d com.apple.macl … fails with Operation not permitted even with sudo. The only way to strip it is to copy the file to a volume where SIP doesn't apply (an APFS volume that isn't the system volume, or a non-Apple filesystem). |
com.apple.provenance | Provenance Sandbox tracking — an 11-byte identifier attached the first time an unsigned (or non-Apple-signed) app clears Gatekeeper. Lets the system trace which app modified which file, used by malware-behaviour heuristics. | ❌ Normally SIP-protected on Tahoe 26; behaviour is the same as com.apple.macl for removal purposes. |
# Both attributes are visible to everyone; the values are opaque to humans.
xattr -l ~/Documents/notes.md | head -20
Output:
com.apple.macl:
00000000 04 00 71 9E 17 21 6B 8B 4C 23 80 9D 5A 4F 0E 6C |..q..!k.L#..ZO.l|
00000010 E2 1B 6D 7A 1F 8E A4 31 2C 5C 9F 6D 70 1C 8B A2 |..mz...1,\.mp...|
...
com.apple.provenance:
00000000 E5 1A 3C 7B 9D 02 4E F6 88 14 71 |..<{..N...q|
# Trying to delete returns Operation not permitted even with sudo
sudo xattr -d com.apple.macl ~/Documents/notes.md
Output:
xattr: [Errno 1] Operation not permitted: '/Users/alice/Documents/notes.md'
If
com.apple.maclis actually causing a permission problem (a file inheriting an MACL entry that doesn't match the current process), the workaround iscp -p(orditto) the file to a scratch location on an external volume, delete the original, then move the copy back — the MACL entry isn't preserved through the round-trip because the target volume has no MACL system.
xattr flags — XATTR_FLAG_ONLY_SAVING (macOS 15+)
macOS Sequoia 15.0 introduced a new xattr flag (separate from the value): XATTR_FLAG_ONLY_SAVING (single character X in the long-form output). Attributes carrying this flag are preserved only during save operations and Time Machine copies — they are deliberately dropped by regular cp, rsync, ditto, and most third-party file managers. The intent is to mark attributes that are useful for backup/restore round-trips but should not propagate when a user copies the file elsewhere. You'll see the flag in xattr -l -v output as a separator like name#X: instead of name:.
# Attributes carrying the X flag are visible like this
xattr -l ~/Library/Mobile\ Documents/com~apple~CloudDocs/some-file 2>/dev/null | head -5
Output:
com.apple.cloud.lastusedate#PS:
00000000 ...
com.apple.metadata:com_apple_backup_excludeItem#S:
00000000 ...
The single-letter codes after # (P, S, X, etc.) are the flag set. Modifying or removing those attributes requires the same kind of permission as their value — xattr -d works for unflagged attributes, but flagged ones often refuse silently or with a permission error.
com.apple.metadata:* — Spotlight & Finder tags
Spotlight's index is built from Core Data, but a handful of metadata fields are stored on the file as xattrs so they survive being moved between volumes. The most useful are kMDItemWhereFroms (the download URL or sender), _kMDItemUserTags (Finder colour tags), and kMDItemFinderComment (the Finder "Get Info" comment).
# Where did this file come from?
xattr -p com.apple.metadata:kMDItemWhereFroms ~/Downloads/installer.dmg | \
plutil -convert json -o - -
Output:
["https://example.com/download/installer.dmg","https://example.com/downloads/"]
# Read Finder colour tags (a binary plist of strings)
xattr -p com.apple.metadata:_kMDItemUserTags ~/Documents/report.pdf | \
plutil -convert json -o - -
Output:
["Important\n6","Project\n4"]
The trailing \n6 is the colour code (0 = none, 1 = grey, 2 = green, 3 = purple, 4 = blue, 5 = yellow, 6 = red, 7 = orange). Setting Finder tags via xattr requires constructing a binary plist; the easier route is tag from Homebrew (brew install tag) or AppleScript via osascript.
# Read the Finder comment
xattr -p com.apple.metadata:kMDItemFinderComment ~/Documents/report.pdf | \
plutil -convert xml1 -o - - | grep -A1 string
Output:
<string>Q1 numbers, revised after audit</string>
Resource forks and old Mac metadata
Classic Mac OS stored a file's "data fork" and "resource fork" in two parallel streams. macOS still understands resource forks but exposes them as the xattr com.apple.ResourceFork. Most modern apps don't use them, but you'll see them on legacy fonts, classic-era files imported from old archives, and .app bundles whose icons predate Asset Catalog.
# Resource fork inspection
xattr ~/Library/Fonts/LegacyFont.dfont
xattr -l ~/Library/Fonts/LegacyFont.dfont | head -3
Output:
com.apple.FinderInfo
com.apple.ResourceFork
com.apple.FinderInfo:
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
com.apple.FinderInfo is a 32-byte fixed-length structure with the file's creator code (4 bytes), type code (4 bytes), Finder flags, and icon location. It's almost vestigial on modern macOS — uniform type identifiers (UTIs) and Launch Services have superseded creator/type codes — but Finder still respects the "invisible" flag stored in this field.
ACLs — the xattr-adjacent world
macOS layers POSIX-extended Access Control Lists on top of the standard rwx permission bits. ACLs are not technically xattr attributes you'd manage with xattr -w, but they're stored as out-of-band metadata associated with the inode and they obey the same "is it preserved by cp?" rules — so they're worth covering here. The macOS tools are ls -le (show ACLs) and chmod +a / chmod -a (modify), which differ from the Linux getfacl / setfacl syntax.
# Show ACLs alongside POSIX mode
ls -le ~/scratch/shared.log
Output:
-rw-r--r-- 1 alice staff 2048 May 25 10:00 /Users/alice/scratch/shared.log
(No + after the mode means no ACLs. Compare:)
# Grant 'bob' write access via an ACL
chmod +a "bob allow write,append" ~/scratch/shared.log
ls -le ~/scratch/shared.log
Output:
-rw-r--r--+ 1 alice staff 2048 May 25 10:00 /Users/alice/scratch/shared.log
0: user:bob allow write,append
The + after -rw-r--r-- is the signal that an ACL exists. ACL entries are ordered; the number on the left (0:) is its position. Higher entries (lower numbers) win.
# Add a deny entry that wins over the allow above
chmod +a "everyone deny delete" ~/scratch/shared.log
ls -le ~/scratch/shared.log
Output:
-rw-r--r--+ 1 alice staff 2048 May 25 10:00 /Users/alice/scratch/shared.log
0: everyone deny delete
1: user:bob allow write,append
# Remove a specific ACL entry by index
chmod -a# 0 ~/scratch/shared.log
# Or by matching the rule text
chmod -a "everyone deny delete" ~/scratch/shared.log
# Wipe every ACL entry, return to plain POSIX
chmod -N ~/scratch/shared.log
ls -le ~/scratch/shared.log
Output:
-rw-r--r-- 1 alice staff 2048 May 25 10:00 /Users/alice/scratch/shared.log
ACL permission keywords
| Keyword | Effect on file | Effect on directory |
|---|---|---|
read | Read contents | ls directory listing |
write | Modify contents | Create entries |
append | Append-only writes | Create entries (no delete) |
execute | Run as program | Traverse (cd, lookup by name) |
delete | Delete this file | Delete this directory |
delete_child | n/a | Delete entries inside |
readattr | Read POSIX attrs | same |
writeattr | Write POSIX attrs | same |
readextattr | Read xattrs | same |
writeextattr | Write xattrs | same |
readsecurity | Read ACL itself | same |
writesecurity | Modify ACL | same |
chown | Change owner | same |
Inheritance qualifiers (directory-only): file_inherit, directory_inherit, limit_inherit, only_inherit.
# Team directory: devs group inherits write into every new file/dir
mkdir ~/scratch/team
chmod +a "group:devs allow list,add_file,search,add_subdirectory,directory_inherit,file_inherit" ~/scratch/team
ls -lde ~/scratch/team
Output:
drwxr-xr-x+ 2 alice staff 64 May 25 10:00 /Users/alice/scratch/team
0: group:devs allow list,add_file,search,add_subdirectory,directory_inherit,file_inherit
Recursing into directories
-r walks the directory tree, applying the operation to every file. Combine with -d to clear a specific attribute from every file under a path — the standard recipe for unblocking a downloaded app whose bundle contents are all quarantined.
# What attributes does the entire .app bundle carry?
xattr -r /Applications/MyApp.app | head -10
Output:
/Applications/MyApp.app: com.apple.quarantine
/Applications/MyApp.app/Contents: com.apple.quarantine
/Applications/MyApp.app/Contents/Info.plist: com.apple.quarantine
/Applications/MyApp.app/Contents/MacOS: com.apple.quarantine
/Applications/MyApp.app/Contents/MacOS/MyApp: com.apple.quarantine
/Applications/MyApp.app/Contents/PkgInfo: com.apple.quarantine
/Applications/MyApp.app/Contents/Resources: com.apple.quarantine
/Applications/MyApp.app/Contents/Resources/AppIcon.icns: com.apple.quarantine
/Applications/MyApp.app/Contents/Resources/Assets.car: com.apple.quarantine
/Applications/MyApp.app/Contents/Resources/Base.lproj: com.apple.quarantine
# Strip quarantine from every file recursively
sudo xattr -dr com.apple.quarantine /Applications/MyApp.app
# Verify it's gone
xattr -r /Applications/MyApp.app | head -5
Output: (empty — exits 0 on success)
# Or clear *everything* recursively (nuclear option)
sudo xattr -cr /Applications/MyApp.app
Output: (none — exits 0 on success)
Preserving xattrs on copy and archive
The single biggest source of "I cleared the xattr and it came back" confusion is forgetting that cp, rsync, and tar each have their own preservation flags. Choose the right one for the operation.
| Tool | Default | Preserve xattrs |
|---|---|---|
cp | preserves on same-fs APFS, drops cross-fs | cp -p (also preserves mode/owner/times) |
cp -R directory | same | cp -Rp |
rsync | drops xattrs and ACLs by default | rsync -aX for xattrs, -aA for ACLs, -aXA for both |
tar | drops xattrs | macOS's bsdtar: --xattrs flag (off by default) |
ditto | preserves by default | nothing extra needed |
zip | drops xattrs and resource forks | ditto -ck --keepParent --sequesterRsrc (creates AppleDouble __MACOSX) |
unzip | drops xattrs even if zip has them | ditto -xk archive.zip dest/ |
scp | always drops | use rsync -aX over ssh instead |
# Two-volume preservation test
xattr -w com.example.test "preserve-me" /tmp/source.txt
cp -p /tmp/source.txt /Volumes/USB/dest.txt
xattr -l /Volumes/USB/dest.txt
Output:
com.example.test: preserve-me
# rsync — default drops, -X preserves
rsync /tmp/source.txt /Volumes/USB/dest-no-x.txt
rsync -aX /tmp/source.txt /Volumes/USB/dest-with-x.txt
xattr -l /Volumes/USB/dest-no-x.txt
xattr -l /Volumes/USB/dest-with-x.txt
Output:
com.example.test: preserve-me
# ditto — Apple's "copy with full metadata" tool
ditto /tmp/source.txt /Volumes/USB/dest-ditto.txt
xattr -l /Volumes/USB/dest-ditto.txt
Output:
com.example.test: preserve-me
When sharing a built
.appbundle internally, useditto -ck --sequesterRsrc app.app app.ziprather thanzip -r. Plainzipdrops resource forks and quarantine survives in a non-recoverable way; ditto preserves everything in an AppleDouble__MACOSXsub-tree that unzips cleanly on macOS.
Cross-volume preservation traps
Not every filesystem can store macOS xattrs. APFS and HFS+ store them natively. SMB/CIFS shares to Linux servers, NFSv3, and FAT32 (USB sticks) typically don't — macOS will either silently drop them or fake-store them in AppleDouble ._<filename> sidecar files that look like garbage on the other side.
# Check filesystem type of a mount
diskutil info /Volumes/USB | grep "File System Personality"
Output:
File System Personality: ExFAT
# Tell macOS not to write AppleDouble sidecars on network shares
defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true
# And on USB drives
defaults write com.apple.desktopservices DSDontWriteUSBStores -bool true
# Take effect after killing Finder
killall Finder
Output: (none — exits 0 on success)
Querying xattrs from scripts
In automation, you usually want a one-line "does this attribute exist on this file?" check rather than a human-readable dump. Combine xattr -p with shell exit-code testing.
# Bash: check for quarantine, no output
if xattr -p com.apple.quarantine ~/Downloads/MyApp.dmg >/dev/null 2>&1; then
echo "QUARANTINED"
else
echo "clean"
fi
Output:
QUARANTINED
# Capture an attribute into a variable
where_from=$(xattr -p com.apple.metadata:kMDItemWhereFroms \
~/Downloads/installer.dmg 2>/dev/null | \
plutil -convert json -o - - 2>/dev/null | \
python3 -c 'import json,sys; print(json.load(sys.stdin)[0])')
echo "Downloaded from: $where_from"
Output:
Downloaded from: https://example.com/download/installer.dmg
# Find every quarantined binary under /Applications
find /Applications -type f -perm -u+x 2>/dev/null | while read -r f; do
if xattr -p com.apple.quarantine "$f" >/dev/null 2>&1; then
echo "$f"
fi
done
Output:
/Applications/MyApp.app/Contents/MacOS/MyApp
/Applications/MyApp.app/Contents/Helpers/Updater
Common pitfalls
- "It worked yesterday" after a network copy — macOS dropped the xattr when the file traversed SMB/NFS. Use
cp -ponly inside macOS volumes; for cross-host moves, archive withditto -ckfirst. - Quarantine removed but app still won't open — clearing
com.apple.quarantinedoesn't bypass code-signature checks. Inspect withcodesign -dvvv APPandspctl --assess --type exec APP. If unsigned, sign it (seecodesign) or trust it via System Settings → Privacy & Security. xattr -won a wrong file — there's no confirmation prompt; the attribute is silently created or overwritten. Runxattrfirst to confirm what's there.- Recursive operations cross symlinks by default —
xattr -rd com.apple.quarantine /Applications/MyApp.appwill follow symlinks out of the bundle. Add-sto operate on the symlink itself if the target is outside the tree you intended to touch. - Permissions matter — modifying or clearing xattrs requires write permission on the file. System binaries owned by root need
sudo. SIP-protected paths (/System,/usrexcept/usr/local) cannot be modified at all, even by root. - The 128 KB attribute size limit — values larger than ~128 KB on APFS will fail with
Argument too large. Don't store images or full plist trees as a single xattr; use a sidecar file. - Confusing
xattr -candxattr -d—-cclears all attributes (no name argument);-d NAMEdeletes one. Typingxattr -con a critical file removes Finder tags, where-from data, and resource forks alongside whatever you intended to clear. - AppleDouble
._files — when xattrs travel through a non-macOS filesystem, macOS stores them in sidecar files named._<original>. Don'trm -f ._*if you still want the metadata on the next mount. - Reading binary plist values raw —
xattr -p com.apple.metadata:kMDItemWhereFromsoutputs hex by default; pipe throughplutil -convert json -o - -to decode. - ACL inheritance only fires on new entries — applying
directory_inherit,file_inheritto a parent does not retroactively grant the ACL to existing children. Addchmod -R +a ...for retroactive application, or recreate. com.apple.maclandcom.apple.provenancecannot be removed. Both are SIP-protected on macOS 14+ and hardened further on Tahoe 26 —sudo xattr -dreturnsOperation not permitted. See the dedicated section above; the only escape is copying through a non-SIP volume.- macOS Tahoe 26 hardened Gatekeeper around quarantine removal.
sudo xattr -d com.apple.quarantine /path/to/App.appstill works in principle, but Terminal must now have Full Disk Access granted in System Settings → Privacy & Security before the call succeeds — even withsudo. Without Full Disk Access the call returnsOperation not permitted(or "This operation is no longer supported" via relatedspctlpaths). The recursive formxattr -dr com.apple.quarantine /Applications/App.appis the recommended invocation; once Full Disk Access is granted, it works withoutsudofor apps in the user's/Applications. The companion change is that Apple has tightenedspctl --addso the per-app exception path no longer works on Tahoe — notarisation, ad-hoc re-sign, or System Settings → Privacy & Security ("Open Anyway") are the supported paths.
Real-world recipes
Unblock a downloaded app that refuses to launch
The single most common reason a developer reaches for xattr: a .dmg or .app from a trusted third-party developer was flagged by Gatekeeper and the macOS Finder shows "is damaged or unidentified developer".
# Inspect first
xattr -lr /Applications/MyApp.app | head
Output:
/Applications/MyApp.app: com.apple.quarantine
com.apple.quarantine: 0083;6650a4c0;Safari;F1A2C8E3-9D4B-4F1E-A2B3-C4D5E6F70811
# Remove quarantine recursively (requires sudo if installed for all users)
sudo xattr -dr com.apple.quarantine /Applications/MyApp.app
# Re-verify with Gatekeeper
spctl --assess --type exec /Applications/MyApp.app
Output:
/Applications/MyApp.app: accepted
# If spctl still rejects, the app is unsigned or has a broken signature.
codesign --verify --deep --strict --verbose=2 /Applications/MyApp.app
Output:
/Applications/MyApp.app: valid on disk
/Applications/MyApp.app: satisfies its Designated Requirement
Programmatically "trust" a freshly downloaded DMG
For automation pipelines that download installers — for example, a CI runner pulling a notarized DMG and mounting it for a UI test — you want to clear quarantine in one step without launching the binary.
#!/bin/bash
# trust-dmg.sh — download, clear quarantine, mount, run installer
set -euo pipefail
URL="https://example.com/downloads/MyApp.dmg"
DMG="$(mktemp -d)/MyApp.dmg"
curl -fsSLo "$DMG" "$URL"
xattr -dr com.apple.quarantine "$DMG"
# Verify signature before trusting
codesign --verify --strict "$DMG" || { echo "signature failed"; exit 1; }
# Mount and install
hdiutil attach -nobrowse "$DMG"
sudo installer -pkg /Volumes/MyApp/MyApp.pkg -target /
hdiutil detach /Volumes/MyApp
Output: (none — exits 0 on success)
Where did this file come from?
Find the URL or sender for a downloaded file. Useful when triaging a suspicious binary or finding the source page for a doc you saved months ago.
xattr -p com.apple.metadata:kMDItemWhereFroms ~/Downloads/installer.dmg | \
plutil -convert json -o - -
Output:
["https://example.com/download/installer.dmg","https://example.com/downloads/"]
# As a one-line shell function (add to ~/.zshrc)
wherefrom() {
xattr -p com.apple.metadata:kMDItemWhereFroms "$1" 2>/dev/null | \
plutil -convert json -o - - 2>/dev/null
}
wherefrom ~/Downloads/installer.dmg
Output:
["https://example.com/download/installer.dmg","https://example.com/downloads/"]
Audit all quarantined binaries in /Applications
A security-review one-liner: list every executable still carrying a quarantine flag.
find /Applications -type d -name "*.app" -maxdepth 2 | while read -r app; do
if xattr "$app" 2>/dev/null | grep -q com.apple.quarantine; then
printf "%-50s %s\n" "$(basename "$app")" "$(xattr -p com.apple.quarantine "$app")"
fi
done
Output:
MyApp.app 0083;6650a4c0;Safari;F1A2C8E3-9D4B-4F1E-A2B3-C4D5E6F70811
DevHelper.app 0083;66510800;Chrome;A1B2C3D4-...
Tag a build artifact with provenance
For internal builds you want to retain the git SHA, build timestamp, and CI runner ID on the binary itself — survives cp/rsync -X to the artifact bucket.
xattr -w com.example.git-sha "$(git rev-parse HEAD)" ./dist/myapp
xattr -w com.example.build-id "$(date -u +%Y%m%dT%H%M%SZ)" ./dist/myapp
xattr -w com.example.ci-runner "${GITHUB_RUN_ID:-local}" ./dist/myapp
xattr -l ./dist/myapp
Output:
com.example.git-sha: 3f8a92c7e2b1f0a9d4c5b6e7f8a9b0c1d2e3f4a5
com.example.build-id: 20260525T103045Z
com.example.ci-runner: 7654321
Strip Apple metadata before sharing a doc externally
A privacy-conscious habit: clear kMDItemWhereFroms and kMDItemFinderComment before forwarding a document.
strip_meta() {
for attr in com.apple.metadata:kMDItemWhereFroms \
com.apple.metadata:kMDItemFinderComment \
com.apple.metadata:_kMDItemUserTags \
com.apple.quarantine; do
xattr -d "$attr" "$1" 2>/dev/null || true
done
}
strip_meta ~/Desktop/draft-contract.pdf
xattr -l ~/Desktop/draft-contract.pdf
Output: (empty — file has no xattrs)
Recursive ACL: shared team folder where new files inherit group access
A common requirement: /srv/team-share where any member of the devs group can create files, and every new file is readable+writable by the group without each person needing to remember to chmod.
sudo mkdir -p /srv/team-share
sudo chown alice:devs /srv/team-share
sudo chmod 770 /srv/team-share
sudo chmod +a "group:devs allow list,add_file,search,add_subdirectory,delete_child,readattr,writeattr,readextattr,writeextattr,readsecurity,file_inherit,directory_inherit" /srv/team-share
ls -lde /srv/team-share
Output:
drwxrwx---+ 2 alice devs 64 May 25 10:00 /srv/team-share
0: group:devs allow list,add_file,search,add_subdirectory,delete_child,readattr,writeattr,readextattr,writeextattr,readsecurity,file_inherit,directory_inherit
Migrate a folder to a remote share without losing xattrs
When backing up ~/Documents to a NAS that supports the macOS SMB extensions, use rsync -aX to preserve attributes. If the destination doesn't support xattrs, archive first.
# Destination supports xattrs (SMB to another Mac, or APFS-on-NAS)
rsync -aXH --delete ~/Documents/ /Volumes/NAS/backup/Documents/
# Destination is FAT32 / EXT4 / cloud — archive first
ditto -ck --keepParent --sequesterRsrc ~/Documents ~/Documents.zip
rsync -a ~/Documents.zip /Volumes/USB/
Output: (none — exits 0 on success)
--sequesterRsrcinstructsdittoto put AppleDouble metadata in a parallel__MACOSX/subtree inside the zip, so cross-platform tools see clean filenames and macOS tools recover the metadata on unzip. Useditto -xk archive.zip dest/to restore.
[!WARN] Never run
xattr -c -r /orxattr -cr /System— clearing xattrs on system files removes signatures and breaks SIP-protected components. Limit recursion to user paths and/Applications.
See also
codesign— signature verification that runs after quarantine is cleared.macos-cli— broader macOS terminal reference.- Linux
permissions— POSIX permissions, ACLs viagetfacl/setfacl,chattrimmutability.
Sources
References consulted while writing and updating this article. Links open in a new tab. Order is curated by relevance — sorting is intentionally disabled (data-no-sort).
| Source | Why cited |
|---|---|
| Quarantine, MACL and provenance: what are they up to? — Eclectic Light Co. | December-2025 deep dive on com.apple.macl, com.apple.provenance, and the new XATTR_FLAG_ONLY_SAVING flag introduced in macOS 15 — primary source for the SIP-protected-xattr and xattr-flags sections. |
| Command line to allow app past Gatekeeper — MacRumors Forums | Community thread documenting the Tahoe 26 Full-Disk-Access requirement for xattr -dr com.apple.quarantine and the deprecation of sudo spctl --add, basis for pitfall #12. |
| What to do with apps not launching on macOS Tahoe — MacPaw | Cross-confirmation of the Tahoe-specific Gatekeeper tightening behaviour and the recommended Full-Disk-Access workflow. |
| macOS Gatekeeper / Quarantine / XProtect — HackTricks | Reference for the quarantine-value byte layout (flags / timestamp / agent / UUID) cited in the quarantine-decoding example. |
| What's new for enterprise in macOS Tahoe 26 — Apple Support | Apple's authoritative enterprise changelog used to cross-check that Gatekeeper / xattr behaviour changes in Tahoe are platform-level (not MDM-payload changes). |
| Quarantine Attribute Removed by Unsigned or Untrusted Process — Detection.FYI | Security-tooling perspective on xattr -d com.apple.quarantine patterns used by malware, cited indirectly when warning against indiscriminate xattr -cr use. |