Fix lint issues

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