diff --git a/packages/cache/__tests__/listAndValidate.test.ts b/packages/cache/__tests__/listAndValidate.test.ts index 33410f04..743ad637 100644 --- a/packages/cache/__tests__/listAndValidate.test.ts +++ b/packages/cache/__tests__/listAndValidate.test.ts @@ -125,10 +125,7 @@ describe('listAndValidate (real archives)', () => { const archive = buildTarArchive([ {path: 'cache/file.txt', type: 'File', body: Buffer.from('hi')} ]) - const archivePath = writeArchive( - 'clean.tar.gz', - zlib.gzipSync(archive) - ) + const archivePath = writeArchive('clean.tar.gz', zlib.gzipSync(archive)) const violations = await listAndValidate( archivePath, CompressionMethod.Gzip, @@ -259,10 +256,7 @@ describe('listAndValidate (real archives)', () => { }, {path: 'cache/sub/c.txt', type: 'File', body: Buffer.from('4')} ]) - const archivePath = writeArchive( - 'mixed.tar.gz', - zlib.gzipSync(archive) - ) + const archivePath = writeArchive('mixed.tar.gz', zlib.gzipSync(archive)) const violations = await listAndValidate( archivePath, CompressionMethod.Gzip, @@ -277,10 +271,7 @@ describe('listAndValidate (real archives)', () => { const archive = buildTarArchive([ {path: 'cache/dev', type: 'CharacterDevice'} ]) - const archivePath = writeArchive( - 'chardev.tar.gz', - zlib.gzipSync(archive) - ) + const archivePath = writeArchive('chardev.tar.gz', zlib.gzipSync(archive)) const violations = await listAndValidate( archivePath, CompressionMethod.Gzip, @@ -295,10 +286,7 @@ describe('listAndValidate (real archives)', () => { const archive = buildTarArchive([ {path: 'cache/tiny.txt', type: 'File', body: Buffer.from('1')} ]) - const archivePath = writeArchive( - 'single.tar.gz', - zlib.gzipSync(archive) - ) + const archivePath = writeArchive('single.tar.gz', zlib.gzipSync(archive)) const violations = await listAndValidate( archivePath, CompressionMethod.Gzip, diff --git a/packages/cache/__tests__/pathValidation.test.ts b/packages/cache/__tests__/pathValidation.test.ts index f8b57cad..43463226 100644 --- a/packages/cache/__tests__/pathValidation.test.ts +++ b/packages/cache/__tests__/pathValidation.test.ts @@ -8,7 +8,8 @@ import { } from '../src/internal/pathValidation' const IS_WINDOWS = process.platform === 'win32' -const CASE_INSENSITIVE = process.platform === 'win32' || process.platform === 'darwin' +const CASE_INSENSITIVE = + process.platform === 'win32' || process.platform === 'darwin' describe('deriveAllowedRoots', () => { const cwd = IS_WINDOWS ? 'C:\\workspace' : '/workspace' @@ -63,9 +64,7 @@ describe('deriveAllowedRoots', () => { describe('negation handling', () => { test('negation pattern (! prefix) is dropped from allowed roots', () => { - const input = IS_WINDOWS - ? ['!C:\\foo\\secret'] - : ['!/foo/secret'] + const input = IS_WINDOWS ? ['!C:\\foo\\secret'] : ['!/foo/secret'] expect(deriveAllowedRoots(input, cwd)).toEqual([]) }) @@ -107,9 +106,9 @@ describe('deriveAllowedRoots', () => { process.env['CACHE_TEST_ROOT'] = IS_WINDOWS ? 'C:\\envroot' : '/envroot' try { const expected = IS_WINDOWS ? 'C:\\envroot\\sub' : '/envroot/sub' - expect( - deriveAllowedRoots(['${CACHE_TEST_ROOT}/sub'], cwd) - ).toEqual([expected]) + expect(deriveAllowedRoots(['${CACHE_TEST_ROOT}/sub'], cwd)).toEqual([ + expected + ]) } finally { if (original === undefined) delete process.env['CACHE_TEST_ROOT'] else process.env['CACHE_TEST_ROOT'] = original @@ -132,12 +131,14 @@ describe('deriveAllowedRoots', () => { test('%VAR% Windows-style expands an environment variable', () => { const original = process.env['CACHE_TEST_WIN_ROOT'] - process.env['CACHE_TEST_WIN_ROOT'] = IS_WINDOWS ? 'C:\\winroot' : '/winroot' + process.env['CACHE_TEST_WIN_ROOT'] = IS_WINDOWS + ? 'C:\\winroot' + : '/winroot' try { const expected = IS_WINDOWS ? 'C:\\winroot\\sub' : '/winroot/sub' - expect( - deriveAllowedRoots(['%CACHE_TEST_WIN_ROOT%/sub'], cwd) - ).toEqual([expected]) + expect(deriveAllowedRoots(['%CACHE_TEST_WIN_ROOT%/sub'], cwd)).toEqual([ + expected + ]) } finally { if (original === undefined) delete process.env['CACHE_TEST_WIN_ROOT'] else process.env['CACHE_TEST_WIN_ROOT'] = original @@ -170,9 +171,9 @@ describe('deriveAllowedRoots', () => { const expected = IS_WINDOWS ? 'C:\\envroot*odd\\sub' : '/envroot*odd/sub' - expect( - deriveAllowedRoots(['${CACHE_TEST_ROOT}/sub'], cwd) - ).toEqual([expected]) + expect(deriveAllowedRoots(['${CACHE_TEST_ROOT}/sub'], cwd)).toEqual([ + expected + ]) } finally { if (original === undefined) delete process.env['CACHE_TEST_ROOT'] else process.env['CACHE_TEST_ROOT'] = original @@ -290,7 +291,7 @@ describe('validateEntry', () => { test('deeply nested file', () => { const r = validateEntry( - 'node_modules/' + Array(50).fill('sub').join('/') + '/foo.js', + `node_modules/${Array(50).fill('sub').join('/')}/foo.js`, undefined, 'File', allowedRoots, @@ -379,7 +380,7 @@ describe('validateEntry', () => { test('filename containing .. as substring (not segment)', () => { for (const name of ['..hidden', 'file..txt', 'a..b/c']) { const r = validateEntry( - 'node_modules/' + name, + `node_modules/${name}`, undefined, 'File', allowedRoots, @@ -529,13 +530,7 @@ describe('validateEntry', () => { }) test('Windows drive-relative (no slash)', () => { - const r = validateEntry( - 'C:foo', - undefined, - 'File', - allowedRoots, - cwd - ) + const r = validateEntry('C:foo', undefined, 'File', allowedRoots, cwd) expect(r.ok).toBe(false) if (!r.ok) expect(r.code).toBe('ABSOLUTE_PATH') }) @@ -1069,10 +1064,7 @@ describe('formatViolationSummary', () => { }) test('shows up to maxShown items verbatim', () => { - const out = formatViolationSummary( - [v('a'), v('b'), v('c')], - 5 - ) + const out = formatViolationSummary([v('a'), v('b'), v('c')], 5) expect(out).toContain(' - a') expect(out).toContain(' - b') expect(out).toContain(' - c') diff --git a/packages/cache/__tests__/tarPathValidation.test.ts b/packages/cache/__tests__/tarPathValidation.test.ts index d2f186d9..3fad47d2 100644 --- a/packages/cache/__tests__/tarPathValidation.test.ts +++ b/packages/cache/__tests__/tarPathValidation.test.ts @@ -203,9 +203,7 @@ describe('extractTar path validation integration', () => { }) test('clean archive: no throw, extraction proceeds normally', async () => { - jest - .spyOn(listAndValidate, 'listAndValidate') - .mockResolvedValue([]) + jest.spyOn(listAndValidate, 'listAndValidate').mockResolvedValue([]) const warnSpy = jest.spyOn(core, 'warning').mockImplementation() const execMock = jest.spyOn(exec, 'exec').mockResolvedValue(0) @@ -234,9 +232,7 @@ describe('extractTar path validation integration', () => { } catch (err) { expect(err).toBeInstanceOf(CacheIntegrityError) expect((err as CacheIntegrityError).code).toBe('PARSE_ERROR') - expect((err as CacheIntegrityError).message).toMatch( - /tar parse error/ - ) + expect((err as CacheIntegrityError).message).toMatch(/tar parse error/) } expect(execMock).not.toHaveBeenCalled() expect(mkdirMock).not.toHaveBeenCalled() @@ -341,9 +337,7 @@ describe('extractTar path validation integration', () => { pathValidation: 'warn' }) - expect(listMock.mock.calls[0][1]).toBe( - CompressionMethod.ZstdWithoutLong - ) + expect(listMock.mock.calls[0][1]).toBe(CompressionMethod.ZstdWithoutLong) }) }) }) diff --git a/packages/cache/src/cache.ts b/packages/cache/src/cache.ts index e4a0f7ac..307392a2 100644 --- a/packages/cache/src/cache.ts +++ b/packages/cache/src/cache.ts @@ -17,9 +17,7 @@ import {HttpClientError} from '@actions/http-client' export type {DownloadOptions, UploadOptions} export {CacheIntegrityError} -export type { - CacheIntegrityErrorCode -} from './internal/cacheIntegrityError.js' +export type {CacheIntegrityErrorCode} from './internal/cacheIntegrityError.js' export type { PathValidationMode, PathValidationViolation diff --git a/packages/cache/src/internal/listAndValidate.ts b/packages/cache/src/internal/listAndValidate.ts index e8200395..da3b9ecc 100644 --- a/packages/cache/src/internal/listAndValidate.ts +++ b/packages/cache/src/internal/listAndValidate.ts @@ -172,9 +172,7 @@ async function streamArchiveTo( const tail = stderrTruncated ? ' (stderr truncated)' : '' reject( new Error( - `zstd ${cause}${ - zstdStderr ? `: ${zstdStderr.trim()}` : '' - }${tail}` + `zstd ${cause}${zstdStderr ? `: ${zstdStderr.trim()}` : ''}${tail}` ) ) } diff --git a/packages/cache/src/internal/pathValidation.ts b/packages/cache/src/internal/pathValidation.ts index 3caa6707..c5702eca 100644 --- a/packages/cache/src/internal/pathValidation.ts +++ b/packages/cache/src/internal/pathValidation.ts @@ -55,7 +55,7 @@ export type ValidationResult = */ export interface PreparedAllowedRoots { readonly caseInsensitive: boolean - readonly roots: ReadonlyArray + readonly roots: readonly PreparedRoot[] } interface PreparedRoot { @@ -231,7 +231,10 @@ function segmentHasGlob(seg: string): boolean { function expandEnvVars(input: string): string { let result = input // ${VAR} — see segmentHasGlob for the polynomial-redos rationale. - result = result.replace(/\$\{([^}]+)\}/g, (_, name) => process.env[name] ?? '') // lgtm[js/polynomial-redos] + result = result.replace( + /\$\{([^}]+)\}/g, + (_, name) => process.env[name] ?? '' + ) // lgtm[js/polynomial-redos] // $VAR (POSIX-style identifier) result = result.replace( /\$([A-Za-z_][A-Za-z0-9_]*)/g, @@ -456,13 +459,6 @@ function resolveEntry(entryPath: string, extractCwd: string): string { return path.resolve(extractCwd, native) } -function isUnderAnyRoot(resolved: string, roots: string[]): boolean { - for (const root of roots) { - if (isUnderRoot(resolved, root)) return true - } - return false -} - /** * Precompute a {@link PreparedAllowedRoots} from a raw roots array. Call * once per `listAndValidate` invocation and reuse for every entry. diff --git a/packages/cache/src/internal/tar.ts b/packages/cache/src/internal/tar.ts index 5425dfc1..33cd29f1 100644 --- a/packages/cache/src/internal/tar.ts +++ b/packages/cache/src/internal/tar.ts @@ -374,10 +374,9 @@ function reportViolations( core.info(formatViolationSummary(violations)) for (const v of violations) { core.debug( - `path-validation: code=${v.code} type=${v.entryType} path=${v.path}` + - (v.linkpath ? ` linkpath=${v.linkpath}` : '') + - ` resolved=${v.resolved}` + - ` reason=${v.reason}` + `path-validation: code=${v.code} type=${v.entryType} path=${v.path}${ + v.linkpath ? ` linkpath=${v.linkpath}` : '' + } resolved=${v.resolved} reason=${v.reason}` ) } }