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
1 change: 1 addition & 0 deletions .github/workflows/1874-diagnose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: stall-evidence-${{ matrix.arch }}-${{ matrix.mode }}
include-hidden-files: true
path: |
stall-summary.txt
.tmp/stall-failure-*.log
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ jobs:
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: xctest-host-results-${{ github.run_id }}-${{ github.run_attempt }}
include-hidden-files: true
path: .tmp/xctest-host
if-no-files-found: warn

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/mutation-affected.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ jobs:
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mutation-affected-select
include-hidden-files: true
path: .tmp/mutation/lane-envelope.json
if-no-files-found: warn

Expand Down Expand Up @@ -122,6 +123,7 @@ jobs:
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mutation-affected-shard-${{ matrix.name }}
include-hidden-files: true
path: |
.tmp/mutation/mutation.json
.tmp/mutation/mutation.html
Expand Down Expand Up @@ -175,6 +177,7 @@ jobs:
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mutation-affected
include-hidden-files: true
path: |
.tmp/mutation/shards
.tmp/mutation/lane-envelope.json
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/mutation-weekly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ jobs:
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mutation-shard-${{ matrix.name }}
include-hidden-files: true
path: |
.tmp/mutation/mutation.json
.tmp/mutation/lane-envelope.json
Expand Down Expand Up @@ -160,6 +161,7 @@ jobs:
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mutation-decision-kernels
include-hidden-files: true
path: |
.tmp/mutation/shards
.tmp/mutation/lane-envelope.json
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/replays-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ jobs:
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: parser-fuzz-run-${{ github.run_id }}-${{ github.run_attempt }}
include-hidden-files: true
path: .tmp/fuzz
if-no-files-found: warn

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test-app-build-cache.yml
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ jobs:
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ${{ matrix.artifactName }}
include-hidden-files: true
path: .tmp/test-app-artifact/binary.tar.gz
if-no-files-found: error
compression-level: 0 # already gzipped
1 change: 1 addition & 0 deletions .github/workflows/xctest-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,5 +186,6 @@ jobs:
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: xctest-nightly-results-${{ github.run_id }}-${{ github.run_attempt }}
include-hidden-files: true
path: .tmp/xctest-nightly
if-no-files-found: warn
77 changes: 77 additions & 0 deletions test/ci/upload-artifact-hidden-paths.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { expect, test } from 'vitest';
import { parse } from 'yaml';

const repoRoot = path.resolve(import.meta.dirname, '../..');

type Step = { uses?: unknown; with?: Record<string, unknown> };

function uploadSteps(node: unknown): Step[] {
if (Array.isArray(node)) return node.flatMap(uploadSteps);
if (node === null || typeof node !== 'object') return [];
const record = node as Record<string, unknown>;
const self =
typeof record.uses === 'string' && record.uses.startsWith('actions/upload-artifact@')
? [record as Step]
: [];
return [...self, ...Object.values(record).flatMap(uploadSteps)];
}

function isHidden(entry: string): boolean {
return entry
.split('/')
.some((segment) => segment.startsWith('.') && segment !== '.' && segment !== '..');
}

/** Every YAML GitHub reads under a `.github` tree: both extensions, local actions at any depth. */
function offenders(root: string): string[] {
return fs
.readdirSync(root, { recursive: true, encoding: 'utf8' })
.map((entry) => path.join(root, entry))
.filter((file) => /\.ya?ml$/.test(file) && fs.statSync(file).isFile())
.flatMap((file) =>
uploadSteps(parse(fs.readFileSync(file, 'utf8')))
.filter((step) => {
const paths = String(step.with?.path ?? '')
.split('\n')
.map((entry) => entry.trim())
.filter(Boolean);
return paths.some(isHidden) && step.with?.['include-hidden-files'] !== true;
})
.map((step) => `${path.relative(root, file)}: ${step.with?.name ?? '(unnamed)'}`),
);
}

test('every artifact upload that writes a hidden path opts into hidden files', () => {
expect(
offenders(path.join(repoRoot, '.github')),
'these uploads silently discard their hidden paths',
).toEqual([]);
});

test('the scan reaches every shape GitHub accepts', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'hidden-uploads-'));
const plant = (file: string, name: string) => {
fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true });
fs.writeFileSync(
path.join(root, file),
`jobs:\n j:\n steps:\n - uses: actions/upload-artifact@v4\n` +
` with:\n name: ${name}\n path: .tmp/out\n`,
);
};
try {
plant('workflows/dotyaml.yaml', 'yaml-extension');
plant('actions/nested/inner/action.yaml', 'nested-action');
plant('actions/shallow/action.yml', 'shallow-action');

expect(offenders(root).sort()).toEqual([
'actions/nested/inner/action.yaml: nested-action',
'actions/shallow/action.yml: shallow-action',
'workflows/dotyaml.yaml: yaml-extension',
]);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
1 change: 1 addition & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export default defineConfig({
'scripts/__tests__/size-report-package.test.ts',
// Parses CI configuration only, so this action guard needs no device or subprocess lane.
'test/ci/upload-agent-device-artifacts.test.ts',
'test/ci/upload-artifact-hidden-paths.test.ts',
// The size reporter is preserved across a base checkout; its entrypoint and imported
// modules must move as one directory or the Bundle Size lane fails before measuring.
'test/ci/size-workflow.test.ts',
Expand Down
Loading