parent
8aebe1c138
commit
8e97add9bf
5 changed files with 145 additions and 59 deletions
104
package-lock.json
generated
104
package-lock.json
generated
|
|
@ -19,7 +19,7 @@
|
|||
"@biomejs/biome": "2.4.13",
|
||||
"@smithy/property-provider": "^4.3.4",
|
||||
"@types/node": "^25.9.1",
|
||||
"@vitest/coverage-v8": "^4.1.6",
|
||||
"@vitest/coverage-v8": "4.1.5",
|
||||
"aws-sdk-client-mock": "^4.1.0",
|
||||
"esbuild": "^0.28.0",
|
||||
"generate-license-file": "^4.2.1",
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
"memfs": "^4.57.2",
|
||||
"standard-version": "^9.5.0",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^4.1.6"
|
||||
"vitest": "4.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16.3.0"
|
||||
|
|
@ -2878,14 +2878,14 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vitest/coverage-v8": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.7.tgz",
|
||||
"integrity": "sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz",
|
||||
"integrity": "sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bcoe/v8-coverage": "^1.0.2",
|
||||
"@vitest/utils": "4.1.7",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"ast-v8-to-istanbul": "^1.0.0",
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
|
|
@ -2899,8 +2899,8 @@
|
|||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vitest/browser": "4.1.7",
|
||||
"vitest": "4.1.7"
|
||||
"@vitest/browser": "4.1.5",
|
||||
"vitest": "4.1.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vitest/browser": {
|
||||
|
|
@ -2909,16 +2909,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz",
|
||||
"integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz",
|
||||
"integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.1.7",
|
||||
"@vitest/utils": "4.1.7",
|
||||
"@vitest/spy": "4.1.5",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"chai": "^6.2.2",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
|
|
@ -2927,13 +2927,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz",
|
||||
"integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz",
|
||||
"integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.1.7",
|
||||
"@vitest/spy": "4.1.5",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
|
|
@ -2954,9 +2954,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz",
|
||||
"integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz",
|
||||
"integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -2967,13 +2967,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz",
|
||||
"integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz",
|
||||
"integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.1.7",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
|
|
@ -2981,14 +2981,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz",
|
||||
"integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz",
|
||||
"integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.7",
|
||||
"@vitest/utils": "4.1.7",
|
||||
"@vitest/pretty-format": "4.1.5",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
|
|
@ -2997,9 +2997,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz",
|
||||
"integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz",
|
||||
"integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
|
@ -3007,13 +3007,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz",
|
||||
"integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz",
|
||||
"integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.7",
|
||||
"@vitest/pretty-format": "4.1.5",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
|
|
@ -9995,19 +9995,19 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz",
|
||||
"integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==",
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz",
|
||||
"integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.1.7",
|
||||
"@vitest/mocker": "4.1.7",
|
||||
"@vitest/pretty-format": "4.1.7",
|
||||
"@vitest/runner": "4.1.7",
|
||||
"@vitest/snapshot": "4.1.7",
|
||||
"@vitest/spy": "4.1.7",
|
||||
"@vitest/utils": "4.1.7",
|
||||
"@vitest/expect": "4.1.5",
|
||||
"@vitest/mocker": "4.1.5",
|
||||
"@vitest/pretty-format": "4.1.5",
|
||||
"@vitest/runner": "4.1.5",
|
||||
"@vitest/snapshot": "4.1.5",
|
||||
"@vitest/spy": "4.1.5",
|
||||
"@vitest/utils": "4.1.5",
|
||||
"es-module-lexer": "^2.0.0",
|
||||
"expect-type": "^1.3.0",
|
||||
"magic-string": "^0.30.21",
|
||||
|
|
@ -10035,12 +10035,12 @@
|
|||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.1.7",
|
||||
"@vitest/browser-preview": "4.1.7",
|
||||
"@vitest/browser-webdriverio": "4.1.7",
|
||||
"@vitest/coverage-istanbul": "4.1.7",
|
||||
"@vitest/coverage-v8": "4.1.7",
|
||||
"@vitest/ui": "4.1.7",
|
||||
"@vitest/browser-playwright": "4.1.5",
|
||||
"@vitest/browser-preview": "4.1.5",
|
||||
"@vitest/browser-webdriverio": "4.1.5",
|
||||
"@vitest/coverage-istanbul": "4.1.5",
|
||||
"@vitest/coverage-v8": "4.1.5",
|
||||
"@vitest/ui": "4.1.5",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
"@biomejs/biome": "2.4.13",
|
||||
"@smithy/property-provider": "^4.3.4",
|
||||
"@types/node": "^25.9.1",
|
||||
"@vitest/coverage-v8": "^4.1.6",
|
||||
"@vitest/coverage-v8": "4.1.5",
|
||||
"aws-sdk-client-mock": "^4.1.0",
|
||||
"esbuild": "^0.28.0",
|
||||
"generate-license-file": "^4.2.1",
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
"memfs": "^4.57.2",
|
||||
"standard-version": "^9.5.0",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^4.1.6"
|
||||
"vitest": "4.1.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.2",
|
||||
|
|
|
|||
|
|
@ -274,6 +274,20 @@ export function getBooleanInput(name: string, options?: core.InputOptions & { de
|
|||
// O_NOFOLLOW is undefined on Windows. This sets it to 0 if it's not defined.
|
||||
const O_NOFOLLOW: number = (fs.constants as { O_NOFOLLOW?: number }).O_NOFOLLOW ?? 0;
|
||||
|
||||
export function isAllowListed(filePath: string): boolean {
|
||||
// Kubelet projects service-account tokens through a symlink chain
|
||||
// (token -> ..data/token, ..data -> ..<timestamp>/). The containing path is
|
||||
// kubelet-controlled, so we allow symlink-following reads of this fixed
|
||||
// location only.
|
||||
const KUBERNETES_TOKEN_PATH_REGEX = /^\/var\/run\/secrets\/[^/]+\/serviceaccount\/token$/;
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
// No Kubernetes token paths on Windows
|
||||
return KUBERNETES_TOKEN_PATH_REGEX.test(path.posix.normalize(filePath));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isSymlink(filePath: string): boolean {
|
||||
try {
|
||||
return fs.lstatSync(filePath).isSymbolicLink();
|
||||
|
|
@ -305,10 +319,14 @@ function assertRegularFile(fd: number, filePath: string): void {
|
|||
// ELOOP: too many symbolic links (from NOFOLLOW)
|
||||
|
||||
export function readFileUtf8(filePath: string): string | null {
|
||||
const allowSymlink = isAllowListed(filePath);
|
||||
if (!allowSymlink) {
|
||||
refuseSymlinkOnPath(filePath);
|
||||
}
|
||||
const openFlags = fs.constants.O_RDONLY | (allowSymlink ? 0 : O_NOFOLLOW);
|
||||
let fd: number;
|
||||
try {
|
||||
fd = fs.openSync(filePath, fs.constants.O_RDONLY | O_NOFOLLOW);
|
||||
fd = fs.openSync(filePath, openFlags);
|
||||
} catch (err) {
|
||||
const code = (err as NodeJS.ErrnoException).code;
|
||||
if (code === 'ENOENT') return null;
|
||||
|
|
|
|||
|
|
@ -154,6 +154,75 @@ describe('Configure AWS Credentials helpers', {}, () => {
|
|||
fs.mkdirSync('/dir/subdir', { recursive: true });
|
||||
expect(() => helpers.readFileUtf8('/dir/subdir')).toThrow(/not a regular file/);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'follows the kubelet projected-token symlink chain at /var/run/secrets/*/serviceaccount/token',
|
||||
() => {
|
||||
fs.mkdirSync('/var/run/secrets/eks.amazonaws.com/serviceaccount/..2026_05_28_00_00_00.123', {
|
||||
recursive: true,
|
||||
});
|
||||
fs.writeFileSync(
|
||||
'/var/run/secrets/eks.amazonaws.com/serviceaccount/..2026_05_28_00_00_00.123/token',
|
||||
'jwt-token',
|
||||
);
|
||||
fs.symlinkSync(
|
||||
'..2026_05_28_00_00_00.123',
|
||||
'/var/run/secrets/eks.amazonaws.com/serviceaccount/..data',
|
||||
);
|
||||
fs.symlinkSync(
|
||||
'..data/token',
|
||||
'/var/run/secrets/eks.amazonaws.com/serviceaccount/token',
|
||||
);
|
||||
expect(helpers.readFileUtf8('/var/run/secrets/eks.amazonaws.com/serviceaccount/token')).toBe('jwt-token');
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'still refuses symlinks at lookalike paths outside the allowlist',
|
||||
() => {
|
||||
fs.mkdirSync('/var/run/secrets/eks.amazonaws.com/serviceaccount', { recursive: true });
|
||||
fs.writeFileSync('/var/run/secrets/eks.amazonaws.com/serviceaccount/secret', 'jwt-token');
|
||||
fs.symlinkSync(
|
||||
'/var/run/secrets/eks.amazonaws.com/serviceaccount/secret',
|
||||
'/var/run/secrets/eks.amazonaws.com/serviceaccount/token2',
|
||||
);
|
||||
expect(() =>
|
||||
helpers.readFileUtf8('/var/run/secrets/eks.amazonaws.com/serviceaccount/token2'),
|
||||
).toThrow(/Refusing .* \(.* symbolic link\)/);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('isAllowListed', {}, () => {
|
||||
it.skipIf(process.platform === 'win32')('matches the canonical kubelet projected-token path', () => {
|
||||
expect(
|
||||
helpers.isAllowListed('/var/run/secrets/eks.amazonaws.com/serviceaccount/token'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
helpers.isAllowListed('/var/run/secrets/kubernetes.io/serviceaccount/token'),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')('rejects nested or unrelated paths', () => {
|
||||
expect(helpers.isAllowListed('/var/run/secrets/serviceaccount/token')).toBe(false);
|
||||
expect(
|
||||
helpers.isAllowListed('/var/run/secrets/a/b/serviceaccount/token'),
|
||||
).toBe(false);
|
||||
expect(
|
||||
helpers.isAllowListed('/var/run/secrets/eks.amazonaws.com/serviceaccount/token2'),
|
||||
).toBe(false);
|
||||
expect(
|
||||
helpers.isAllowListed('/etc/var/run/secrets/foo/serviceaccount/token'),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === 'win32')('normalizes path traversal attempts', () => {
|
||||
expect(
|
||||
helpers.isAllowListed(
|
||||
'/var/run/secrets/foo/serviceaccount/../../../../etc/passwd',
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeFileUtf8', {}, () => {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import { run } from '../src/index';
|
|||
import * as profileManager from '../src/profileManager';
|
||||
import mocks from './mockinputs.test';
|
||||
|
||||
vi.mock('node:fs');
|
||||
|
||||
const mockedSTSClient = mockClient(STSClient);
|
||||
|
||||
describe('Configure AWS Credentials', {}, () => {
|
||||
|
|
@ -153,7 +155,6 @@ describe('Configure AWS Credentials', {}, () => {
|
|||
vi.spyOn(core, 'getInput').mockImplementation(mocks.getInput(mocks.WEBIDENTITY_TOKEN_FILE_INPUTS));
|
||||
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
|
||||
mockedSTSClient.on(GetCallerIdentityCommand).resolvesOnce({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||
vi.mock('node:fs');
|
||||
vol.reset();
|
||||
fs.mkdirSync('/home/github', { recursive: true });
|
||||
fs.writeFileSync('/home/github/file.txt', 'test-token');
|
||||
|
|
@ -373,7 +374,6 @@ describe('Configure AWS Credentials', {}, () => {
|
|||
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolves(mocks.outputs.STS_CREDENTIALS);
|
||||
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';
|
||||
vi.mock('node:fs');
|
||||
vol.reset();
|
||||
fs.mkdirSync('/home/github', { recursive: true });
|
||||
fs.writeFileSync('/home/github/file.txt', 'test-token');
|
||||
|
|
@ -828,7 +828,6 @@ describe('Configure AWS Credentials', {}, () => {
|
|||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockedSTSClient.reset();
|
||||
vi.mock('node:fs');
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue