Fix import-safe checks when scripts are run from a path with symlinks (#1265)

* Fix import-safe checks when scripts are run from a path with symlinks

In v6, setup-java was made "import-safe" to facilitate testing. This
prevents setup-java & cleanup-java from doing anything when their
sources get imported.

This works fine in the general case, but actually invoking the script
(`node setup-java/index.js`) when the path to the script contains
symlinks led to the script incorrectly believing it was imported, and
refuse to actually run.

To fix this, we pass `process.argv[1]` through `fs.realpathSync`, which
resolves symlinks in the path.

Fixes #1264

* Preserve import safety when resolving symlink entrypoints

Share entrypoint detection between setup and cleanup, handle non-file entrypoints safely, and normalize both paths for preserved symlinks. Add real-process regression coverage and rebuild action bundles.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 831c32f2-a275-45bd-a92b-c387372a1554

* Update js-yaml to fix merge-source denial of service

Bump the transitive development dependency from 3.15.1 to 3.15.2 to address GHSA-2883-xcg3-v3hh without changing dependency ranges or CI checks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 831c32f2-a275-45bd-a92b-c387372a1554

---------

Co-authored-by: Bruno Borges <brborges@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 831c32f2-a275-45bd-a92b-c387372a1554
This commit is contained in:
otaconix
2026-09-09 02:20:21 -04:00
committed by GitHub
co-authored by Copilot App Bruno Borges
parent 0781fc6af3
commit 134912a529
10 changed files with 226 additions and 8 deletions
+52
View File
@@ -0,0 +1,52 @@
import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals';
import fs from 'fs';
import {isMainModule} from '../src/is-main-module.js';
describe('main module detection', () => {
const originalArgv = process.argv;
beforeEach(() => {
process.argv = [process.execPath, 'entrypoint.js'];
});
afterEach(() => {
process.argv = originalArgv;
jest.restoreAllMocks();
});
it.each([undefined, '-'])(
'skips filesystem access when argv[1] is %s',
entrypoint => {
process.argv =
entrypoint === undefined
? [process.execPath]
: [process.execPath, entrypoint];
const realpath = jest.spyOn(fs, 'realpathSync');
expect(isMainModule(import.meta.url)).toBe(false);
expect(realpath).not.toHaveBeenCalled();
}
);
it.each(['ENOENT', 'ENOTDIR'])(
'treats a non-file entrypoint returning %s as an import',
code => {
jest.spyOn(fs, 'realpathSync').mockImplementation(() => {
throw Object.assign(new Error('No file-based entrypoint'), {code});
});
expect(isMainModule(import.meta.url)).toBe(false);
}
);
it('propagates unexpected filesystem errors', () => {
const error = Object.assign(new Error('Permission denied'), {
code: 'EACCES'
});
jest.spyOn(fs, 'realpathSync').mockImplementation(() => {
throw error;
});
expect(() => isMainModule(import.meta.url)).toThrow(error);
});
});