Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-roads-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ankhorage/devtools': patch
---

Canonicalize packed APM extraction roots before containment validation so equivalent temporary realpaths are accepted without weakening traversal or symlink-escape protection.
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export async function inspectPackedApmReleaseCandidateAsync(
const temporaryRoot = await mkdtemp(resolve(tmpdir(), 'ankh-apm-release-'));
try {
const archive = resolve(temporaryRoot, 'candidate.tgz');
const packageRoot = resolve(temporaryRoot, 'package');
await packCandidateAsync(root, temporaryRoot, archive);
const packageRoot = await realpath(resolve(temporaryRoot, 'package'));
const candidate = await readPackedCandidateAsync(packageRoot, archive);
const result = await validatePackedCandidateAsync(candidate, packageRoot, root, options);
if (!result.valid || options.artifactPath === undefined) return result;
Expand Down
17 changes: 17 additions & 0 deletions src/features/apm-release-validation/apmRelease.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,23 @@ test('packs outside the checkout, disables lifecycle scripts, and retains exact
);
});

test('packed validation accepts a canonicalized extracted root reached through a temporary symlink', async () => {
const root = await fixtureAsync(descriptor());
const temporaryRoot = await temporaryRootAsync();
const resolvedTemporaryRoot = join(temporaryRoot, 'resolved');
const symlinkedTemporaryRoot = join(temporaryRoot, 'symlinked');
await mkdir(resolvedTemporaryRoot);
await symlink(resolvedTemporaryRoot, symlinkedTemporaryRoot, 'dir');
const originalTemporaryRoot = process.env.TMPDIR;
process.env.TMPDIR = symlinkedTemporaryRoot;
try {
expect(await validatePackedApmReleaseAsync(root)).toMatchObject({ valid: true, blockers: [] });
} finally {
if (originalTemporaryRoot === undefined) delete process.env.TMPDIR;
else process.env.TMPDIR = originalTemporaryRoot;
}
});

test('owner code requires consent and native conditional exports resolve from the packed package', async () => {
const root = await fixtureAsync(projectionDescriptor());
expect((await validatePackedApmReleaseAsync(root)).blockers[0]).toContain('owner-code-consent');
Expand Down
Loading