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.*).

bash
which xattr
xattr -h | head -5

Output:

text
/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.

bash
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

OptionMeaning
(no flag)List attribute names on each file
-lLong output — show name and value for each attribute
-p ATTRPrint the value of a single named attribute
-w ATTR VALWrite VAL as the value of ATTR
-d ATTRDelete a single named attribute
-cClear all extended attributes on the file
-rRecurse into directories
-sOperate on the symlink itself, not its target
-xRead or write the value as a hex string (for binary data)
-vPrint the file name before each line (useful with multiple files)
-hShow 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.

bash
# 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:

text
com.example.author
bash
# 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:

text

---
com.example.author

Modern macOS (cp from 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 use cp -p or rsync -X if 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.

bash
# A typical downloaded DMG carries quarantine + WhereFrom metadata
xattr ~/Downloads/installer.dmg

Output:

text
com.apple.metadata:kMDItemWhereFroms
com.apple.quarantine
bash
# Long form — name and value together
xattr -l ~/Downloads/installer.dmg

Output:

text
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
bash
# Print a single attribute's value
xattr -p com.apple.quarantine ~/Downloads/installer.dmg

Output:

text
0083;6650a4c0;Safari;F1A2C8E3-9D4B-4F1E-A2B3-C4D5E6F70811
bash
# Hex form — useful for binary plist values like kMDItemWhereFroms
xattr -px com.apple.metadata:kMDItemWhereFroms ~/Downloads/installer.dmg | head -3

Output:

text
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.

bash
# 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:

text
com.example.build-id: v2026.05.25-r42
com.example.author: Alice Dev
bash
# 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:

text
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.

bash
# Check whether quarantine is set
xattr ~/Downloads/MyApp.dmg

Output:

text
com.apple.metadata:kMDItemWhereFroms
com.apple.quarantine
bash
# Decode the quarantine value
xattr -p com.apple.quarantine ~/Downloads/MyApp.dmg

Output:

text
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).

bash
# 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:

text
/Applications/MyApp.app: accepted

[!WARN] Clearing com.apple.quarantine does not disable Gatekeeper — codesign --verify and 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. See codesign.

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".

xattrWhat it isRemovable?
com.apple.maclMandatory 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.provenanceProvenance 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.
bash
# Both attributes are visible to everyone; the values are opaque to humans.
xattr -l ~/Documents/notes.md | head -20

Output:

text
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|
bash
# Trying to delete returns Operation not permitted even with sudo
sudo xattr -d com.apple.macl ~/Documents/notes.md

Output:

text
xattr: [Errno 1] Operation not permitted: '/Users/alice/Documents/notes.md'

If com.apple.macl is actually causing a permission problem (a file inheriting an MACL entry that doesn't match the current process), the workaround is cp -p (or ditto) 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:.

bash
# 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:

text
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).

bash
# Where did this file come from?
xattr -p com.apple.metadata:kMDItemWhereFroms ~/Downloads/installer.dmg | \
    plutil -convert json -o - -

Output:

text
["https://example.com/download/installer.dmg","https://example.com/downloads/"]
bash
# Read Finder colour tags (a binary plist of strings)
xattr -p com.apple.metadata:_kMDItemUserTags ~/Documents/report.pdf | \
    plutil -convert json -o - -

Output:

text
["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.

bash
# Read the Finder comment
xattr -p com.apple.metadata:kMDItemFinderComment ~/Documents/report.pdf | \
    plutil -convert xml1 -o - - | grep -A1 string

Output:

text
    <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.

bash
# Resource fork inspection
xattr ~/Library/Fonts/LegacyFont.dfont
xattr -l ~/Library/Fonts/LegacyFont.dfont | head -3

Output:

text
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.

bash
# Show ACLs alongside POSIX mode
ls -le ~/scratch/shared.log

Output:

text
-rw-r--r--  1 alice  staff  2048 May 25 10:00 /Users/alice/scratch/shared.log

(No + after the mode means no ACLs. Compare:)

bash
# Grant 'bob' write access via an ACL
chmod +a "bob allow write,append" ~/scratch/shared.log
ls -le ~/scratch/shared.log

Output:

text
-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.

bash
# Add a deny entry that wins over the allow above
chmod +a "everyone deny delete" ~/scratch/shared.log
ls -le ~/scratch/shared.log

Output:

text
-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
bash
# 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:

text
-rw-r--r--  1 alice  staff  2048 May 25 10:00 /Users/alice/scratch/shared.log

ACL permission keywords

KeywordEffect on fileEffect on directory
readRead contentsls directory listing
writeModify contentsCreate entries
appendAppend-only writesCreate entries (no delete)
executeRun as programTraverse (cd, lookup by name)
deleteDelete this fileDelete this directory
delete_childn/aDelete entries inside
readattrRead POSIX attrssame
writeattrWrite POSIX attrssame
readextattrRead xattrssame
writeextattrWrite xattrssame
readsecurityRead ACL itselfsame
writesecurityModify ACLsame
chownChange ownersame

Inheritance qualifiers (directory-only): file_inherit, directory_inherit, limit_inherit, only_inherit.

bash
# 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:

text
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.

bash
# What attributes does the entire .app bundle carry?
xattr -r /Applications/MyApp.app | head -10

Output:

text
/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
bash
# 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)

bash
# 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.

ToolDefaultPreserve xattrs
cppreserves on same-fs APFS, drops cross-fscp -p (also preserves mode/owner/times)
cp -R directorysamecp -Rp
rsyncdrops xattrs and ACLs by defaultrsync -aX for xattrs, -aA for ACLs, -aXA for both
tardrops xattrsmacOS's bsdtar: --xattrs flag (off by default)
dittopreserves by defaultnothing extra needed
zipdrops xattrs and resource forksditto -ck --keepParent --sequesterRsrc (creates AppleDouble __MACOSX)
unzipdrops xattrs even if zip has themditto -xk archive.zip dest/
scpalways dropsuse rsync -aX over ssh instead
bash
# 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:

text
com.example.test: preserve-me
bash
# 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:

text

com.example.test: preserve-me
bash
# 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:

text
com.example.test: preserve-me

When sharing a built .app bundle internally, use ditto -ck --sequesterRsrc app.app app.zip rather than zip -r. Plain zip drops resource forks and quarantine survives in a non-recoverable way; ditto preserves everything in an AppleDouble __MACOSX sub-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.

bash
# Check filesystem type of a mount
diskutil info /Volumes/USB | grep "File System Personality"

Output:

text
   File System Personality:  ExFAT
bash
# 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
# 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:

text
QUARANTINED
bash
# 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:

text
Downloaded from: https://example.com/download/installer.dmg
bash
# 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:

text
/Applications/MyApp.app/Contents/MacOS/MyApp
/Applications/MyApp.app/Contents/Helpers/Updater

Common pitfalls

  1. "It worked yesterday" after a network copy — macOS dropped the xattr when the file traversed SMB/NFS. Use cp -p only inside macOS volumes; for cross-host moves, archive with ditto -ck first.
  2. Quarantine removed but app still won't open — clearing com.apple.quarantine doesn't bypass code-signature checks. Inspect with codesign -dvvv APP and spctl --assess --type exec APP. If unsigned, sign it (see codesign) or trust it via System Settings → Privacy & Security.
  3. xattr -w on a wrong file — there's no confirmation prompt; the attribute is silently created or overwritten. Run xattr first to confirm what's there.
  4. Recursive operations cross symlinks by defaultxattr -rd com.apple.quarantine /Applications/MyApp.app will follow symlinks out of the bundle. Add -s to operate on the symlink itself if the target is outside the tree you intended to touch.
  5. Permissions matter — modifying or clearing xattrs requires write permission on the file. System binaries owned by root need sudo. SIP-protected paths (/System, /usr except /usr/local) cannot be modified at all, even by root.
  6. 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.
  7. Confusing xattr -c and xattr -d-c clears all attributes (no name argument); -d NAME deletes one. Typing xattr -c on a critical file removes Finder tags, where-from data, and resource forks alongside whatever you intended to clear.
  8. AppleDouble ._ files — when xattrs travel through a non-macOS filesystem, macOS stores them in sidecar files named ._<original>. Don't rm -f ._* if you still want the metadata on the next mount.
  9. Reading binary plist values rawxattr -p com.apple.metadata:kMDItemWhereFroms outputs hex by default; pipe through plutil -convert json -o - - to decode.
  10. ACL inheritance only fires on new entries — applying directory_inherit,file_inherit to a parent does not retroactively grant the ACL to existing children. Add chmod -R +a ... for retroactive application, or recreate.
  11. com.apple.macl and com.apple.provenance cannot be removed. Both are SIP-protected on macOS 14+ and hardened further on Tahoe 26 — sudo xattr -d returns Operation not permitted. See the dedicated section above; the only escape is copying through a non-SIP volume.
  12. macOS Tahoe 26 hardened Gatekeeper around quarantine removal. sudo xattr -d com.apple.quarantine /path/to/App.app still works in principle, but Terminal must now have Full Disk Access granted in System Settings → Privacy & Security before the call succeeds — even with sudo. Without Full Disk Access the call returns Operation not permitted (or "This operation is no longer supported" via related spctl paths). The recursive form xattr -dr com.apple.quarantine /Applications/App.app is the recommended invocation; once Full Disk Access is granted, it works without sudo for apps in the user's /Applications. The companion change is that Apple has tightened spctl --add so 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".

bash
# Inspect first
xattr -lr /Applications/MyApp.app | head

Output:

text
/Applications/MyApp.app: com.apple.quarantine
com.apple.quarantine: 0083;6650a4c0;Safari;F1A2C8E3-9D4B-4F1E-A2B3-C4D5E6F70811
bash
# 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:

text
/Applications/MyApp.app: accepted
bash
# If spctl still rejects, the app is unsigned or has a broken signature.
codesign --verify --deep --strict --verbose=2 /Applications/MyApp.app

Output:

text
/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.

bash
#!/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.

bash
xattr -p com.apple.metadata:kMDItemWhereFroms ~/Downloads/installer.dmg | \
    plutil -convert json -o - -

Output:

text
["https://example.com/download/installer.dmg","https://example.com/downloads/"]
bash
# 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:

text
["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.

bash
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:

text
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.

bash
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:

text
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.

bash
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.

bash
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:

text
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.

bash
# 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)

--sequesterRsrc instructs ditto to 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. Use ditto -xk archive.zip dest/ to restore.

[!WARN] Never run xattr -c -r / or xattr -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 via getfacl/setfacl, chattr immutability.

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).

SourceWhy 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 ForumsCommunity 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 — MacPawCross-confirmation of the Tahoe-specific Gatekeeper tightening behaviour and the recommended Full-Disk-Access workflow.
macOS Gatekeeper / Quarantine / XProtect — HackTricksReference 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 SupportApple'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.FYISecurity-tooling perspective on xattr -d com.apple.quarantine patterns used by malware, cited indirectly when warning against indiscriminate xattr -cr use.