path-free: a shared build folder outside home is not a leak (only home and user-name paths are private)

This commit is contained in:
CRTkafa 2026-10-26 20:13:11 +07:00
parent efa9503a50
commit 0858f4e538
3 changed files with 11 additions and 1 deletions

View file

@ -3,6 +3,8 @@
## Unreleased
- `apple build --path-free`: a checkout in a shared build folder outside the home folder is no longer a leak needle (dsymutil and the module cache record it; only home and user-name paths count).
## 0.8.54 · 2026-10-26
- `apple build` is hermetic and can be path-free. `--derived-data <dir>` (`apple.build.derivedData`) is xcodebuild `-derivedDataPath`; with `--out` (or `apple.build.out`) it defaults to `<out>/DerivedData` instead of `~/Library/Developer/Xcode/DerivedData` (without `--out` Xcode's folder stays). `--path-free` (`apple.build.pathFree`) remaps the home folder, checkout, out and DerivedData out of the app binary and the archive's dSYMs / Symbols (Swift `-debug-prefix-map` / `-file-prefix-map`, clang `-ffile-prefix-map`; dwarf-with-dsym stays, symbol upload keeps working), scans the archive and stops before the export with the hit count and the first file + offsets (never the paths). `--scan-paths` only scans and warns. A `--out` outside the home folder prints a hint. `--json` / `latest.json` carry `derivedData`, `pathFree` and `leaks`. Store-pitfalls 124.

View file

@ -52,7 +52,11 @@ export interface PathHit { file: string; kind: string; offset: number }
* checkout. DerivedData is no needle: outside the home folder it is not the user's (dsymutil and Xcode record it in the
* dSYM and the __swift_ast section and no prefix map reaches it); inside the home folder the home needle finds it. */
export function leakNeedles(i: { home: string; user: string; root: string }): { kind: string; value: string }[] {
const list = [{ kind: 'home folder', value: trim(i.home) + '/' }, { kind: 'user folder', value: `/Users/${i.user}/` }, { kind: 'checkout', value: trim(i.root) + '/' }];
// the checkout is a needle only when it says something about the person: inside the home folder or naming the user.
// A shared build folder (/Users/Shared/..., /opt/...) recorded by dsymutil and Xcode's module cache is not private
const root = trim(i.root) + '/';
const personal = root.startsWith(trim(i.home) + '/') || (i.user.length >= 3 && root.includes(`/${i.user}/`));
const list = [{ kind: 'home folder', value: trim(i.home) + '/' }, { kind: 'user folder', value: `/Users/${i.user}/` }, ...(personal ? [{ kind: 'checkout', value: root }] : [])];
const seen = new Set<string>();
return list.filter(n => n.value.length >= 6 && !seen.has(n.value) && seen.add(n.value));
}

View file

@ -74,6 +74,10 @@ describe('leak scan', () => {
test('needles are named by kind; /Users/<user>/ is dropped when it equals the home folder', () => {
expect(needles.map(n => n.kind)).toEqual(['home folder', 'checkout']);
});
test('a shared build folder outside the home folder is not a needle (dsymutil / module cache paths there are fine)', () => {
expect(leakNeedles({ home: '/Users/you', user: 'you', root: '/Users/Shared/ship/app/abc' }).map(n => n.kind)).toEqual(['home folder']);
expect(leakNeedles({ home: '/Users/you', user: 'you', root: '/opt/builds/you/app' }).map(n => n.kind)).toEqual(['home folder', 'checkout']);
});
test('counts hits, keeps first offsets per file, scans app, dSYMs and Symbols', () => {
const r = scanForPaths(archiveScanDirs(arch), needles);
expect(r.files).toBe(2);