import { readFile } from 'node:fs/promises'; import { parse } from 'yaml'; import { describe, expect, it } from 'vitest'; const workflowPath = new URL('../../.gitea/workflows/ci.yml', import.meta.url); describe('CI workflow semantic security policy', () => { it('pins actions, minimizes permissions, and leaves no checkout credential', async () => { const source = await readFile(workflowPath, 'utf8'); const workflow = parse(source); expect(workflow.permissions).toEqual({ contents: 'read' }); expect(workflow.on).toHaveProperty('pull_request'); expect(source).not.toContain('${{ secrets.'); for (const job of Object.values(workflow.jobs)) { expect(job.permissions ?? workflow.permissions).toEqual({ contents: 'read' }); for (const step of job.steps ?? []) { if (!step.uses) continue; expect(step.uses).toMatch(/^[^@\s]+@[a-f0-9]{40}$/); expect(step.uses).not.toMatch(/(?:^|\/)cache(?:\/(?:restore|save))?@/); expect(step.with ?? {}).not.toHaveProperty('cache'); if (step.uses.startsWith('actions/checkout@')) { expect(step.with?.['persist-credentials']).toBe(false); } } } }); it('does not invoke repository package hooks before policy or native rebuild', async () => { const source = await readFile(workflowPath, 'utf8'); expect(source).toContain( 'node scripts/security/dependency-policy.mjs package-lock.json ui/package-lock.json', ); expect(source).toContain('node scripts/security/rebuild-native.mjs'); expect(source).not.toContain('npm run security:dependency-policy'); expect(source).not.toContain('npm run ci:rebuild-native'); }); });