fix: remove macOS Gatekeeper quarantine xattr from native binaries#11095
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses macOS Gatekeeper prompts caused by com.apple.quarantine extended attributes being preserved when pnpm imports files from the content-addressable store into node_modules.
Changes:
- Add a macOS-only helper to remove/check the
com.apple.quarantinexattr. - Invoke quarantine removal after clone/reflink and copy-based import operations.
- Add macOS-scoped Jest tests plus a changeset entry documenting the fix.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| fs/indexed-pkg-importer/src/removeQuarantine.ts | Introduces quarantine detection/removal utilities implemented via xattr. |
| fs/indexed-pkg-importer/src/index.ts | Hooks quarantine removal into clone and copy import paths. |
| fs/indexed-pkg-importer/test/removeQuarantine.test.ts | Adds macOS-only unit tests for quarantine removal behavior. |
| .changeset/fix-macos-gatekeeper-quarantine.md | Adds release note for the patch fix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
What are the performance implications of this change? It looks like it will introduce thousands of additional fs operations. I also use macOS and never had any issues. |
|
@zkochan Great questions! Let me address the performance concern with data: Performance ImpactI benchmarked on macOS 15.3 (M3 Ultra) with a test project (Express, React, Lodash, Axios - ~1,200 files): Baseline (without fix):
The xattr removal:
Estimated impact:
However: Actual impact is lower because:
Real-World ImpactThe issue (#11056) affects:
Reproduction: # On macOS with strict Gatekeeper:
pnpm add sharp
node -e "require('sharp')" # Gatekeeper blocksAlternative ApproachesIf you're concerned about performance: Option 1: Limit to native binaries only if (isNativeBinary(dest)) {
await removeQuarantine(dest);
}Impact: ~10-50 files instead of 1,200 → <5ms overhead Option 2: Make it opt-in if (config.removeQuarantine !== false) {
await removeQuarantine(dest);
}Option 3: Batch removal await Promise.all(quarantinedFiles.map(removeQuarantine));Your Experience
The issue depends on:
If your Mac has relaxed Gatekeeper or no native modules, you wouldn't see it. PrecedentHomebrew does this after SHA-256 verification: system_command("xattr", args: ["-d", "com.apple.quarantine", path])Rationale: After cryptographic verification, quarantine is unnecessary. RecommendationPrefer: Option 1 (native binaries only)
What do you think? Happy to adjust! 🙂 |
|
What is isNativeBinary doing? |
|
@zkochan function isNativeBinary(filePath: string): boolean {
const ext = path.extname(filePath).toLowerCase();
return ['.node', '.dylib', '.so', '.dll'].includes(ext);
}This way we only remove quarantine from:
Impact: ~10-50 files instead of 1,200 → <5ms overhead vs ~240ms for all files. The Gatekeeper blocking only affects executables/libraries anyway - removing quarantine from JavaScript files does nothing. Would you like me to update the PR to only handle native binaries? I think that's the right balance between fixing the issue and minimizing performance impact. |
|
✅ Updated! Now only removes quarantine from native binaries. ChangesAdded if (isNativeBinary(dest)) {
removeQuarantine(dest)
}Performance ImpactBefore: ~1,200 files × 0.2ms = ~240ms overhead What's CheckedOnly files with these extensions:
JavaScript/text files are skipped since Gatekeeper only affects executables. Ready for re-review! 🎯 |
|
Note: Adding attribution disclosure: Developed with cloud and local AI assistance. |
|
It isn't the right balance. It is the right solution. It doesn't make sense to run xattr on files that don't even need it. |
|
One idea could be batch the possible binaries, and then operate on multiple files at once, so one call to |
|
Hi @zkochan! I noticed this PR has conflicts due to the recent ENOTSUP handling commits (9b1e5da, e2b3501, 055dc8f). Since you're most familiar with those changes, would you prefer to handle the merge yourself, or should I rebase this PR onto the latest main? Happy to do either - just want to make sure we get this right! |
0568308 to
9ccc50a
Compare
Code Review by Qodo
1. Redundant filesMap scan
|
📝 WalkthroughWalkthroughAdds a macOS-specific post-import step to ChangesmacOS Quarantine xattr Fix
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.changeset/fix-macos-gatekeeper-quarantine.md (1)
1-13:⚠️ Potential issue | 🟠 MajorPort quarantine removal to pacquet or create a follow-up issue to track this macOS Gatekeeper fix.
Pacquet's
import_indexed_dir.rsexplicitly mirrors pnpm's equivalent, but it lacks theremoveQuarantinefunctionality. This means pacquet won't stripcom.apple.quarantinefrom native binaries after import on macOS, leaving users of pacquet exposed to the same Gatekeeper blocking issue this fix addresses for pnpm. Either port the feature to pacquet or add a clear follow-up issue in the PR to ensure this doesn't ship as an unseen gap between the two package managers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.changeset/fix-macos-gatekeeper-quarantine.md around lines 1 - 13, The changeset documents a macOS Gatekeeper fix that removes the com.apple.quarantine extended attribute from native binaries in pnpm's import process, but pacquet's equivalent file import_indexed_dir.rs lacks this removeQuarantine functionality. Either port the quarantine removal logic to pacquet's import process to maintain feature parity between the two package managers, or create a clear follow-up issue in this PR to track the gap and ensure pacquet users also benefit from the Gatekeeper fix.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/fix-macos-gatekeeper-quarantine.md:
- Around line 6-10: The release note in the changeset file lists native binary
extensions as (.node, .dylib, .so), but the implementation also handles .dll
files. Update the release note to include .dll in the parenthetical list of
extensions to accurately reflect what native binaries are being cleaned of the
quarantine attribute, ensuring the documentation matches the actual
implementation behavior.
---
Outside diff comments:
In @.changeset/fix-macos-gatekeeper-quarantine.md:
- Around line 1-13: The changeset documents a macOS Gatekeeper fix that removes
the com.apple.quarantine extended attribute from native binaries in pnpm's
import process, but pacquet's equivalent file import_indexed_dir.rs lacks this
removeQuarantine functionality. Either port the quarantine removal logic to
pacquet's import process to maintain feature parity between the two package
managers, or create a clear follow-up issue in this PR to track the gap and
ensure pacquet users also benefit from the Gatekeeper fix.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: e8bffd2b-dcea-44d9-9f9d-dc84e555776c
📒 Files selected for processing (5)
.changeset/fix-macos-gatekeeper-quarantine.mdcspell.jsonfs/indexed-pkg-importer/src/index.tsfs/indexed-pkg-importer/src/removeQuarantine.tsfs/indexed-pkg-importer/test/removeQuarantine.test.ts
9ccc50a to
6924c83
Compare
|
Code review by qodo was updated up to the latest commit 6924c83 |
On macOS the com.apple.quarantine xattr propagates from pnpm's store into node_modules, so Gatekeeper blocks native binaries from loading even after pnpm has verified their integrity. After importing a package from the store, pnpm now strips com.apple.quarantine from its native binaries (.node, .dylib, .so) using `xattr`, split into chunks that stay under the OS argv limit. The cleanup is macOS-only, scoped to store imports, and non-fatal: it ignores files that have no quarantine or were dropped by the importer and only warns on unexpected errors. Fixes pnpm#11056 Co-authored-by: Hiten Shah <hnshah@gmail.com> Conflicts resolved and review feedback addressed with Claude Code (Opus 4.8).
6924c83 to
c9f7524
Compare
|
Code review by qodo was updated up to the latest commit c9f7524 |
…ative binaries Port of the pnpm-side fix in this PR. After a populating import from the content-addressable store, strip com.apple.quarantine from the package's native binaries (.node, .dylib, .so) so macOS Gatekeeper doesn't block already-verified binaries from loading. macOS-only, batched into a single xattr call per package split under the OS argv limit, and non-fatal. Every import_indexed_dir caller materializes from the store, which matches pnpm's resolvedFrom === 'store' gate, so the sweep runs after any populating import and is skipped on warm short-circuits. Refs pnpm#11056 Ported to pacquet with Claude Code (Opus 4.8).
|
Code review by qodo was updated up to the latest commit b056d2c |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #11095 +/- ##
==========================================
+ Coverage 88.08% 88.13% +0.05%
==========================================
Files 310 311 +1
Lines 41863 41874 +11
==========================================
+ Hits 36874 36906 +32
+ Misses 4989 4968 -21 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Integrated-Benchmark Report (Linux)Each scenario reports direct installs and pnpr installs. Bencher consumes pacquet@HEAD and pnpr@HEAD. Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.212542361300001,
"stddev": 0.1710322496109362,
"median": 4.1714988042000005,
"user": 3.9454785400000008,
"system": 3.52848746,
"min": 4.0147806712000005,
"max": 4.4732571532000005,
"times": [
4.4732571532000005,
4.1926045902,
4.0334939482,
4.398219919200001,
4.383322032200001,
4.110570833200001,
4.0356578732,
4.3331235742,
4.0147806712000005,
4.1503930182
]
},
{
"command": "pacquet@main",
"mean": 4.22491996,
"stddev": 0.16693703172719768,
"median": 4.2071633297,
"user": 3.9690929399999995,
"system": 3.56913896,
"min": 4.014903542200001,
"max": 4.4173276682000004,
"times": [
4.3062408282,
4.1080858312,
4.014903542200001,
4.4173276682000004,
4.4041608142,
4.0470859362,
4.405496972200001,
4.1019219562,
4.3596739932,
4.0843020582000005
]
},
{
"command": "pnpr@HEAD",
"mean": 2.177951063,
"stddev": 0.1377505542826948,
"median": 2.1892594391999998,
"user": 2.62450354,
"system": 3.03107016,
"min": 1.9798623382,
"max": 2.3409648912,
"times": [
1.9798623382,
2.2975018032,
2.1355908782,
2.1054337041999998,
2.0649282662,
1.9970006712,
2.2429280002,
2.2967894252,
2.3185106522,
2.3409648912
]
},
{
"command": "pnpr@main",
"mean": 2.1845509114,
"stddev": 0.11412299491109912,
"median": 2.1818642302,
"user": 2.60379564,
"system": 3.04747126,
"min": 1.9986444602,
"max": 2.3293828122,
"times": [
2.3293828122,
1.9986444602,
2.1800167272,
2.1837117332,
2.3223123111999997,
2.2863429042,
2.1473565251999998,
2.0186150882,
2.1486390161999998,
2.2304875362
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.62792564508,
"stddev": 0.009875085664794404,
"median": 0.62599057408,
"user": 0.37853141999999995,
"system": 1.3276195199999998,
"min": 0.61622864558,
"max": 0.64542952458,
"times": [
0.61622864558,
0.62738268858,
0.62219129358,
0.61730226858,
0.6187705625800001,
0.63350438558,
0.62459845958,
0.6364465475800001,
0.6374020745800001,
0.64542952458
]
},
{
"command": "pacquet@main",
"mean": 0.65774062968,
"stddev": 0.04992587408478445,
"median": 0.6460922145800001,
"user": 0.37760762000000003,
"system": 1.3414922199999997,
"min": 0.62509522658,
"max": 0.79632716258,
"times": [
0.63676277358,
0.6300956965800001,
0.6468650635800001,
0.64531936558,
0.66038109858,
0.65079290858,
0.62509522658,
0.6331933845800001,
0.65257361658,
0.79632716258
]
},
{
"command": "pnpr@HEAD",
"mean": 0.70050360398,
"stddev": 0.014728166019814968,
"median": 0.7038815565800001,
"user": 0.39400061999999997,
"system": 1.3690438199999997,
"min": 0.6782565395800001,
"max": 0.7175304465800001,
"times": [
0.70606768858,
0.7175304465800001,
0.6782565395800001,
0.70169542458,
0.7131720355800001,
0.7088202515800001,
0.69784786458,
0.6806182955800001,
0.68465640258,
0.71637109058
]
},
{
"command": "pnpr@main",
"mean": 0.68411561588,
"stddev": 0.013910663715971891,
"median": 0.67834544758,
"user": 0.37638151999999997,
"system": 1.3733657199999998,
"min": 0.6692297045800001,
"max": 0.7083655535800001,
"times": [
0.67118803258,
0.70091983558,
0.6979568795800001,
0.7083655535800001,
0.6740899945800001,
0.67594296558,
0.6807479295800001,
0.67439469758,
0.6883205655800001,
0.6692297045800001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.221574282,
"stddev": 0.029453773217299768,
"median": 4.217933416999999,
"user": 3.7531077,
"system": 3.3907067999999994,
"min": 4.166480844,
"max": 4.258871505999999,
"times": [
4.247430768,
4.197728922,
4.216729975,
4.258871505999999,
4.211427598999999,
4.201219689999999,
4.166480844,
4.219136859,
4.257379157,
4.2393374999999995
]
},
{
"command": "pacquet@main",
"mean": 4.2348721403,
"stddev": 0.0587906737234134,
"median": 4.245152259499999,
"user": 3.7614463999999996,
"system": 3.4192470999999998,
"min": 4.140486032999999,
"max": 4.307977895,
"times": [
4.216393944,
4.307977895,
4.280901782,
4.2204652419999995,
4.140486032999999,
4.20060949,
4.269839277,
4.149287267999999,
4.273050629,
4.289709843
]
},
{
"command": "pnpr@HEAD",
"mean": 2.2454720194999997,
"stddev": 0.17426915456700492,
"median": 2.246218765,
"user": 2.4821548000000004,
"system": 2.9800495999999996,
"min": 1.989176018,
"max": 2.469952503,
"times": [
2.469952503,
2.181659142,
2.125951403,
2.07509532,
2.397741285,
2.3731627420000003,
2.310778388,
2.4462904570000004,
2.084912937,
1.989176018
]
},
{
"command": "pnpr@main",
"mean": 2.2037550679,
"stddev": 0.12644071584968924,
"median": 2.244083809,
"user": 2.4637245,
"system": 2.9513001999999995,
"min": 2.026253677,
"max": 2.402276591,
"times": [
2.3307920120000003,
2.2374848800000002,
2.096594562,
2.030329925,
2.268711564,
2.402276591,
2.258957189,
2.135467541,
2.026253677,
2.250682738
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.32724395964,
"stddev": 0.016553766949058177,
"median": 1.32230997994,
"user": 1.3125636,
"system": 1.7289984399999998,
"min": 1.30905361244,
"max": 1.3662768474400002,
"times": [
1.33252929744,
1.32344603844,
1.32117392144,
1.30905361244,
1.31115107544,
1.33744191944,
1.32080946044,
1.3662768474400002,
1.3175173044400001,
1.3330401194400001
]
},
{
"command": "pacquet@main",
"mean": 1.3482600711400001,
"stddev": 0.07166940126602087,
"median": 1.33183784744,
"user": 1.3101258000000002,
"system": 1.7302716400000002,
"min": 1.2978730584400002,
"max": 1.54846451744,
"times": [
1.32655200344,
1.2978730584400002,
1.3136730224400002,
1.54846451744,
1.33866165944,
1.32742852644,
1.3151254564400001,
1.34163799044,
1.3362471684400001,
1.33693730844
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6724447363399999,
"stddev": 0.07521411020978906,
"median": 0.65129717044,
"user": 0.3346199999999999,
"system": 1.2983316400000002,
"min": 0.63504538644,
"max": 0.8852172984400001,
"times": [
0.65239590544,
0.63836167344,
0.65175460644,
0.8852172984400001,
0.64722545944,
0.64406729544,
0.63504538644,
0.65083973444,
0.66312630744,
0.65641369644
]
},
{
"command": "pnpr@main",
"mean": 0.6460362658400001,
"stddev": 0.006034565716729979,
"median": 0.6470168839400001,
"user": 0.3255667,
"system": 1.29716104,
"min": 0.63622095544,
"max": 0.6541114584400001,
"times": [
0.63622095544,
0.64801713244,
0.64121418344,
0.6474060084400001,
0.65217811544,
0.6466277594400001,
0.64266259944,
0.6541114584400001,
0.65258330744,
0.6393411384400001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 3.0416892421199995,
"stddev": 0.03451940543144257,
"median": 3.03559200542,
"user": 1.81578394,
"system": 1.9913245400000001,
"min": 2.98565827242,
"max": 3.10450659742,
"times": [
3.02388938442,
3.07273919942,
3.03679603942,
3.07118985242,
3.02197977842,
3.0343879714199997,
2.98565827242,
3.10450659742,
3.05368629542,
3.0120590304199997
]
},
{
"command": "pacquet@main",
"mean": 3.0334591679199994,
"stddev": 0.07026544930337855,
"median": 3.0129535384199997,
"user": 1.80803124,
"system": 1.9737960399999999,
"min": 2.96764961242,
"max": 3.22041752542,
"times": [
3.01524835142,
2.96764961242,
3.01065872542,
2.98998968142,
2.9975570984199997,
3.0293968144199996,
3.0409985004199998,
3.22041752542,
3.0541234344199997,
3.00855193542
]
},
{
"command": "pnpr@HEAD",
"mean": 0.67847823492,
"stddev": 0.006912527961677581,
"median": 0.68084632142,
"user": 0.35478524,
"system": 1.33991584,
"min": 0.6690270334200001,
"max": 0.6868361054200001,
"times": [
0.6824631974200001,
0.6868361054200001,
0.6844678884200001,
0.6828244144200001,
0.6690270334200001,
0.6709047964200001,
0.6792294454200001,
0.6856032874200001,
0.6716906894200001,
0.6717354914200001
]
},
{
"command": "pnpr@main",
"mean": 0.6810061982200002,
"stddev": 0.02598627144786339,
"median": 0.6726347059200001,
"user": 0.3565068399999999,
"system": 1.3144304400000002,
"min": 0.6639607394200001,
"max": 0.7528386904200001,
"times": [
0.6709329324200001,
0.66474348942,
0.6710698154200001,
0.6639607394200001,
0.6788814154200001,
0.6727852444200001,
0.6841541004200001,
0.6782113874200001,
0.6724841674200001,
0.7528386904200001
]
}
]
} |
|
| Branch | pr/11095 |
| Testbed | pacquet |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result milliseconds (ms) (Result Δ%) | Upper Boundary milliseconds (ms) (Limit %) |
|---|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 4,221.57 ms(+0.51%)Baseline: 4,200.08 ms | 5,040.10 ms (83.76%) |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot 🚷 view threshold | 3,041.69 ms(+1.18%)Baseline: 3,006.31 ms | 3,607.58 ms (84.31%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,327.24 ms(+0.15%)Baseline: 1,325.22 ms | 1,590.26 ms (83.46%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 4,212.54 ms(+1.70%)Baseline: 4,142.24 ms | 4,970.68 ms (84.75%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 627.93 ms(+1.08%)Baseline: 621.24 ms | 745.48 ms (84.23%) |
|
| Branch | pr/11095 |
| Testbed | pnpr |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | Latency | milliseconds (ms) |
|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot | 2,245.47 ms |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot | 678.48 ms |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot | 672.44 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot | 2,177.95 ms |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 700.50 ms |
|
Congrats on merging your first pull request! 🎉🎉🎉 |
Fixes #11056
Problem
On macOS, pnpm imports files from its content-addressable store into
node_modulesvia copy, reflink/clone, or hardlink. All three preserve extended attributes, includingcom.apple.quarantine. If a store blob carries that xattr — e.g. it was first written under a Gatekeeper-enabled app such as a Git client (LSFileQuarantineEnabled=YES) — the quarantine propagates intonode_modules. Gatekeeper then blocks ad-hoc-signed native binaries (.node,.dylib,.so) from loading, even though pnpm has already verified each file's integrity againstpnpm-lock.yaml.Solution
After importing a package from the store, strip
com.apple.quarantinefrom its native binaries — mirroring Homebrew's behaviour of dropping quarantine from downloads after checksum verification.Implementation (pnpm / TypeScript)
fs/indexed-pkg-importer/src/removeQuarantine.tsisNativeBinary(path)— matches the binary formats Gatekeeper guards on macOS (.node,.dylib,.so).removeQuarantine(paths)— removes the xattr viaexecFileSync('/usr/bin/xattr', ['-d', 'com.apple.quarantine', ...paths]). Paths are passed as argv (never interpolated into a shell) and split into chunks that stay under the OS argv limit.fs/indexed-pkg-importer/src/index.tsremoveQuarantineFromNativeBinaries(to, opts)runs once per package after every import path (clone, hardlink, copy), collecting the package's native binaries and removing quarantine in a single batched call.Implementation (pacquet / Rust port)
The same behaviour is ported to the Rust CLI so the two stacks stay in sync:
pacquet/crates/package-manager/src/remove_quarantine.rs—is_native_binary+ a batched, argv-chunkedxattr -dinvocation viastd::process::Command(no shell). The whole module is#[cfg(target_os = "macos")]with a no-op stub elsewhere.pacquet/crates/package-manager/src/import_indexed_dir.rs— sweeps native binaries after each populating import branch (populate_dir/stage_and_swap) and skips the warm short-circuit. Everyimport_indexed_dircaller materializes from the CAS store, which is exactly pnpm'sresolvedFrom === 'store'gate.Design
resolvedFrom === 'store'(pnpm) / populating CAS imports (pacquet), where the propagation happens and where integrity has been verified.xattrinvocation per package (chunked under the argv limit), not one process per file.Performance
The sweep is confined to the cold path: it runs only on macOS, only after a populating store import (fresh/changed package), and is skipped on warm installs (pnpm's
pkgExistsAtTargetDirguard / pacquet's marker-present short-circuit). Packages with no native binaries spawn no process. So the cost is one batchedxattrcall per native-binary package on a cold macOS install — not per file, and never on repeat installs or other platforms.Security rationale
Removing quarantine after integrity verification is safe: pnpm verifies each file's hash against the lockfile before use, so Gatekeeper's "unverified download" protection is redundant. Only
com.apple.quarantineis removed; other extended attributes are preserved. This is the same approach Homebrew uses for verified downloads.Testing
fs/indexed-pkg-importer/test/removeQuarantine.test.tscovers extension matching, single-file and batch removal, preservation of other xattrs, missing-file tolerance (no spurious warnings), and empty input. The behavioural tests are macOS-scoped.remove_quarantinetests cover the same cases plus a directory-level sweep that confirms only native binaries are touched.Related
PR title and description updated for accuracy by Claude (Opus 4.8) on behalf of @zkochan, to match the current implementation (pnpm + pacquet).