diff --git a/.claude/skills/harden-github-action/SKILL.md b/.claude/skills/harden-github-action/SKILL.md new file mode 100644 index 000000000..701aacd0e --- /dev/null +++ b/.claude/skills/harden-github-action/SKILL.md @@ -0,0 +1,474 @@ +--- +name: harden-github-action +description: Use when creating or reviewing GitHub Actions workflows in this repo. Applies all security patterns established during the apollo-ui hardening session — permissions, action pinning, checkout safety, install patterns, secrets handling, fork protection, Turbo caching, artifacts, and minimumReleaseAge. +--- + +# Harden GitHub Action + +## When to Use + +Use when the user asks to: + +- "Create a new GitHub Actions workflow" +- "Harden / secure an existing workflow" +- "Review a workflow for security issues" +- "Add a CI job for X" +- "Why is my workflow failing the security scan?" +- Any phrasing involving `.github/workflows/*.yml` or `.github/actions/*/action.yml` + +Do **not** use this skill for: +- Fixing npm/pnpm security vulnerabilities in `package.json` (use `fix-security-vulnerabilities`) +- General CI debugging unrelated to security patterns + +## Repo Context + +- **Package manager**: pnpm 10.x +- **Node version**: 22 +- **Workspaces**: Turborepo monorepo (`packages/`, `web-packages/`, `apps/`) +- **Dual registry**: npm public + GitHub Packages (`@uipath` scope at `https://npm.pkg.github.com`) +- **Composite install action**: `.github/actions/install-node-deps/action.yml` — always prefer this over manual setup +- **Security scanner**: zizmor (via `security-scan.yml`); suppression config at `zizmor.yml` +- **Workspace quarantine**: `pnpm-workspace.yaml` has `minimumReleaseAge: 20160` (14 days), `blockExoticSubdeps: true`, and a `minimumReleaseAgeExclude` list managed by the weekly `prune-release-age-exemptions.yml` workflow + +--- + +## Rules Reference + +### 1. Permissions — Scope Write Permissions to Jobs, Not Workflows + +**The actual security boundary:** supply-chain-critical write permissions must never appear at workflow level. Everything else is a matter of hygiene. + +#### Permissions safe at workflow level (read-only, no supply-chain risk) +`contents: read` · `pull-requests: read` · `issues: read` · `packages: read` · `checks: read` · `actions: read` + +These cannot modify code, publish packages, or compromise infrastructure. `contents: read` at workflow level is the GitHub/OpenSSF-recommended baseline. + +#### Permissions that must be job-scoped + +| Permission | Why | +|---|---| +| `contents: write` | Can push commits, create releases — direct code tampering | +| `packages: write` | Can publish to registries — supply-chain critical | +| `id-token: write` | OIDC token for cloud auth / npm provenance — impersonation risk | +| `deployments: write` | Can trigger production deployments | +| `pull-requests: write` | Can merge PRs, bypass branch protections | +| `statuses: write` | Can fake commit status checks (bypass CI gates) | +| `issues: write` | Can close/modify issues (low direct risk but keep scoped) | +| `checks: write` | Can fake CI check results | +| `security-events: write` | Can upload SARIF; acceptable at workflow level only for a dedicated security-scanning workflow | + +#### Pattern + +```yaml +# Workflow containing any write-capable job → deny-all + per-job grants +permissions: {} + +jobs: + lint: + permissions: + contents: read + release: + permissions: + contents: write # push version bump + packages: write # publish to GHP + id-token: write # npm provenance — ONLY on publish job, nowhere else +``` + +```yaml +# Purely read-only workflow → workflow-level contents: read is fine +permissions: + contents: read +``` + +**Never use** `permissions: write-all` or `permissions: read-all`. + +--- + +### 2. Action Pinning — Full Commit SHA + +All third-party actions **must** be pinned to a full 40-character commit SHA. Never use `@latest`, `@vX`, or branch tags. + +```yaml +# ✅ Correct +uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 +uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 +uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 +uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 +uses: zizmorcore/zizmor-action@135698455da5c3b3e55f73f4419e481ab68cdd95 # v0.4.1 +uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1.72.0 + +# ❌ Wrong — do not use +uses: actions/checkout@v4 +uses: actions/checkout@latest +uses: actions/checkout@main +``` + +To find the correct SHA for any action version, run: +```bash +gh api repos///git/ref/tags/ --jq '.object.sha' +# If the tag is an annotated tag, dereference it: +gh api repos///git/refs/tags/ --jq '.object.sha' | xargs -I{} gh api repos///git/tags/{} --jq '.object.sha' +``` + +Local first-party composite actions (`.github/actions/*`) do **not** need pinning — use `./.github/actions/install-node-deps` directly. + +--- + +### 3. Checkout Safety — `persist-credentials: false` + +Always add `persist-credentials: false` to `actions/checkout` **unless** the job explicitly needs to push commits (e.g., the release bot that commits version bumps). + +```yaml +# ✅ Standard checkout (read-only) +- name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + +# ✅ Release job exception — must document the exception +# zizmor: ignore[artipacked] +# Credentials needed for semantic-release to push version bumps; no artifacts uploaded in this workflow +- name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + token: ${{ secrets.RELEASE_TOKEN }} +``` + +For jobs that use the default checkout and then push a branch (e.g., `prune-release-age-exemptions.yml`), keep `persist-credentials` at its default (`true`) and document it inline: + +```yaml +# persist-credentials kept (default true) so the job can push the cleanup branch. +- name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +``` + +Every `artipacked` suppression added to `zizmor.yml` **must** include a comment explaining why credentials are needed. + +--- + +### 4. Install Pattern — Composite Action + +Always use `./.github/actions/install-node-deps` instead of manually setting up pnpm and Node. + +```yaml +- name: Install Node dependencies + uses: ./.github/actions/install-node-deps + with: + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} +``` + +**Never:** +- Run `pnpm install` without `--frozen-lockfile` in CI — the composite action always uses it +- Use `pnpm dlx`, `pnpx`, or `npx -y` for packages already in `devDependencies` — use `pnpm exec` instead +- Add `pnpm/action-setup` or `actions/setup-node` manually when the composite action suffices + +--- + +### 5. Secrets — Step-Scoped Only + +Never place secrets in a workflow-level `env:` block. Always scope to the specific step that needs them. + +```yaml +# ❌ Wrong — workflow-level env exposes secrets to all steps +env: + NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + +# ✅ Correct — step-level env +- name: Publish package + env: + GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} + NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + run: pnpm publish:dev "$PACKAGE" "$SUFFIX" +``` + +**`GH_NPM_REGISTRY_TOKEN`** is handled by the composite install action — do not re-expose it at workflow or job level for the install step. Only expose it at the step level for jobs that genuinely need it beyond install (e.g. `pnpm unpublish:dev` in `dev-cleanup.yml`). + +--- + +### 6. Fork PR Protection + +Any job that uses secrets, publishes packages, or modifies shared state **must** include a fork guard: + +```yaml +jobs: + publish: + if: github.event.pull_request.head.repo.fork == false +``` + +**Always use `pull_request` event** (not `pull_request_target`) for PR-triggered workflows. `pull_request_target` runs with repo secrets against the base branch code, which is dangerous for untrusted forks. + +--- + +### 7. Turborepo Cache — Branch-Isolated Keys + +The Turbo cache key **must** include `github.ref_name` for branch isolation. Without it, branches share cache entries and can corrupt each other's build output. + +```yaml +- name: Cache Turborepo + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.ref_name }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.ref_name }}- +``` + +The restore key intentionally omits `github.sha` to allow cache hits from earlier commits on the same branch. + +--- + +### 8. Artifacts — Only Coverage, Short Retention + +Do **not** upload `dist/`, `.turbo/`, or build outputs in failure artifacts — those can contain compiled secrets or sensitive outputs. Only upload `coverage/`. + +```yaml +- name: Upload Coverage + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: coverage + path: coverage/ + retention-days: 7 # success artifacts + +- name: Upload Failure Artifacts + if: failure() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: failure-${{ matrix.check }}-${{ github.sha }} + path: coverage/ # ← coverage only, NOT dist/ or .turbo/ + retention-days: 3 # failure artifacts expire sooner +``` + +--- + +### 9. PR Comment Parsing — Filter by Bot Author + +When extracting data from PR comments written by the workflow bot, always filter by author to prevent injection from non-bot comments: + +```bash +# ✅ Correct — filter by bot author +COMMENT=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \ + --jq '[.[] | select(.body | contains("")) + | select(.user.type == "Bot" or .user.login == "github-actions[bot]") + | .body][0]' 2>/dev/null) || true + +# ❌ Wrong — any user can inject data by posting a comment with the sentinel string +COMMENT=$(gh api ... --jq '[.[] | select(.body | contains("")) | .body][0]') +``` + +--- + +### 10. Vercel CLI — Exact Version Pin + +Pin the Vercel CLI to an exact version number in both the install command and the cache key. Never use `@latest`. + +```yaml +- name: Cache Vercel CLI + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: ~/.npm + key: ${{ runner.os }}-vercel-cli-53.4.0 # ← version in key + +- name: Install Vercel CLI + run: npm install -g vercel@53.4.0 # ← exact version +``` + +When upgrading the Vercel CLI, update both the install command and the cache key together. + +--- + +### 11. `minimumReleaseAge` and `pnpm add` + +`pnpm install --frozen-lockfile` (used in CI) **never re-resolves packages**, so it is unaffected by `minimumReleaseAge`. The lockfile is the contract. + +`pnpm add` **does** re-resolve all packages and **will fail** if any currently-locked package was published within the 14-day quarantine window. + +**When blocked by `minimumReleaseAge`:** + +1. Identify the blocking package from the error message. +2. Add it to `minimumReleaseAgeExclude` in `pnpm-workspace.yaml`. **The version number is required in the comment** — the `prune-release-age-exemptions.yml` workflow reads it to decide when to remove the entry: + +```yaml +# pnpm-workspace.yaml +minimumReleaseAgeExclude: + - some-package # 1.2.3 — reason it was added (too new when pnpm add ran) +``` + +The format is `# `. Without the version, the prune workflow skips the entry and it will never be auto-removed. + +3. The weekly `prune-release-age-exemptions.yml` workflow will open a PR to remove it automatically once that version ages past 14 days. + +**When adding many packages at once** (multiple are blocking): +```bash +# Temporarily disable quarantine, add packages, restore +# 1. Set minimumReleaseAge: 0 in pnpm-workspace.yaml +# 2. Run: pnpm add ... +# 3. Restore minimumReleaseAge: 20160 +# 4. Verify: pnpm install --frozen-lockfile succeeds +``` + +--- + +## Workflow Skeleton + +Use this as a starting point for any new workflow: + +```yaml +name: My Workflow + +on: + pull_request: + branches: + - main + - 'support/**' + +# Deny-all default; jobs grant only what they need. +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + my-job: + name: My Job + runs-on: ubuntu-latest + # Fork guard — remove if this job uses no secrets and touches no shared state + if: github.event.pull_request.head.repo.fork == false + permissions: + contents: read + # pull-requests: write # add only if posting PR comments + + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps + with: + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} + + - name: Cache Turborepo + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.ref_name }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ github.ref_name }}- + + - name: Run checks + run: pnpm +``` + +--- + +## Review Checklist + +Use this checklist when reviewing or hardening any workflow file. + +### Permissions +- [ ] No supply-chain-critical write scope (`contents: write`, `packages: write`, `id-token: write`, `deployments: write`, `statuses: write`) at workflow level +- [ ] Workflows with any write-capable job use `permissions: {}` at workflow level +- [ ] Read-only workflows have at least `permissions: contents: read` at workflow level (or `{}`) +- [ ] Each job lists only the permissions it genuinely needs +- [ ] `id-token: write` appears only on the npm-publish job (provenance) +- [ ] No workflow uses `permissions: write-all` or `permissions: read-all` + +### Action Pinning +- [ ] Every third-party `uses:` is pinned to a full 40-character SHA +- [ ] The SHA has a `# vX.Y.Z` comment showing the human-readable version +- [ ] No `@latest`, `@vX`, or branch-ref tags anywhere +- [ ] First-party composite actions (`./.github/actions/*`) are referenced by path, not SHA + +### Checkout Safety +- [ ] Every `actions/checkout` step has `persist-credentials: false` — OR — +- [ ] The exception is documented with `# zizmor: ignore[artipacked]` and a comment, and the suppression is added to `zizmor.yml` with an explanation + +### Install Pattern +- [ ] All install steps use `./.github/actions/install-node-deps` +- [ ] `registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }}` is passed to the composite action +- [ ] Only `registry-token` is passed to the composite action — `registry-url`/`scope` are baked in +- [ ] No raw `pnpm install` without `--frozen-lockfile` +- [ ] No `pnpm dlx` / `pnpx` / `npx -y` for packages already in devDependencies — use `pnpm exec` + +### Secrets +- [ ] No secrets in workflow-level `env:` blocks +- [ ] No secrets in job-level `env:` blocks (unless unavoidable and documented) +- [ ] `GH_NPM_REGISTRY_TOKEN` and `NPM_AUTH_TOKEN` appear only in the `env:` of the specific step that needs them +- [ ] No secrets interpolated directly into `run:` shell strings — always via `env:` + +### Fork PR Protection +- [ ] Every job that uses secrets has `if: github.event.pull_request.head.repo.fork == false` +- [ ] Every job that publishes packages has the fork guard +- [ ] Every job that modifies shared state (comments, deployments) has the fork guard +- [ ] Workflow uses `pull_request` event, not `pull_request_target` + +### Turbo Cache +- [ ] Cache key includes `github.ref_name` for branch isolation +- [ ] Cache key format: `${{ runner.os }}-turbo-${{ github.ref_name }}-${{ github.sha }}` +- [ ] Restore key format: `${{ runner.os }}-turbo-${{ github.ref_name }}-` + +### Artifacts +- [ ] Failure artifacts upload only `coverage/` — not `dist/`, `.turbo/`, or build outputs +- [ ] Success coverage artifacts: `retention-days: 7` +- [ ] Failure artifacts: `retention-days: 3` + +### PR Comment Parsing +- [ ] Comment lookup filters by bot author: `select(.user.type == "Bot" or .user.login == "github-actions[bot]")` +- [ ] Sentinel strings are unique enough to prevent false matches + +### Vercel Deploy (if applicable) +- [ ] Vercel CLI pinned to exact version: `npm install -g vercel@X.Y.Z` +- [ ] Cache key includes CLI version: `${{ runner.os }}-vercel-cli-X.Y.Z` +- [ ] `permissions: {}` at workflow level (has write-capable jobs) + +### Security Scanning +- [ ] The new workflow will be scanned by zizmor (automatic via `security-scan.yml`) +- [ ] Any new `artipacked` suppression in `zizmor.yml` has a comment explaining why credentials are needed + +--- + +## Common Mistakes + +| Mistake | Correct Pattern | +|---------|----------------| +| `uses: actions/checkout@v4` | Pin to full SHA with `# v4` comment | +| Write scope at workflow level (`contents: write`, `packages: write`, etc.) | Move to the specific job; use `permissions: {}` at workflow level | +| Missing `permissions:` block entirely on a workflow with write jobs | Add `permissions: {}` + per-job grants | +| `persist-credentials: true` on read-only jobs | `persist-credentials: false` unless job pushes | +| `env: NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}` at job level | Move to the specific `env:` block of the step that needs it | +| `pnpm dlx tsx scripts/foo.ts` | `pnpm exec tsx scripts/foo.ts` (tsx is in devDependencies) | +| Cache key missing `github.ref_name` | `${{ runner.os }}-turbo-${{ github.ref_name }}-${{ github.sha }}` | +| Upload `dist/` in failure artifacts | Upload `coverage/` only | +| PR comment parsed without bot-author filter | Add `select(.user.type == "Bot" or .user.login == "github-actions[bot]")` | +| `npm install -g vercel@latest` | `npm install -g vercel@X.Y.Z` (exact version) | +| Fork jobs not guarded | `if: github.event.pull_request.head.repo.fork == false` | +| Missing `# zizmor: ignore[artipacked]` on release checkout | Add suppress comment and update `zizmor.yml` | +| Using `pnpm install --lockfile-only` as a security check | It's a no-op when lockfile is consistent — use CODEOWNERS review instead | + +--- + +## Reference — Known-Good SHA Pins (as of this hardening session) + +These are the verified SHAs in use in this repo. Check for newer versions when creating new workflows. + +| Action | SHA | Version | +|--------|-----|---------| +| `actions/checkout` | `34e114876b0b11c390a56381ad16ebd13914f8d5` | v4 | +| `actions/setup-node` | `49933ea5288caeca8642d1e84afbd3f7d6820020` | v4 | +| `actions/cache` | `0057852bfaa89a56745cba8c7296529d2fc39830` | v4 | +| `actions/upload-artifact` | `ea165f8d65b6e75b540449e92b4886f43607fa02` | v4 | +| `actions/github-script` | `f28e40c7f34bde8b3046d885e986cb6290c5673b` | v7 | +| `pnpm/action-setup` | `b906affcce14559ad1aafd4ab0e942779e9f58b1` | v4 | +| `zizmorcore/zizmor-action` | `135698455da5c3b3e55f73f4419e481ab68cdd95` | v0.4.1 | +| `reviewdog/action-actionlint` | `6fb7acc99f4a1008869fa8a0f09cfca740837d9d` | v1.72.0 | + +Always verify the SHA is still correct before using it in a new workflow: +```bash +gh api repos/actions/checkout/git/ref/tags/v4 --jq '.object.sha' +``` diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f336245c9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Normalize line endings on commit; check out as LF. +* text=auto eol=lf + +# Shell scripts must stay LF on Windows checkouts so they execute on Linux runners. +*.sh text eol=lf + +# Lockfile is generated; mark as text but discourage diffs. +pnpm-lock.yaml text eol=lf linguist-generated=true diff --git a/.github/actions/install-node-deps/action.yml b/.github/actions/install-node-deps/action.yml new file mode 100644 index 000000000..2b974d927 --- /dev/null +++ b/.github/actions/install-node-deps/action.yml @@ -0,0 +1,31 @@ +name: Install Node dependencies +description: Sets up pnpm + Node and runs `pnpm install --frozen-lockfile` against the @uipath GHP registry. + +inputs: + node-version: + description: Node major version + required: false + default: "22" + registry-token: + description: GitHub PAT with read:packages for the @uipath GHP registry. + required: true + +runs: + using: composite + steps: + - name: Setup pnpm + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: ${{ inputs.node-version }} + cache: pnpm + registry-url: "https://npm.pkg.github.com" + scope: "@uipath" + + - name: Install dependencies + shell: bash + env: + NODE_AUTH_TOKEN: ${{ inputs.registry-token }} + run: pnpm install --frozen-lockfile diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 44b0dcac4..0f8c1f008 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -49,145 +49,296 @@ apollo-ui/ - All packages depend on `apollo-core` (design tokens) - Web components depend on framework packages -## Coding Guidelines +## General Guidelines + +- **Think critically**: When asked "does this make sense?" or given a plan, analyze it before agreeing. Push back on mistakes. +- **Plan before acting**: For major changes (refactors, new features, behavior changes), propose what you'll change and why before writing code. +- **Do not gold-plate**: Complete the task; do not add unrequested features, files, or abstractions. +- **No documentation files**: Do not create `*.md` files unless the user explicitly requests them. + +--- + +## GitHub Actions Security + +Flag these issues immediately when editing or reviewing any `.github/workflows/*.yml` or `.github/actions/*/action.yml` file. + +### Permissions + +**Safe at workflow level** (read-only, no supply-chain risk): +`contents: read`, `pull-requests: read`, `issues: read`, `packages: read`, `checks: read`, `actions: read` + +**Must be job-scoped** — never at workflow level: +| Permission | Risk | +|---|---| +| `contents: write` | Can push code / create releases | +| `packages: write` | Can publish to registries — supply chain critical | +| `id-token: write` | OIDC token; can impersonate workflow to cloud providers | +| `deployments: write` | Can trigger production deployments | +| `pull-requests: write` | Can merge PRs, bypass branch protections | +| `statuses: write` | Can fake commit status checks | +| `issues: write` | Can close/modify issues | +| `checks: write` | Can fake CI check results | +| `security-events: write` | Can upload SARIF; keep job-scoped unless the whole workflow is a dedicated security scanner | + +**Pattern:** +- Workflows with any write-capable job → `permissions: {}` at workflow level + explicit per-job grants +- Purely read-only workflows → `permissions: contents: read` at workflow level is sufficient + +```yaml +# Workflow with mixed jobs — deny-all + per-job grants +permissions: {} + +jobs: + lint: + permissions: + contents: read + publish: + permissions: + contents: write # push version bump + packages: write # publish to GHP + id-token: write # npm provenance — only ever on the publish job +``` -### Naming Conventions +Flag: missing `permissions:` block, `write-all`, `read-all`, or any of the must-be-job-scoped permissions at workflow level. -**React Components:** -- Prefix: `Ap*` (e.g., `ApButton`, `ApTextField`, `ApCard`) -- File naming: PascalCase (e.g., `ApButton.tsx`) +### Action Pinning — Full Commit SHA Required -**Files:** -- Components: PascalCase (`ApButton.tsx`) -- Utilities: camelCase (`formatDate.ts`) -- Tests: `*.test.ts` or `*.spec.ts` -- Stories: `*.stories.tsx` +All third-party `uses:` must be pinned to a full 40-character SHA with a human-readable version comment. -**TypeScript:** -- Strict mode enabled - use proper types, avoid `any` -- Prefer type inference where clear -- Use generics for reusable components -- Export types alongside implementations +```yaml +# Correct +uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -### Component Patterns +# Wrong — flag these +uses: actions/checkout@v4 +uses: actions/checkout@latest +uses: actions/checkout@main +``` -- Use design tokens from `apollo-core` (never hardcode colors/spacing) -- Prefer composition over inheritance -- Keep components focused and single-purpose -- Follow existing patterns in the codebase -- **No new Emotion or MUI usage in `apollo-react`** — see Styling Standards below +Known-good SHAs in use in this repo: + +| Action | SHA | Version | +|--------|-----|---------| +| `actions/checkout` | `34e114876b0b11c390a56381ad16ebd13914f8d5` | v4 | +| `actions/setup-node` | `49933ea5288caeca8642d1e84afbd3f7d6820020` | v4 | +| `actions/cache` | `0057852bfaa89a56745cba8c7296529d2fc39830` | v4 | +| `actions/upload-artifact` | `ea165f8d65b6e75b540449e92b4886f43607fa02` | v4 | +| `actions/github-script` | `f28e40c7f34bde8b3046d885e986cb6290c5673b` | v7 | +| `pnpm/action-setup` | `b906affcce14559ad1aafd4ab0e942779e9f58b1` | v4 | + +First-party composite actions (`./.github/actions/*`) are referenced by path — no SHA needed. + +### Checkout — `persist-credentials: false` + +Always set `persist-credentials: false` on `actions/checkout` unless the job explicitly pushes commits (e.g., the release job). + +```yaml +# Correct for read-only jobs +- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + +# Release job exception — must document with zizmor suppress comment +# zizmor: ignore[artipacked] +# Credentials needed for semantic-release to push version bumps +- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + token: ${{ secrets.RELEASE_TOKEN }} +``` + +Flag: `actions/checkout` without `persist-credentials: false` on jobs that do not push. + +### Secrets — Step-Scoped Only + +Never put secrets in workflow-level or job-level `env:` blocks. Always scope to the specific step that needs them. + +```yaml +# Wrong — flag this +env: + NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + +# Correct +- name: Publish + env: + NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + run: pnpm publish:dev ... +``` + +Flag: `${{ secrets.* }}` appearing in a workflow-level or job-level `env:` block. + +### Fork PR Protection + +Any job that uses secrets, publishes packages, or modifies shared state must include a fork guard: + +```yaml +if: github.event.pull_request.head.repo.fork == false +``` + +Also flag: use of `pull_request_target` event for PR-triggered workflows — this passes repo secrets to untrusted fork code. Use `pull_request` instead. + +### Install Pattern — Use Composite Action + +Always use `./.github/actions/install-node-deps` instead of manually setting up pnpm and Node. + +```yaml +# Correct +- uses: ./.github/actions/install-node-deps + with: + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} + +# Wrong — flag manual setup when the composite action suffices +- uses: pnpm/action-setup@... +- uses: actions/setup-node@... +- run: pnpm install +``` + +Never run `pnpm install` without `--frozen-lockfile` in CI. + +Flag: `pnpm dlx`, `pnpx`, or `npx -y` for packages that are already in `devDependencies` — use `pnpm exec` instead. + +### Turborepo Cache — Branch-Isolated Key + +The cache key must include `github.ref_name` for branch isolation: + +```yaml +# Correct +key: ${{ runner.os }}-turbo-${{ github.ref_name }}-${{ github.sha }} +restore-keys: | + ${{ runner.os }}-turbo-${{ github.ref_name }}- + +# Wrong — missing ref_name causes branches to share cache +key: ${{ runner.os }}-turbo-${{ github.sha }} +``` + +### Artifacts — Coverage Only, Short Retention + +Do not upload `dist/`, `.turbo/`, or build outputs in failure artifacts. Only upload `coverage/`. + +```yaml +# Correct +- name: Upload Failure Artifacts + if: failure() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + path: coverage/ # NOT dist/ or .turbo/ + retention-days: 3 # failure artifacts +``` + +Success coverage artifacts: `retention-days: 7`. Failure artifacts: `retention-days: 3`. -### Styling Standards (apollo-react) +--- -The `apollo-react` package is migrating from Emotion/MUI to Tailwind CSS + `apollo-wind`. **All new code must use Tailwind patterns.** +## pnpm / Node + +- Always use `pnpm install --frozen-lockfile` in CI. The composite action handles this. +- Use `pnpm exec ` for CLI tools already in `devDependencies`. Never `pnpm dlx` / `pnpx` / `npx -y` for them. +- `pnpm install --frozen-lockfile` is unaffected by `minimumReleaseAge` (it never re-resolves). `pnpm add` re-resolves and may fail if locked packages were published within the 14-day quarantine. +- When `pnpm add` is blocked by `minimumReleaseAge`, add the offending package to `minimumReleaseAgeExclude` in `pnpm-workspace.yaml`. **The version must be in the comment** — the prune workflow reads it to know when to remove the entry. Format: `- 'pkg' # 1.2.3 — reason`. Flag any entry that is missing the version. + +```yaml +# ✅ correct +minimumReleaseAgeExclude: + - 'next' # 16.2.6 — locked version too new when added + +# ❌ missing version — prune workflow will never remove this +minimumReleaseAgeExclude: + - 'next' # too new when added +``` +- Pin the Vercel CLI to an exact version: `npm install -g vercel@X.Y.Z` — never `@latest`. + +--- + +## TypeScript / React Patterns (apollo-react) + +### No New Styled-Components or MUI + +The `apollo-react` package is migrating from Emotion/MUI to Tailwind CSS + `apollo-wind`. All new code must use Tailwind. **Block PRs that introduce:** - New imports from `@emotion/styled`, `@emotion/react`, or usage of `styled.*` / `css` helpers - New `*.styles.ts` files - New `@mui/material/*` component imports for building UI (existing MUI theme overrides in `theme/` are exempt) -- New usage of `Ap*` components from `@uipath/apollo-react` (these are MUI wrappers — use `apollo-wind` components instead) +- New `Ap*` components from `@uipath/apollo-react` used to build other components (these are MUI wrappers — use `apollo-wind` components instead) **Flag for migration when:** - A PR significantly modifies an existing file that uses styled-components or MUI — recommend migrating the touched component to Tailwind as part of the change **Approved patterns:** - Tailwind utility classes as static literal strings in JSX -- `cn()` from `@uipath/apollo-wind` for conflicting class overrides +- `cn()` from `@uipath/apollo-wind` only when classes conflict or need overrides - CSS custom properties (`style` prop) for dynamic dimensions - Existing MUI theme overrides in `packages/apollo-react/src/theme/` (maintenance only) -### Testing Requirements +### Naming Conventions -**Required (Library Code):** -- All code in `packages/` and `web-packages/` -- Unit tests for utilities and hooks -- Component tests for critical UI behavior -- Visual regression tests for components +- React components: `Ap*` prefix (e.g., `ApButton`, `ApTextField`) +- Files: PascalCase for components, camelCase for utilities +- Tests: `*.test.ts` / `*.spec.ts` +- Stories: `*.stories.tsx` -**NOT Required (Demo Code):** -- Code in `apps/` folder (Storybook, playgrounds) -- Showcase/demo applications +### TypeScript -### Documentation +- Strict mode enabled — use proper types, avoid `any` +- Export types alongside implementations +- Use generics for reusable components -- JSDoc comments for public APIs -- Storybook stories for all components -- README.md in each package with usage examples - -## Build-Time vs Runtime Security Model - -**This is critical for security reviews:** - -### Build-Time Scripts (`packages/*/scripts/*.ts`) -✅ **Safe/Acceptable:** -- Run in trusted CI/CD environment with repository files -- Process trusted sources (Figma exports, repository structure) -- Path operations on repository files -- Processing files from `src/icons/svg/` (trusted design team exports) -- String operations on folder names from repository structure -- Missing input validation for build-time configuration -- Recursive directory traversal with depth limits -- File system operations in `scripts/` folders - -### Runtime Code (Components, Hooks, Utilities) -⚠️ **Requires Strict Security:** -- Runs in user applications -- Validate all user inputs -- Sanitize data before rendering -- Prevent XSS, prototype pollution, injection attacks -- Missing input validation on user-facing APIs is a security issue - -### Context-Specific Patterns -- `new Function()` in library config: OK (developer-defined, not user input) -- SVG processing in build scripts: OK (trusted Figma source) -- `innerHTML` in dev.ts: OK (dev-only file with static markup) -- Regex patterns for internal file matching: OK (build-time only) - -## What to Flag as Security Issues - -**Flag these ONLY in runtime code:** -- Secrets/credentials in code or configs -- XSS vulnerabilities in React components -- Prototype pollution in runtime utilities -- Missing input validation on user-facing APIs -- Unsafe dependencies with known CVEs -- Template injection in user-controlled content - -**DO NOT flag in build scripts:** -- Theoretical security issues in trusted build-time contexts -- Path operations on repository files -- File processing from known trusted sources +--- -## Code Review Approach +## Build-Time vs Runtime Security + +**Build-time scripts** (`packages/*/scripts/*.ts`) run in trusted CI. Path operations on repository files, processing trusted Figma exports, and recursive directory traversal are acceptable. Do not flag theoretical issues in this context. -When providing suggestions or reviewing code: +**Runtime code** (components, hooks, utilities) runs in user applications. Flag: missing input validation, XSS, prototype pollution, unsafe dependencies with known CVEs, template injection. -1. **Summary**: Brief overview of what's being done -2. **Code Quality**: Critical issues with structure, patterns, or best practices -3. **Security**: Real security concerns (distinguish build-time vs runtime) -4. **Type Safety**: TypeScript errors or type issues -5. **Testing**: Missing tests for library code (NOT required for apps/) -6. **Performance**: Significant performance implications +--- + +## Code Review Approach -**Focus on blocking issues** - things that prevent safe merging: +**Block on:** +- Missing `permissions: {}` (or a maximally restrictive scope like `contents: read`) at workflow level +- Unpinned third-party actions (`@v4`, `@latest`, branch tags) +- Missing `persist-credentials: false` on read-only checkouts +- Secrets in workflow or job-level `env:` blocks +- `pnpm dlx` / `npx -y` for packages in devDependencies +- Missing `--frozen-lockfile` on `pnpm install` in CI +- Missing fork guard on jobs that use secrets or publish +- New Emotion styled-components or MUI component usage in `apollo-react` - Breaking changes to public APIs - Security vulnerabilities in runtime code - TypeScript errors -- Missing tests for critical library functionality -- Incorrect use of design tokens -- New Emotion styled-components or MUI component usage in apollo-react (use Tailwind instead) +- Missing tests in `packages/` or `web-packages/` -**Don't block on:** +**Do not block on:** - Minor style/formatting (Biome handles this) -- Theoretical security in build scripts -- Missing tests in playground apps +- Theoretical security issues in build-time scripts +- Missing tests in `apps/` (Storybook, playgrounds) - Minor optimizations -**Be pragmatic:** -- Approve when functionally correct and secure -- Build-time scripts != runtime code -- Test library code, not demos -- Trust the tools (Biome, TypeScript) +--- + +## Coding Guidelines + +### Component Patterns + +- Use design tokens from `apollo-core` (never hardcode colors/spacing) +- Prefer composition over inheritance +- Keep components focused and single-purpose + +### Testing Requirements + +- Required for all code in `packages/` and `web-packages/` +- Unit tests for utilities and hooks +- Component tests for critical UI behavior +- Visual regression tests for components +- NOT required for `apps/` (demo apps, Storybook) + +### Documentation + +- JSDoc comments for public APIs +- Storybook stories for all components +- README in each package with usage examples + +--- ## Component Checklist @@ -196,33 +347,21 @@ When creating new components, verify: - [ ] Uses tokens from `apollo-core` - [ ] Includes TypeScript types - [ ] Has Storybook story -- [ ] Has unit tests (if in packages/ or web-packages/) +- [ ] Has unit tests (if in `packages/` or `web-packages/`) - [ ] Has visual regression tests - [ ] Documented in package README - [ ] No new `@emotion/styled`, `@emotion/react`, or `@mui/material` imports (use Tailwind + apollo-wind) -## Available Resources - -**Scripts (from root):** -- `pnpm build` - Build all packages -- `pnpm dev` - Run all packages in dev mode -- `pnpm test` - Run all tests -- `pnpm lint` - Lint all packages -- `pnpm storybook:dev` - Run Storybook -- `pnpm format` - Format with Biome - -**Package-specific:** -- `pnpm build:packages` - Build only packages/ -- `pnpm dev:react-playground` - Run React playground -- `pnpm test:visual` - Visual regression tests - -**Release:** -- `pnpm release` - Release packages (semantic-release) +--- -## Key Principles +## Available Scripts (from root) -1. **Context matters**: Build-time scripts != runtime code -2. **Pragmatic over perfect**: Functional and secure > style nitpicks -3. **Test what matters**: Library code needs tests, demos don't -4. **Trust the tools**: Biome handles formatting, TypeScript handles types -5. **Design tokens first**: Use apollo-core tokens, never hardcode +- `pnpm build` — Build all packages +- `pnpm dev` — Run all packages in dev mode +- `pnpm test` — Run all tests +- `pnpm lint` — Lint all packages +- `pnpm storybook:dev` — Run Storybook +- `pnpm format` — Format with Biome +- `pnpm build:packages` — Build only packages/ +- `pnpm test:visual` — Visual regression tests +- `pnpm release` — Release packages (semantic-release) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..07582d262 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,96 @@ +version: 2 +updates: + # npm / pnpm workspace dependencies (root + all workspaces auto-discovered) + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + day: monday + time: "06:00" + timezone: America/Los_Angeles + open-pull-requests-limit: 10 + versioning-strategy: increase-if-necessary + labels: + - dependencies + commit-message: + prefix: chore(deps) + prefix-development: chore(deps-dev) + include: scope + groups: + # Routine non-major updates bundled by dependency-type. + # Majors fall through and open individual PRs unless captured by a major-only group below. + production-minor-patch: + dependency-type: production + update-types: + - minor + - patch + development-minor-patch: + dependency-type: development + update-types: + - minor + - patch + + # Majors that MUST move together across the family (one PR per family). + mui-major: + patterns: + - "@mui/*" + - "@emotion/*" + update-types: + - major + lingui-major: + patterns: + - "@lingui/*" + update-types: + - major + tiptap-major: + patterns: + - "@tiptap/*" + update-types: + - major + storybook-major: + patterns: + - "storybook" + - "@storybook/*" + update-types: + - major + radix-ui-major: + patterns: + - "@radix-ui/*" + update-types: + - major + tanstack-major: + patterns: + - "@tanstack/*" + update-types: + - major + + # Bundle security PRs (otherwise each alert opens an individual PR). + security: + applies-to: security-updates + patterns: + - "*" + + # GitHub Actions — pinned by SHA, so updates come through as SHA bumps with tag-comment hints. + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + time: "06:00" + timezone: America/Los_Angeles + open-pull-requests-limit: 5 + labels: + - dependencies + - github-actions + commit-message: + prefix: chore(ci) + include: scope + groups: + actions-minor-patch: + update-types: + - minor + - patch + security: + applies-to: security-updates + patterns: + - "*" diff --git a/.github/scripts/check-release-age-exemptions.mjs b/.github/scripts/check-release-age-exemptions.mjs new file mode 100644 index 000000000..1772d125c --- /dev/null +++ b/.github/scripts/check-release-age-exemptions.mjs @@ -0,0 +1,99 @@ +/** + * Checks which packages in `minimumReleaseAgeExclude` (pnpm-workspace.yaml) have a + * locked version older than the 14-day quarantine and can be removed from the list. + * + * Each non-glob entry must carry its version in the inline comment, e.g.: + * - 'next' # 16.2.6 — reason + * The prune script reads that version to decide when to remove the entry. + */ + +import { execFileSync } from 'node:child_process'; +import { appendFileSync, readFileSync, writeFileSync } from 'node:fs'; + +const QUARANTINE_MS = 14 * 24 * 60 * 60 * 1000; // must match minimumReleaseAge in pnpm-workspace.yaml +const WORKSPACE_FILE = 'pnpm-workspace.yaml'; + +function parseExemptions(yaml) { + const lines = yaml.split('\n'); + const start = lines.findIndex((l) => l.trimEnd() === 'minimumReleaseAgeExclude:'); + if (start === -1) return []; + + const entries = []; + for (let i = start + 1; i < lines.length; i++) { + const line = lines[i]; + if (!/^\s+-/.test(line)) break; + + const pkg = line.replace(/^\s+-\s+/, '').replace(/['"]/g, '').split(/[#\s]/)[0]; + const version = line.match(/#\s*(\d+\.\d+\S*)/)?.[1] ?? null; + + if (pkg && !pkg.includes('*') && version) entries.push({ pkg, version }); + } + return entries; +} + +function publishedAt(pkg, version) { + try { + const times = JSON.parse( + execFileSync('npm', ['view', pkg, 'time', '--json'], { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'], + }), + ); + const date = times[version]; + return date ? new Date(date) : null; + } catch { + return null; + } +} + +// --- Main --- + +const yaml = readFileSync(WORKSPACE_FILE, 'utf8'); +const exemptions = parseExemptions(yaml); + +if (exemptions.length === 0) { + console.log('No versioned exemptions to check.'); + if (process.env.GITHUB_OUTPUT) appendFileSync(process.env.GITHUB_OUTPUT, 'removable=\ncount=0\n'); + process.exit(0); +} + +console.log(`Checking ${exemptions.length} exemption(s): ${exemptions.map((e) => `${e.pkg}@${e.version}`).join(', ')}\n`); + +const now = Date.now(); +const quarantineDays = Math.floor(QUARANTINE_MS / 86_400_000); +const removable = []; + +for (const { pkg, version } of exemptions) { + const published = publishedAt(pkg, version); + if (!published) { + console.log(` ${pkg}@${version}: could not fetch publish date — skipping`); + continue; + } + + const ageDays = Math.floor((now - published.getTime()) / 86_400_000); + + if (ageDays >= quarantineDays) { + console.log(` ${pkg}@${version}: ${ageDays}d old → removable`); + removable.push(pkg); + } else { + console.log(` ${pkg}@${version}: ${ageDays}d old → ${quarantineDays - ageDays}d remaining`); + } +} + +if (removable.length === 0) { + console.log('\nNothing to remove yet.'); + if (process.env.GITHUB_OUTPUT) appendFileSync(process.env.GITHUB_OUTPUT, 'removable=\ncount=0\n'); + process.exit(0); +} + +let updated = yaml; +for (const pkg of removable) { + const escaped = pkg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + updated = updated.replace(new RegExp(`^[ \\t]+-[ \\t]+['"]?${escaped}['"]?[^\\n]*\\n`, 'm'), ''); +} +writeFileSync(WORKSPACE_FILE, updated); +console.log(`\nRemoved from pnpm-workspace.yaml: ${removable.join(', ')}`); + +if (process.env.GITHUB_OUTPUT) { + appendFileSync(process.env.GITHUB_OUTPUT, `removable=${removable.join(',')}\ncount=${removable.length}\n`); +} diff --git a/.github/scripts/test-registry/test-registry.ts b/.github/scripts/test-registry/test-registry.ts index 8fb5c3860..97c22eba9 100644 --- a/.github/scripts/test-registry/test-registry.ts +++ b/.github/scripts/test-registry/test-registry.ts @@ -19,7 +19,9 @@ function testComponent(component: string, baseAppPath: string): TestResult { filter: (src) => !src.includes('node_modules'), }); - // Install dependencies in the temp directory + // Install dependencies in the temp directory. + // No --frozen-lockfile: the shadcn-initialised app has no committed lockfile by design — + // this test simulates a fresh consumer install to verify components resolve correctly. execFileSync('pnpm', ['install'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], diff --git a/.github/workflows/apollo-vertex-auto-merge.yml b/.github/workflows/apollo-vertex-auto-merge.yml index 4400607e8..4130fa722 100644 --- a/.github/workflows/apollo-vertex-auto-merge.yml +++ b/.github/workflows/apollo-vertex-auto-merge.yml @@ -10,8 +10,14 @@ on: paths: - "apps/apollo-vertex/**" +# Deny-all default; the enable-auto-merge job grants only what it needs. +permissions: {} + jobs: enable-auto-merge: + # Never auto-merge a fork PR. Fork PRs require human review regardless of file scope — + # the path-based check below cannot establish trust in the author's commits. + if: github.event.pull_request.head.repo.fork == false permissions: contents: read pull-requests: write diff --git a/.github/workflows/apollo-vertex-lint.yml b/.github/workflows/apollo-vertex-lint.yml index 56365140a..0471871a7 100644 --- a/.github/workflows/apollo-vertex-lint.yml +++ b/.github/workflows/apollo-vertex-lint.yml @@ -7,10 +7,6 @@ on: - "apps/apollo-vertex/**" - ".github/workflows/apollo-vertex-lint.yml" -env: - GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} - concurrency: group: vertex-lint-${{ github.ref }} cancel-in-progress: true @@ -22,21 +18,17 @@ jobs: lint: name: Lint & Format runs-on: ubuntu-latest + if: github.event.pull_request.head.repo.fork == false steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: "22" - cache: "pnpm" + persist-credentials: false - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps + with: + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - name: Lint run: pnpm --filter apollo-vertex lint diff --git a/.github/workflows/apollo-vertex-registry-check.yml b/.github/workflows/apollo-vertex-registry-check.yml index 361a4c790..e46ef53c1 100644 --- a/.github/workflows/apollo-vertex-registry-check.yml +++ b/.github/workflows/apollo-vertex-registry-check.yml @@ -37,29 +37,22 @@ jobs: prepare: name: Prepare needs: detect-changes - if: needs.detect-changes.outputs.has_vertex_changes == 'true' + if: needs.detect-changes.outputs.has_vertex_changes == 'true' && github.event.pull_request.head.repo.fork == false runs-on: ubuntu-latest + permissions: + contents: read outputs: matrix: ${{ steps.matrix.outputs.result }} steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: "22" - registry-url: "https://npm.pkg.github.com" - scope: "@uipath" - cache: "pnpm" + persist-credentials: false - - name: Install dependencies - env: - NODE_AUTH_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - run: pnpm install + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps + with: + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - name: Build registry run: pnpm --filter apollo-vertex registry:build @@ -83,13 +76,13 @@ jobs: echo "result=${MATRIX}" >> "${GITHUB_OUTPUT}" - name: Create base shadcn app - run: cd ${{ runner.temp }} && pnpx shadcn@latest init --preset a0 --template next --name minimal-app + run: cd ${{ runner.temp }} && pnpm dlx shadcn@4.4.0 init --preset a0 --template next --name minimal-app - name: Configure @uipath registry working-directory: ${{ runner.temp }}/minimal-app env: REGISTRY_URL: http://localhost:3000 - run: pnpx tsx ${{ github.workspace }}/.github/scripts/test-registry/configure-registry.ts + run: ${{ github.workspace }}/node_modules/.bin/tsx ${{ github.workspace }}/.github/scripts/test-registry/configure-registry.ts - name: Copy locales into base app run: cp -r apps/apollo-vertex/locales ${{ runner.temp }}/minimal-app/locales @@ -114,8 +107,10 @@ jobs: test-components: name: Test batch ${{ matrix.batch_index }} needs: [detect-changes, prepare] - if: needs.detect-changes.outputs.has_vertex_changes == 'true' + if: needs.detect-changes.outputs.has_vertex_changes == 'true' && github.event.pull_request.head.repo.fork == false runs-on: ubuntu-latest + permissions: + contents: read strategy: fail-fast: false matrix: @@ -124,6 +119,7 @@ jobs: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: + persist-credentials: false sparse-checkout: | .github/scripts/test-registry package.json @@ -152,20 +148,22 @@ jobs: path: ${{ runner.temp }}/serve-dir/r - name: Start registry server - run: npx -y serve ${{ runner.temp }}/serve-dir -p 3000 & + run: pnpm dlx serve@14.2.6 ${{ runner.temp }}/serve-dir -p 3000 & - name: Test registry components env: NODE_AUTH_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} COMPONENTS: ${{ matrix.components }} BASE_APP_PATH: ${{ runner.temp }}/base-app - run: pnpx tsx ${{ github.workspace }}/.github/scripts/test-registry/test-registry.ts + run: pnpm dlx tsx@4.20.6 ${{ github.workspace }}/.github/scripts/test-registry/test-registry.ts registry-check: name: Apollo Vertex Registry Check needs: [detect-changes, test-components] if: always() runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Check results run: | @@ -173,6 +171,10 @@ jobs: echo "No relevant changes detected — skipping registry check" exit 0 fi + if [ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]; then + echo "Fork PR — registry check skipped (secrets not available)" + exit 0 + fi if [ "${{ needs.test-components.result }}" != "success" ]; then echo "Some registry component tests failed" exit 1 diff --git a/.github/workflows/close-stale-prs.yml b/.github/workflows/close-stale-prs.yml index e44e964a6..33833f19f 100644 --- a/.github/workflows/close-stale-prs.yml +++ b/.github/workflows/close-stale-prs.yml @@ -14,14 +14,16 @@ concurrency: group: ${{ github.workflow }} cancel-in-progress: false -permissions: - contents: read - pull-requests: write +# Deny-all default; the close-stale job grants only what it needs. +permissions: {} jobs: close-stale: name: Close stale PRs runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write timeout-minutes: 10 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/commit-lint.yml b/.github/workflows/commit-lint.yml index 3b0a91472..bc3696856 100644 --- a/.github/workflows/commit-lint.yml +++ b/.github/workflows/commit-lint.yml @@ -4,16 +4,15 @@ on: pull_request: types: [opened, edited, synchronize, reopened] -env: - GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} +permissions: + contents: read jobs: commitlint: name: Validate Commit Messages runs-on: ubuntu-latest - permissions: - contents: read + # GH_NPM_REGISTRY_TOKEN is unavailable on fork PRs — skip rather than fail with 401. + if: github.event.pull_request.head.repo.fork == false steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -21,21 +20,14 @@ jobs: fetch-depth: 0 persist-credentials: false - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps with: - node-version: 22 - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install --frozen-lockfile + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - name: Validate commit messages if: github.event_name == 'pull_request' env: BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} - run: npx commitlint --from "${BASE_SHA}" --to "${HEAD_SHA}" --verbose + run: pnpm exec commitlint --from "${BASE_SHA}" --to "${HEAD_SHA}" --verbose diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 84030ce67..1a84d737e 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -2,43 +2,55 @@ name: Dependency Review on: [pull_request] -permissions: - contents: read - pull-requests: write - -env: - GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} +permissions: {} jobs: - dependency-review: - name: Dependencies license check + vulnerability-review: + name: Vulnerability & advisory review runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + # The dependency-review-action requires a pull_request event with the base ref available. + if: github.event_name == 'pull_request' steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 + - name: Dependency Review + # Scans added/changed deps against GitHub Advisory Database. Fails on high+ severity by default. + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + with: + fail-on-severity: high + comment-summary-in-pr: on-failure - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + dependency-review: + name: Dependencies license check + runs-on: ubuntu-latest + if: github.event.pull_request.head.repo.fork == false + permissions: + contents: read + issues: write + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: - node-version: 22 - cache: 'pnpm' + persist-credentials: false - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps + with: + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - name: Check licenses id: check - run: npx -y tsx scripts/check-licenses.ts + run: pnpm exec tsx scripts/check-licenses.ts continue-on-error: true - name: Post or update PR comment - if: "!github.event.pull_request.head.repo.fork" + if: github.event.pull_request.head.repo.fork == false uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | diff --git a/.github/workflows/dev-cleanup.yml b/.github/workflows/dev-cleanup.yml index 180de6c12..4c4c76405 100644 --- a/.github/workflows/dev-cleanup.yml +++ b/.github/workflows/dev-cleanup.yml @@ -7,9 +7,10 @@ on: - main - 'support/**' +# Deny-all default; the cleanup job grants only what it needs. +permissions: {} + env: - GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} TURBO_TELEMETRY_DISABLED: 1 DO_NOT_TRACK: 1 @@ -17,9 +18,10 @@ jobs: cleanup: name: Cleanup Dev Packages runs-on: ubuntu-latest + # Skip fork PRs — cleanup uses publish-scoped tokens; fork PRs must not receive them. + if: github.event.pull_request.head.repo.fork == false permissions: contents: read - pull-requests: write issues: write steps: @@ -27,18 +29,12 @@ jobs: uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ github.event.pull_request.base.ref }} + persist-credentials: false - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps with: - node-version: 22 - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install --frozen-lockfile + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - name: Get published packages from PR comment id: packages @@ -47,27 +43,33 @@ jobs: PR_NUMBER: ${{ github.event.pull_request.number }} run: | COMMENT=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \ - --jq '[.[] | select(.body | contains("")) | .body][0]' 2>/dev/null) || true + --jq '[.[] | select(.body | contains("")) | select(.user.type == "Bot" or .user.login == "github-actions[bot]") | .body][0]' 2>/dev/null) || true if [ -z "$COMMENT" ]; then echo "No dev packages comment found" - echo "packages=" >> $GITHUB_OUTPUT + echo "packages=" >> "$GITHUB_OUTPUT" exit 0 fi # Extract package@version from table format (supports pr123 and pr123.abc1234 formats) # Matches: | `@uipath/package@version-pr123.sha` | status | timestamp | - PACKAGES=$(echo "$COMMENT" | grep -oE '\| `@uipath/[a-z0-9-]+@[0-9.]+-pr[0-9]+(\.[a-z0-9]+)?`' | sed 's/^| `//' | sed 's/`$//' | sort -u) + # Backticks in the grep pattern are literal regex chars, not command substitution. + # shellcheck disable=SC2016 + PACKAGES=$(echo "$COMMENT" | grep -oE '\| `@uipath/[a-z0-9-]+@[0-9.]+-pr[0-9]+(\.[a-z0-9]+)?`' | sed 's/^| `//' | sed "s/\`\$//" | sort -u) # Use delimiter for GitHub output (newlines not supported) - echo "packages<> $GITHUB_OUTPUT - echo "$PACKAGES" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + { + echo "packages<> "$GITHUB_OUTPUT" echo "Found: $PACKAGES" - name: Cleanup dev packages if: steps.packages.outputs.packages != '' env: PACKAGES: ${{ steps.packages.outputs.packages }} + GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} + NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} run: | echo "Cleaning up dev packages..." echo "" diff --git a/.github/workflows/dev-publish.yml b/.github/workflows/dev-publish.yml index b6773d541..3e9e89a6f 100644 --- a/.github/workflows/dev-publish.yml +++ b/.github/workflows/dev-publish.yml @@ -1,8 +1,7 @@ name: Dev Publish -permissions: - contents: read - pull-requests: read +# Deny-all default; jobs grant the minimum they need. +permissions: {} on: pull_request: @@ -12,8 +11,6 @@ on: - 'support/**' env: - GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} TURBO_TELEMETRY_DISABLED: 1 DO_NOT_TRACK: 1 BASE_REF: ${{ github.event.pull_request.base.ref }} @@ -26,6 +23,9 @@ jobs: detect-changes: name: Detect Changed Packages runs-on: ubuntu-latest + permissions: + contents: read + issues: read outputs: has_label: ${{ steps.check-label.outputs.has_label }} packages: ${{ steps.changed.outputs.packages }} @@ -39,6 +39,7 @@ jobs: uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 + persist-credentials: false - name: Check for dev-packages label id: check-label @@ -46,10 +47,10 @@ jobs: LABELS_JSON: ${{ toJSON(github.event.pull_request.labels.*.name) }} run: | if echo "$LABELS_JSON" | jq -e 'index("dev-packages")' > /dev/null; then - echo "has_label=true" >> $GITHUB_OUTPUT + echo "has_label=true" >> "$GITHUB_OUTPUT" echo "✓ dev-packages label is present" else - echo "has_label=false" >> $GITHUB_OUTPUT + echo "has_label=false" >> "$GITHUB_OUTPUT" echo "ℹ️ dev-packages label not present - will cleanup only" fi @@ -60,26 +61,34 @@ jobs: PR_NUMBER: ${{ github.event.pull_request.number }} run: | COMMENT=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \ - --jq '[.[] | select(.body | contains("")) | .body][0]' 2>/dev/null) || true + --jq '[.[] | select(.body | contains("")) | select(.user.type == "Bot" or .user.login == "github-actions[bot]") | .body][0]' 2>/dev/null) || true if [ -z "$COMMENT" ]; then - echo "has_previous=false" >> $GITHUB_OUTPUT - echo "packages=" >> $GITHUB_OUTPUT + { + echo "has_previous=false" + echo "packages=" + } >> "$GITHUB_OUTPUT" exit 0 fi # Extract all package@version from comment (table format) # Matches: | `@uipath/package@version-pr123.sha` | status | timestamp | - PUBLISHED=$(echo "$COMMENT" | grep -oE '\| `@uipath/[a-z0-9-]+@[0-9.]+-pr[0-9]+(\.[a-z0-9]+)?`' | sed 's/^| `//' | sed 's/`$//' | sort -u) + # Backticks in the grep pattern are literal regex chars, not command substitution. + # shellcheck disable=SC2016 + PUBLISHED=$(echo "$COMMENT" | grep -oE '\| `@uipath/[a-z0-9-]+@[0-9.]+-pr[0-9]+(\.[a-z0-9]+)?`' | sed 's/^| `//' | sed "s/\`\$//" | sort -u) if [ -z "$PUBLISHED" ]; then - echo "has_previous=false" >> $GITHUB_OUTPUT - echo "packages=" >> $GITHUB_OUTPUT + { + echo "has_previous=false" + echo "packages=" + } >> "$GITHUB_OUTPUT" else - echo "has_previous=true" >> $GITHUB_OUTPUT - echo "packages<> $GITHUB_OUTPUT - echo "$PUBLISHED" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + { + echo "has_previous=true" + echo "packages<> "$GITHUB_OUTPUT" echo "Found previous versions: $PUBLISHED" fi @@ -92,13 +101,13 @@ jobs: # Convert newline-separated package@version list to JSON array # Use -Rsc: Raw input, Slurp mode, Compact output (single line) MATRIX_JSON=$(echo "$PACKAGES" | jq -Rsc 'split("\n") | map(select(length > 0))') - echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT + echo "matrix=$MATRIX_JSON" >> "$GITHUB_OUTPUT" echo "Previous matrix: $MATRIX_JSON" - name: Get changed packages id: changed run: | - CHANGED_FILES=$(git diff --name-only origin/${BASE_REF}...HEAD) + CHANGED_FILES=$(git diff --name-only "origin/${BASE_REF}...HEAD") PACKAGES="" for pkg_dir in packages/* web-packages/*; do @@ -118,11 +127,15 @@ jobs: PACKAGES=$(echo "$PACKAGES" | xargs) if [ -z "$PACKAGES" ]; then - echo "has_changes=false" >> $GITHUB_OUTPUT - echo "packages=" >> $GITHUB_OUTPUT + { + echo "has_changes=false" + echo "packages=" + } >> "$GITHUB_OUTPUT" else - echo "packages=$PACKAGES" >> $GITHUB_OUTPUT - echo "has_changes=true" >> $GITHUB_OUTPUT + { + echo "packages=$PACKAGES" + echo "has_changes=true" + } >> "$GITHUB_OUTPUT" fi echo "Changed packages: $PACKAGES" @@ -133,14 +146,16 @@ jobs: # Convert space-separated list to compact JSON array PACKAGES="${{ steps.changed.outputs.packages }}" MATRIX_JSON=$(echo "$PACKAGES" | jq -Rc 'split(" ") | map(select(length > 0))') - echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT + echo "matrix=$MATRIX_JSON" >> "$GITHUB_OUTPUT" echo "Matrix: $MATRIX_JSON" cleanup: name: Cleanup ${{ matrix.package_version }} needs: detect-changes - if: needs.detect-changes.outputs.has_previous == 'true' + if: needs.detect-changes.outputs.has_previous == 'true' && github.event.pull_request.head.repo.fork == false runs-on: ubuntu-latest + permissions: + contents: read strategy: fail-fast: false matrix: @@ -148,22 +163,19 @@ jobs: steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 - cache: 'pnpm' + persist-credentials: false - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps + with: + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - name: Cleanup previous version env: PACKAGE_VERSION: ${{ matrix.package_version }} + GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} + NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} run: | if ! [[ "$PACKAGE_VERSION" =~ ^@uipath/[a-z0-9-]+@[0-9.]+-pr[0-9]+(\.[a-z0-9]+)?$ ]]; then echo "::error::Invalid package version format: $PACKAGE_VERSION" @@ -179,27 +191,21 @@ jobs: update-comment-publishing: name: Update Comment - Publishing needs: [detect-changes, cleanup] - if: always() && needs.detect-changes.outputs.has_label == 'true' && needs.detect-changes.outputs.has_changes == 'true' + if: always() && needs.detect-changes.outputs.has_label == 'true' && needs.detect-changes.outputs.has_changes == 'true' && github.event.pull_request.head.repo.fork == false runs-on: ubuntu-latest permissions: contents: read - pull-requests: write issues: write steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 - cache: 'pnpm' + persist-credentials: false - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps + with: + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - name: Create initial PR comment env: @@ -214,11 +220,10 @@ jobs: publish: name: Publish ${{ matrix.package }} needs: [detect-changes, cleanup, update-comment-publishing] - if: always() && needs.detect-changes.outputs.has_label == 'true' && needs.detect-changes.outputs.has_changes == 'true' + if: always() && needs.detect-changes.outputs.has_label == 'true' && needs.detect-changes.outputs.has_changes == 'true' && github.event.pull_request.head.repo.fork == false runs-on: ubuntu-latest permissions: contents: read - pull-requests: write issues: write strategy: fail-fast: false # Continue even if one package fails @@ -227,18 +232,13 @@ jobs: steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 - cache: 'pnpm' + persist-credentials: false - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps + with: + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - name: Build package env: @@ -250,6 +250,8 @@ jobs: env: PACKAGE: ${{ matrix.package }} PR_NUMBER: ${{ github.event.pull_request.number }} + GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} + NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} run: | SHORT_SHA="${GITHUB_SHA:0:7}" SUFFIX="pr${PR_NUMBER}.${SHORT_SHA}" @@ -274,11 +276,15 @@ jobs: fi if pnpm publish:dev "$PACKAGE" "$SUFFIX"; then - echo "full_version=$PACKAGE@$version-$SUFFIX" >> $GITHUB_OUTPUT - echo "status=success" >> $GITHUB_OUTPUT + { + echo "full_version=$PACKAGE@$version-$SUFFIX" + echo "status=success" + } >> "$GITHUB_OUTPUT" else - echo "full_version=$PACKAGE@$version-$SUFFIX" >> $GITHUB_OUTPUT - echo "status=failure" >> $GITHUB_OUTPUT + { + echo "full_version=$PACKAGE@$version-$SUFFIX" + echo "status=failure" + } >> "$GITHUB_OUTPUT" exit 1 fi @@ -294,11 +300,10 @@ jobs: update-comment-cleanup: name: Update Comment - Cleanup Only needs: [detect-changes, cleanup] - if: always() && needs.detect-changes.outputs.has_previous == 'true' && needs.detect-changes.outputs.has_label == 'false' + if: always() && needs.detect-changes.outputs.has_previous == 'true' && needs.detect-changes.outputs.has_label == 'false' && github.event.pull_request.head.repo.fork == false runs-on: ubuntu-latest permissions: contents: read - pull-requests: write issues: write steps: - name: Update PR comment with cleanup status diff --git a/.github/workflows/notify-vertex-updates.yml b/.github/workflows/notify-vertex-updates.yml index 9bd39bfc6..994ea73f6 100644 --- a/.github/workflows/notify-vertex-updates.yml +++ b/.github/workflows/notify-vertex-updates.yml @@ -18,18 +18,12 @@ jobs: uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 + persist-credentials: false - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps with: - node-version: 22 - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install --frozen-lockfile + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - name: Notify changed components env: diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 7aa28bc8c..9ffa040cd 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -7,8 +7,6 @@ on: - 'support/**' env: - GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} BASE_REF: ${{ github.event.pull_request.base.ref }} concurrency: @@ -22,6 +20,9 @@ jobs: checks: name: ${{ matrix.check }} runs-on: ubuntu-latest + # GH_NPM_REGISTRY_TOKEN is unavailable on fork PRs — skip rather than fail with 401. + # To support external contributors, make @uipath/* packages publicly readable on GHP. + if: github.event.pull_request.head.repo.fork == false strategy: fail-fast: false matrix: @@ -42,25 +43,18 @@ jobs: fetch-depth: 0 persist-credentials: false - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps with: - node-version: 22 - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install --frozen-lockfile + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - name: Cache Turborepo uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: .turbo - key: ${{ runner.os }}-turbo-${{ github.sha }} + key: ${{ runner.os }}-turbo-${{ github.ref_name }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-turbo- + ${{ runner.os }}-turbo-${{ github.ref_name }}- - name: Run Format Check if: matrix.check == 'Format' @@ -107,7 +101,7 @@ jobs: - name: Run Build if: matrix.check == 'Build' run: | - if git diff --name-only origin/${BASE_REF}...HEAD | grep -qE '^(pnpm-lock\.yaml|package\.json)$'; then + if git diff --name-only "origin/${BASE_REF}...HEAD" | grep -qE '^(pnpm-lock\.yaml|package\.json)$'; then echo "📦 Root dependency files changed - building all packages" pnpm build else @@ -120,10 +114,7 @@ jobs: uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: failure-${{ matrix.check }}-${{ github.sha }} - path: | - coverage/ - dist/ - .turbo/ + path: coverage/ retention-days: 3 detect-lockfile-changes: @@ -141,16 +132,16 @@ jobs: - name: Check for lockfile changes id: lockfile_check run: | - if git diff --name-only origin/${BASE_REF}...HEAD | grep -q '^pnpm-lock\.yaml$'; then - echo "changed=true" >> $GITHUB_OUTPUT + if git diff --name-only "origin/${BASE_REF}...HEAD" | grep -q '^pnpm-lock\.yaml$'; then + echo "changed=true" >> "$GITHUB_OUTPUT" else - echo "changed=false" >> $GITHUB_OUTPUT + echo "changed=false" >> "$GITHUB_OUTPUT" fi audit: name: ${{ matrix.check }} needs: detect-lockfile-changes - if: needs.detect-lockfile-changes.outputs.changed == 'true' + if: needs.detect-lockfile-changes.outputs.changed == 'true' && github.event.pull_request.head.repo.fork == false runs-on: ubuntu-latest strategy: fail-fast: false @@ -158,6 +149,7 @@ jobs: check: - Audit All Dependencies - Audit Production Dependencies + - Audit Package Signatures steps: - name: Checkout code @@ -166,17 +158,10 @@ jobs: fetch-depth: 0 persist-credentials: false - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps with: - node-version: 22 - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install --frozen-lockfile + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - name: Run Audit All Dependencies if: matrix.check == 'Audit All Dependencies' @@ -185,3 +170,7 @@ jobs: - name: Run Audit Production Dependencies if: matrix.check == 'Audit Production Dependencies' run: pnpm audit --prod + + - name: Audit Package Signatures + if: matrix.check == 'Audit Package Signatures' + run: npm audit signatures diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index f26051e5c..a186c4cff 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -4,13 +4,15 @@ on: pull_request: types: [opened, synchronize, reopened] -permissions: - contents: read - pull-requests: write +# Deny-all default; the label job grants only what it needs. +permissions: {} jobs: label: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write steps: - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5 with: diff --git a/.github/workflows/prune-release-age-exemptions.yml b/.github/workflows/prune-release-age-exemptions.yml new file mode 100644 index 000000000..8ec4df4a1 --- /dev/null +++ b/.github/workflows/prune-release-age-exemptions.yml @@ -0,0 +1,58 @@ +name: Prune minimumReleaseAge Exemptions + +on: + schedule: + - cron: '0 9 * * 1' # Monday 09:00 UTC + workflow_dispatch: + +# Deny-all default; the prune job grants only what it needs. +permissions: {} + +jobs: + prune: + name: Remove stale exemptions + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout + # persist-credentials kept (default true) so the job can push the cleanup branch. + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '22' + + - name: Check exemption ages + id: check + run: node .github/scripts/check-release-age-exemptions.mjs + + - name: Open PR to remove stale exemptions + if: steps.check.outputs.count != '' && steps.check.outputs.count != '0' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REMOVABLE: ${{ steps.check.outputs.removable }} + run: | + BRANCH="chore/prune-release-age-exemptions-$(date +%Y%m%d)" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -b "$BRANCH" + git add pnpm-workspace.yaml + git commit -m "chore(repo): prune stale minimumReleaseAge exemptions" + git push origin "$BRANCH" + + # Build a markdown list of removed packages + # shellcheck disable=SC2016 + PACKAGES_MD=$(echo "$REMOVABLE" | tr ',' '\n' | sed 's/.*/* `&`/') + + # shellcheck disable=SC2016 + gh pr create \ + --title "chore(repo): prune stale minimumReleaseAge exemptions" \ + --body "$(printf '## Summary\n\nThe following packages were in \`minimumReleaseAgeExclude\` in \`pnpm-workspace.yaml\` because their latest release was too new at the time. Their latest versions are now older than the 14-day quarantine and no longer need an exemption.\n\n%s\n\n_Automated by the [Prune Release Age Exemptions](%s/%s/actions/workflows/prune-release-age-exemptions.yml) workflow._' "$PACKAGES_MD" "$GITHUB_SERVER_URL" "$GITHUB_REPOSITORY")" \ + --base main \ + --head "$BRANCH" \ + --label "dependencies" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d688779ab..547b71be9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,9 +6,10 @@ on: - main - 'support/**' +# Deny-all default; the release job grants only what it needs. +permissions: {} + env: - GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} TURBO_TELEMETRY_DISABLED: 1 DO_NOT_TRACK: 1 @@ -24,6 +25,7 @@ jobs: permissions: contents: write packages: write + id-token: write steps: # zizmor: ignore[artipacked] @@ -34,25 +36,18 @@ jobs: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN }} - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + - name: Install Node dependencies + uses: ./.github/actions/install-node-deps with: - node-version: 22 - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install --frozen-lockfile + registry-token: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} - name: Cache Turborepo uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: .turbo - key: ${{ runner.os }}-turbo-${{ github.sha }} + key: ${{ runner.os }}-turbo-${{ github.ref_name }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-turbo- + ${{ runner.os }}-turbo-${{ github.ref_name }}- - name: Build packages run: pnpm build diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index d37b2c75b..e2e1896a4 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -14,15 +14,17 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -permissions: - contents: read - security-events: write - actions: read +# Deny-all default; jobs grant the minimum they need. +permissions: {} jobs: zizmor: name: Zizmor Security Scan runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + actions: read steps: - name: Checkout code @@ -39,3 +41,25 @@ jobs: persona: regular env: ZIZMOR_CONFIG: zizmor.yml + + actionlint: + name: Actionlint Workflow Syntax Check + runs-on: ubuntu-latest + # github-pr-check reporter only surfaces results in PR context; skip on push. + if: github.event_name == 'pull_request' + permissions: + contents: read + checks: write + + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Run actionlint + # Catches a different class of issues than zizmor (syntax, expression typos, shellcheck on run: blocks). + uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1.72.0 + with: + reporter: github-pr-check + fail_on_error: true diff --git a/.github/workflows/support-branch-scope.yml b/.github/workflows/support-branch-scope.yml index 8c853ba73..0d7459b3a 100644 --- a/.github/workflows/support-branch-scope.yml +++ b/.github/workflows/support-branch-scope.yml @@ -9,15 +9,17 @@ concurrency: group: support-scope-${{ github.event.pull_request.number }} cancel-in-progress: true -permissions: - contents: read - pull-requests: write - issues: write +# Deny-all default; the check-scope job grants only what it needs. +permissions: {} jobs: check-scope: name: Check package scope runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: write steps: - name: Extract package from branch name id: parse @@ -60,13 +62,15 @@ jobs: - name: Get changed files id: changes env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_URL: ${{ github.event.pull_request.html_url }} run: | CHANGED=$(gh pr diff "$PR_URL" --name-only) - echo "files<> "$GITHUB_OUTPUT" - echo "$CHANGED" >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" + { + echo "files<> "$GITHUB_OUTPUT" - name: Check scope id: scope @@ -80,9 +84,7 @@ jobs: [ -z "$file" ] && continue case "$file" in - pnpm-lock.yaml) continue ;; - .npmrc) continue ;; - .github/*) continue ;; + pnpm-lock.yaml) continue ;; # legitimate when bumping deps inside PKG_DIR ${PKG_DIR}/*) continue ;; esac @@ -90,10 +92,12 @@ jobs: done <<< "$CHANGED_FILES" if [ -n "$OUT_OF_SCOPE" ]; then - echo "status=fail" >> "$GITHUB_OUTPUT" - echo "files<> "$GITHUB_OUTPUT" - echo -e "$OUT_OF_SCOPE" >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" + { + echo "status=fail" + echo "files<> "$GITHUB_OUTPUT" else echo "status=pass" >> "$GITHUB_OUTPUT" fi diff --git a/.github/workflows/vercel-deploy.yml b/.github/workflows/vercel-deploy.yml index 0cb4814f7..a2f3bc75d 100644 --- a/.github/workflows/vercel-deploy.yml +++ b/.github/workflows/vercel-deploy.yml @@ -5,6 +5,9 @@ on: push: branches: [main] +# Deny-all default; jobs grant the minimum they need. +permissions: {} + concurrency: group: vercel-${{ github.ref }} cancel-in-progress: true @@ -13,9 +16,9 @@ jobs: pre-deploy: name: Initialize Deployment Status runs-on: ubuntu-latest - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false permissions: - pull-requests: write + issues: write steps: - name: Post initial deployment status @@ -86,11 +89,11 @@ jobs: name: Deploy ${{ matrix.project_name }} runs-on: ubuntu-latest needs: pre-deploy - if: ${{ !cancelled() && (github.event_name == 'push' || needs.pre-deploy.result != 'cancelled') }} + if: ${{ !cancelled() && (github.event_name == 'push' || (needs.pre-deploy.result == 'success' && github.event.pull_request.head.repo.fork == false)) }} continue-on-error: true permissions: contents: read - pull-requests: write + issues: write strategy: matrix: include: @@ -116,40 +119,24 @@ jobs: with: node-version: '22' - - name: Setup pnpm - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - - - name: Get pnpm store directory - id: pnpm-cache - run: | - echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT - - - name: Setup pnpm cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 - with: - path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - name: Cache Vercel CLI uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.npm - key: ${{ runner.os }}-vercel-cli + key: ${{ runner.os }}-vercel-cli-53.4.0 restore-keys: | - ${{ runner.os }}-vercel-cli + ${{ runner.os }}-vercel-cli-53.4.0 - name: Install Vercel CLI - run: npm install -g vercel@latest + run: npm install -g vercel@53.4.0 - name: Set deployment variables id: vars run: | if [ "${{ github.event_name }}" == "pull_request" ]; then - echo "prod_flag=" >> $GITHUB_OUTPUT + echo "prod_flag=" >> "$GITHUB_OUTPUT" else - echo "prod_flag=--prod" >> $GITHUB_OUTPUT + echo "prod_flag=--prod" >> "$GITHUB_OUTPUT" fi - name: Set Vercel Project ID @@ -158,19 +145,19 @@ jobs: # Map matrix secret name to actual secret value case "${{ matrix.vercel_project_id_secret }}" in VERCEL_PROJECT_ID_CANVAS) - echo "VERCEL_PROJECT_ID=${{ secrets.VERCEL_PROJECT_ID_CANVAS }}" >> $GITHUB_ENV + echo "VERCEL_PROJECT_ID=${{ secrets.VERCEL_PROJECT_ID_CANVAS }}" >> "$GITHUB_ENV" ;; VERCEL_PROJECT_ID_DOCS) - echo "VERCEL_PROJECT_ID=${{ secrets.VERCEL_PROJECT_ID_DOCS }}" >> $GITHUB_ENV + echo "VERCEL_PROJECT_ID=${{ secrets.VERCEL_PROJECT_ID_DOCS }}" >> "$GITHUB_ENV" ;; VERCEL_PROJECT_ID_LANDING) - echo "VERCEL_PROJECT_ID=${{ secrets.VERCEL_PROJECT_ID_LANDING }}" >> $GITHUB_ENV + echo "VERCEL_PROJECT_ID=${{ secrets.VERCEL_PROJECT_ID_LANDING }}" >> "$GITHUB_ENV" ;; VERCEL_PROJECT_ID_UI_REACT) - echo "VERCEL_PROJECT_ID=${{ secrets.VERCEL_PROJECT_ID_UI_REACT }}" >> $GITHUB_ENV + echo "VERCEL_PROJECT_ID=${{ secrets.VERCEL_PROJECT_ID_UI_REACT }}" >> "$GITHUB_ENV" ;; VERCEL_PROJECT_ID_VERTEX) - echo "VERCEL_PROJECT_ID=${{ secrets.VERCEL_PROJECT_ID_VERTEX }}" >> $GITHUB_ENV + echo "VERCEL_PROJECT_ID=${{ secrets.VERCEL_PROJECT_ID_VERTEX }}" >> "$GITHUB_ENV" ;; *) echo "Error: Unknown vercel_project_id_secret value '${{ matrix.vercel_project_id_secret }}'. Please update the case statement in .github/workflows/vercel-deploy.yml." >&2 @@ -187,9 +174,9 @@ jobs: ERROR_MSG="" DEPLOY_URL="" set +e # Don't exit on error - DEPLOY_OUTPUT=$(vercel deploy --token "$VERCEL_TOKEN" --yes \ - --build-env GH_NPM_REGISTRY_TOKEN="$GH_NPM_REGISTRY_TOKEN" \ - ${{ steps.vars.outputs.prod_flag }} 2>&1) + VERCEL_ARGS=(deploy --token "$VERCEL_TOKEN" --yes --build-env GH_NPM_REGISTRY_TOKEN="$GH_NPM_REGISTRY_TOKEN") + [[ -n "$PROD_FLAG" ]] && VERCEL_ARGS+=("$PROD_FLAG") + DEPLOY_OUTPUT=$(vercel "${VERCEL_ARGS[@]}" 2>&1) DEPLOY_EXIT_CODE=$? set -e @@ -203,9 +190,9 @@ jobs: fi done - if [ $DEPLOY_EXIT_CODE -eq 0 ]; then + if [ "$DEPLOY_EXIT_CODE" -eq 0 ]; then # Extract or construct the deployment URL - if [ "${{ steps.vars.outputs.prod_flag }}" == "--prod" ]; then + if [ "$PROD_FLAG" == "--prod" ]; then # For production: use the clean production URL format DEPLOY_URL="https://${{ matrix.project_name }}.vercel.app" else @@ -219,25 +206,30 @@ jobs: fi fi - echo "url=$DEPLOY_URL" >> $GITHUB_OUTPUT - echo "project=${{ matrix.project_name }}" >> $GITHUB_OUTPUT - echo "error_message=" >> $GITHUB_OUTPUT - echo "✅ Deployed ${{ matrix.project_name }} to $DEPLOY_URL" >> $GITHUB_STEP_SUMMARY + { + echo "url=$DEPLOY_URL" + echo "project=${{ matrix.project_name }}" + echo "error_message=" + } >> "$GITHUB_OUTPUT" + echo "✅ Deployed ${{ matrix.project_name }} to $DEPLOY_URL" >> "$GITHUB_STEP_SUMMARY" else - echo "url=" >> $GITHUB_OUTPUT - echo "project=${{ matrix.project_name }}" >> $GITHUB_OUTPUT + { + echo "url=" + echo "project=${{ matrix.project_name }}" + } >> "$GITHUB_OUTPUT" ERROR_MSG=$(echo "$DEPLOY_OUTPUT" | tail -n 5 | tr '\n' ' ') # Truncate error message if too long (max 500 chars for output safety) - if [ ${#ERROR_MSG} -gt 500 ]; then + if [ "${#ERROR_MSG}" -gt 500 ]; then ERROR_MSG="${ERROR_MSG:0:500}..." fi - echo "error_message=$ERROR_MSG" >> $GITHUB_OUTPUT - echo "❌ Failed to deploy ${{ matrix.project_name }}: $ERROR_MSG" >> $GITHUB_STEP_SUMMARY + echo "error_message=$ERROR_MSG" >> "$GITHUB_OUTPUT" + echo "❌ Failed to deploy ${{ matrix.project_name }}: $ERROR_MSG" >> "$GITHUB_STEP_SUMMARY" exit 1 fi env: + PROD_FLAG: ${{ steps.vars.outputs.prod_flag }} VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} GH_NPM_REGISTRY_TOKEN: ${{ secrets.GH_NPM_REGISTRY_TOKEN }} diff --git a/.gitignore b/.gitignore index 1f443e8e5..51aba828f 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,22 @@ playwright-report/ # Environment variables .env -.env*.local +.env.* +!.env.example + +# Credentials / keys +*.pem +*.key +*.p12 +*.pfx +id_rsa* + +# Deployment platforms +.netlify/ + +# Claude local settings (token-bearing) +.claude/settings.local.json +.claude/*.local.* # IDE .idea/ diff --git a/.npmrc b/.npmrc index 42e4b31c8..1b4af9b55 100644 --- a/.npmrc +++ b/.npmrc @@ -1,5 +1,6 @@ @uipath:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=${GH_NPM_REGISTRY_TOKEN} +//npm.pkg.github.com/:always-auth=true //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN} auto-install-peers=true strict-peer-dependencies=false diff --git a/CODEOWNERS b/CODEOWNERS index e282d1ecb..af372e93a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,15 +1,27 @@ -# Global Owners -# * @UiPath/Apollo @UiPath/portal-members # commented out until remaining owners are added. +# Global owner — applies to anything not matched by a more specific rule below. +* @UiPath/Apollo + +# Security-sensitive paths — require @UiPath/Apollo review for any change. +/.github/ @UiPath/Apollo +/.npmrc @UiPath/Apollo +/.gitignore @UiPath/Apollo +/.gitattributes @UiPath/Apollo +/CODEOWNERS @UiPath/Apollo +/SECURITY.md @UiPath/Apollo +/package.json @UiPath/Apollo +/pnpm-lock.yaml @UiPath/Apollo +/pnpm-workspace.yaml @UiPath/Apollo +/scripts/ @UiPath/Apollo # Packages -packages/apollo-core @UiPath/Apollo -packages/apollo-react @UiPath/Apollo -packages/apollo-wind +/packages/apollo-core/ @UiPath/Apollo +/packages/apollo-react/ @UiPath/Apollo +/packages/apollo-wind/ @UiPath/Apollo # apps -apps/apollo-vertex @UiPath/team-frontendhoven -apps/react-playground @UiPath/Apollo -apps/storybook +/apps/apollo-vertex/ @UiPath/team-frontendhoven @UiPath/Apollo +/apps/react-playground/ @UiPath/Apollo +/apps/storybook/ @UiPath/Apollo # web-packages -web-packages/ap-chat \ No newline at end of file +/web-packages/ap-chat/ @UiPath/Apollo \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..0946ef1f1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report security vulnerabilities **privately** — do not file public GitHub issues for security problems. + +### Channel + +Use GitHub's [Private Vulnerability Reporting](https://github.com/UiPath/apollo-ui/security/advisories/new) for this repository. This routes the report directly to the maintainers and keeps the disclosure confidential until a fix is available. + +## Scope + +This repository hosts the Apollo design system: `@uipath/apollo-core`, `@uipath/apollo-react`, `@uipath/apollo-wind`, `@uipath/ap-chat`, and supporting tooling. Reports about these packages — including their build pipeline, published artifacts, and the public documentation deployments — are in scope. + +Out of scope: vulnerabilities in third-party dependencies that have a published advisory; report those upstream. + +## Response + +We aim to acknowledge reports within 3 business days. After triage we will work with you on a disclosure timeline. Coordinated disclosure is appreciated. diff --git a/apps/apollo-docs/vercel.json b/apps/apollo-docs/vercel.json index ab6563404..2e18c08f1 100644 --- a/apps/apollo-docs/vercel.json +++ b/apps/apollo-docs/vercel.json @@ -1,6 +1,6 @@ { "buildCommand": "cd ../.. && pnpm turbo build --filter=apollo-docs", - "installCommand": "cd ../.. && pnpm install --filter=apollo-docs...", + "installCommand": "cd ../.. && pnpm install --frozen-lockfile --filter=apollo-docs...", "framework": "nextjs", "ignoreCommand": "git diff HEAD^ HEAD --quiet . && git diff HEAD^ HEAD --quiet ../../packages" } diff --git a/apps/apollo-vertex/package.json b/apps/apollo-vertex/package.json index 29ae182d3..c23ddd7da 100644 --- a/apps/apollo-vertex/package.json +++ b/apps/apollo-vertex/package.json @@ -109,7 +109,7 @@ "oxlint": "^1.38.0", "oxlint-tsgolint": "^0.18.1", "pagefind": "^1.4.0", - "shadcn": "latest", + "shadcn": "4.4.0", "tw-animate-css": "^1.4.0" } } diff --git a/apps/apollo-vertex/vercel.json b/apps/apollo-vertex/vercel.json index 445211be6..96c580322 100644 --- a/apps/apollo-vertex/vercel.json +++ b/apps/apollo-vertex/vercel.json @@ -1,6 +1,6 @@ { "buildCommand": "cd ../.. && pnpm turbo build --filter=apollo-vertex", - "installCommand": "cd ../.. && pnpm install", + "installCommand": "cd ../.. && pnpm install --frozen-lockfile", "framework": "nextjs", "ignoreCommand": "git diff HEAD^ HEAD --quiet . && git diff HEAD^ HEAD --quiet ../../packages" } diff --git a/apps/landing/vercel.json b/apps/landing/vercel.json index d92eb97fc..8b9540332 100644 --- a/apps/landing/vercel.json +++ b/apps/landing/vercel.json @@ -1,6 +1,6 @@ { "buildCommand": "cd ../.. && pnpm turbo build --filter=apollo-landing", - "installCommand": "cd ../.. && pnpm install", + "installCommand": "cd ../.. && pnpm install --frozen-lockfile", "outputDirectory": "dist", "framework": null, "ignoreCommand": "git diff HEAD^ HEAD --quiet ." diff --git a/apps/react-playground/vercel.json b/apps/react-playground/vercel.json index 4b1ec9af4..067e9ae0f 100644 --- a/apps/react-playground/vercel.json +++ b/apps/react-playground/vercel.json @@ -1,6 +1,6 @@ { "buildCommand": "cd ../.. && pnpm turbo build --filter=react-playground", - "installCommand": "cd ../.. && pnpm install", + "installCommand": "cd ../.. && pnpm install --frozen-lockfile", "outputDirectory": "dist", "framework": null, "ignoreCommand": "git diff HEAD^ HEAD --quiet . && git diff HEAD^ HEAD --quiet ../../packages", diff --git a/apps/storybook/vercel.json b/apps/storybook/vercel.json index e3949a4b4..bb492dcb4 100644 --- a/apps/storybook/vercel.json +++ b/apps/storybook/vercel.json @@ -1,6 +1,6 @@ { "buildCommand": "cd ../.. && pnpm turbo storybook:build --filter=storybook-app", - "installCommand": "cd ../.. && pnpm install", + "installCommand": "cd ../.. && pnpm install --frozen-lockfile", "outputDirectory": "storybook-static", "framework": null, "ignoreCommand": "git diff HEAD^ HEAD --quiet . && git diff HEAD^ HEAD --quiet ../../packages" diff --git a/package.json b/package.json index 57b26c300..5472238b7 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,11 @@ } }, "pnpm": { + "onlyBuiltDependencies": [ + "@biomejs/biome", + "esbuild", + "sharp" + ], "overrides": { "minimatch": "^10.2.5", "lodash": "^4.18.1", diff --git a/packages/apollo-wind/vercel.json b/packages/apollo-wind/vercel.json index 37427789f..f9596bbcc 100644 --- a/packages/apollo-wind/vercel.json +++ b/packages/apollo-wind/vercel.json @@ -1,6 +1,6 @@ { "buildCommand": "cd ../.. && pnpm turbo storybook:build --filter=@uipath/apollo-wind", - "installCommand": "cd ../.. && pnpm install", + "installCommand": "cd ../.. && pnpm install --frozen-lockfile", "outputDirectory": "storybook-static", "framework": null, "ignoreCommand": "git diff HEAD^ HEAD --quiet . && git diff HEAD^ HEAD --quiet ../../packages/apollo-core" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8332343da..a3c102f20 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,10 +121,10 @@ importers: version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) nextra: specifier: ^4.6.1 - version: 4.6.1(next@16.2.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + version: 4.6.1(next@16.2.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) nextra-theme-docs: specifier: ^4.6.1 - version: 4.6.1(@types/react@19.2.8)(immer@10.2.0)(next@16.2.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(nextra@4.6.1(next@16.2.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + version: 4.6.1(@types/react@19.2.8)(immer@10.2.0)(next@16.2.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(nextra@4.6.1(next@16.2.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) postcss: specifier: ^8.5.14 version: 8.5.14 @@ -323,10 +323,10 @@ importers: version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) nextra: specifier: ^4.6.1 - version: 4.6.1(next@16.2.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + version: 4.6.1(next@16.2.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) nextra-theme-docs: specifier: ^4.6.1 - version: 4.6.1(@types/react@19.2.8)(immer@10.2.0)(next@16.2.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(nextra@4.6.1(next@16.2.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + version: 4.6.1(@types/react@19.2.8)(immer@10.2.0)(next@16.2.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(nextra@4.6.1(next@16.2.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) pkce-challenge: specifier: ^6.0.0 version: 6.0.0 @@ -410,8 +410,8 @@ importers: specifier: ^1.4.0 version: 1.4.0 shadcn: - specifier: latest - version: 4.5.0(@types/node@24.10.1)(babel-plugin-macros@3.1.0)(typescript@5.9.3) + specifier: 4.4.0 + version: 4.4.0(@types/node@24.10.1)(babel-plugin-macros@3.1.0)(typescript@5.9.3) tw-animate-css: specifier: ^1.4.0 version: 1.4.0 @@ -1470,11 +1470,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.29.2': - resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.29.3': resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} engines: {node: '>=6.0.0'} @@ -2024,6 +2019,7 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64-musl@2.4.4': resolution: {integrity: sha512-+sPAXq3bxmFwhVFJnSwkSF5Rw2ZAJMH3MF6C9IveAEOdSpgajPhoQhbbAK12SehN9j2QrHpk4J/cHsa/HqWaYQ==} @@ -2036,6 +2032,7 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-arm64@2.4.4': resolution: {integrity: sha512-V/NFfbWhsUU6w+m5WYbBenlEAz8eYnSqRMDMAW3K+3v0tYVkNyZn8VU0XPxk/lOqNXLSCCrV7FmV/u3SjCBShg==} @@ -2048,6 +2045,7 @@ packages: engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64-musl@2.4.4': resolution: {integrity: sha512-gGvFTGpOIQDb5CQ2VC0n9Z2UEqlP46c4aNgHmAMytYieTGEcfqhfCFnhs6xjt0S3igE6q5GLuIXtdQt3Izok+g==} @@ -2060,6 +2058,7 @@ packages: engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64@2.4.4': resolution: {integrity: sha512-R4+ZCDtG9kHArasyBO+UBD6jr/FcFCTH8QkNTOCu0pRJzCWyWC4EtZa2AmUZB5h3e0jD7bRV2KvrENcf8rndBg==} @@ -4446,6 +4445,7 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} @@ -4458,6 +4458,7 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} @@ -4470,6 +4471,7 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} @@ -4482,6 +4484,7 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} @@ -4494,6 +4497,7 @@ packages: engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} @@ -4506,6 +4510,7 @@ packages: engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -5366,11 +5371,13 @@ packages: resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.59.0': resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.44.0': resolution: {integrity: sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==} @@ -5381,6 +5388,7 @@ packages: resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.44.0': resolution: {integrity: sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==} @@ -5391,41 +5399,49 @@ packages: resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.59.0': resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.59.0': resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.59.0': resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.59.0': resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.59.0': resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.59.0': resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.59.0': resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.44.0': resolution: {integrity: sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==} @@ -5436,6 +5452,7 @@ packages: resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.44.0': resolution: {integrity: sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==} @@ -5446,6 +5463,7 @@ packages: resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.59.0': resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} @@ -6804,6 +6822,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@upsetjs/venn.js@2.0.0': resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} @@ -8221,10 +8240,6 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - diff@8.0.3: - resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} - engines: {node: '>=0.3.1'} - diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} @@ -10065,6 +10080,7 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} @@ -10077,6 +10093,7 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} @@ -10089,6 +10106,7 @@ packages: engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} @@ -10101,6 +10119,7 @@ packages: engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -12550,8 +12569,8 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - shadcn@4.5.0: - resolution: {integrity: sha512-ZpNOz7IMI5aezbMEWNxBvl2aJ1ek6NuAMqpL/FUnk5IuRxERl8ohYEnqqAmhPOcur8RbGuCoqTZLQ3Oi4Xkf8A==} + shadcn@4.4.0: + resolution: {integrity: sha512-0V1AjVktKwhK1e0ONXb9SeBoyJePH04iTSJeMjl9eROqjjyb8OoWYtWDI39UdBU3GzpUlFBJFohhB9c6fGOkQA==} hasBin: true shallow-equal@3.1.0: @@ -14018,11 +14037,6 @@ packages: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} - zod-to-json-schema@3.25.0: - resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} - peerDependencies: - zod: ^3.25 || ^4 - zod-to-json-schema@3.25.1: resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: @@ -14371,10 +14385,6 @@ snapshots: dependencies: '@babel/types': 7.28.5 - '@babel/parser@7.29.2': - dependencies: - '@babel/types': 7.29.0 - '@babel/parser@7.29.3': dependencies: '@babel/types': 7.29.0 @@ -21936,10 +21946,7 @@ snapshots: diff-sequences@29.6.3: {} - diff@8.0.3: {} - - diff@8.0.4: - optional: true + diff@8.0.4: {} dir-glob@3.0.1: dependencies: @@ -22869,7 +22876,6 @@ snapshots: graceful-fs: 4.2.11 jsonfile: 6.2.1 universalify: 2.0.1 - optional: true fs-extra@6.0.1: dependencies: @@ -23988,7 +23994,6 @@ snapshots: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 - optional: true jsonparse@1.3.1: {} @@ -24347,7 +24352,7 @@ snapshots: magicast@0.5.2: dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 source-map-js: 1.2.1 @@ -25187,13 +25192,13 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@4.6.1(@types/react@19.2.8)(immer@10.2.0)(next@16.2.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(nextra@4.6.1(next@16.2.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): + nextra-theme-docs@4.6.1(@types/react@19.2.8)(immer@10.2.0)(next@16.2.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(nextra@4.6.1(next@16.2.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): dependencies: '@headlessui/react': 2.2.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3) clsx: 2.1.1 next: 16.2.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2) next-themes: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - nextra: 4.6.1(next@16.2.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) + nextra: 4.6.1(next@16.2.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) react: 19.2.3 react-compiler-runtime: 19.1.0-rc.3(react@19.2.3) react-dom: 19.2.3(react@19.2.3) @@ -25205,7 +25210,7 @@ snapshots: - immer - use-sync-external-store - nextra@4.6.1(next@16.2.6(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3): + nextra@4.6.1(next@16.2.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.94.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3): dependencies: '@formatjs/intl-localematcher': 0.6.2 '@headlessui/react': 2.2.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -25488,7 +25493,7 @@ snapshots: log-symbols: 6.0.0 stdin-discarder: 0.2.2 string-width: 7.2.0 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 orderedmap@2.1.1: {} @@ -27264,24 +27269,24 @@ snapshots: setprototypeof@1.2.0: {} - shadcn@4.5.0(@types/node@24.10.1)(babel-plugin-macros@3.1.0)(typescript@5.9.3): + shadcn@4.4.0(@types/node@24.10.1)(babel-plugin-macros@3.1.0)(typescript@5.9.3): dependencies: '@babel/core': 7.28.5 - '@babel/parser': 7.28.5 + '@babel/parser': 7.29.3 '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) '@dotenvx/dotenvx': 1.51.1 '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.76) '@types/validate-npm-package-name': 4.0.2 - browserslist: 4.28.0 + browserslist: 4.28.1 commander: 14.0.2 - cosmiconfig: 9.0.0(typescript@5.9.3) + cosmiconfig: 9.0.1(typescript@5.9.3) dedent: 1.7.0(babel-plugin-macros@3.1.0) deepmerge: 4.3.1 - diff: 8.0.3 - execa: 9.6.0 + diff: 8.0.4 + execa: 9.6.1 fast-glob: 3.3.3 - fs-extra: 11.3.2 + fs-extra: 11.3.5 fuzzysort: 3.1.0 https-proxy-agent: 7.0.6 kleur: 4.1.5 @@ -27290,7 +27295,7 @@ snapshots: open: 11.0.0 ora: 8.2.0 postcss: 8.5.14 - postcss-selector-parser: 7.1.0 + postcss-selector-parser: 7.1.1 prompts: 2.4.2 recast: 0.23.11 stringify-object: 5.0.0 @@ -27299,7 +27304,7 @@ snapshots: tsconfig-paths: 4.2.0 validate-npm-package-name: 7.0.2 zod: 3.25.76 - zod-to-json-schema: 3.25.0(zod@3.25.76) + zod-to-json-schema: 3.25.1(zod@3.25.76) transitivePeerDependencies: - '@cfworker/json-schema' - '@types/node' @@ -28957,10 +28962,6 @@ snapshots: yoctocolors@2.1.2: {} - zod-to-json-schema@3.25.0(zod@3.25.76): - dependencies: - zod: 3.25.76 - zod-to-json-schema@3.25.1(zod@3.25.76): dependencies: zod: 3.25.76 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 128e051f8..6f2977012 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,6 +3,24 @@ packages: - 'web-packages/*' - 'apps/*' +# Supply-chain hardening +# 14-day quarantine on newly-published packages (minutes; 14d × 1440) +minimumReleaseAge: 20160 +# Exempt packages whose latest release is too new to satisfy the quarantine. +# The weekly `prune-release-age-exemptions` workflow auto-PRs removal of entries once aged. +# +# Format: - 'pkg' # +# The version is required — the prune script uses it to decide when to remove the entry. +minimumReleaseAgeExclude: + - '@uipath/*' # permanent — own scope, always installable immediately + - 'postcss' # 8.5.14 — security override in pnpm.overrides + - 'mermaid' # 11.15.0 — GHSA-v2wj-7wpq-c8vv backport patch + - 'next' # 16.2.6 — locked version too new + - 'fs-extra' # 11.3.5 — locked version too new + - '@babel/parser' # 7.29.3 — locked version too new +# Refuse transitive git/tarball deps (pnpm 11 default; explicit on 10.x) +blockExoticSubdeps: true + # dompurify XSS - FIXME: no patch available yet auditConfig: ignoreGhsas: diff --git a/scripts/publish-to-registries.sh b/scripts/publish-to-registries.sh index b64f55d3e..18d6e0ff4 100755 --- a/scripts/publish-to-registries.sh +++ b/scripts/publish-to-registries.sh @@ -14,19 +14,19 @@ if [ -z "$GH_NPM_REGISTRY_TOKEN" ]; then exit 1 fi -PUBLISH_ARGS="$@" - echo "📦 Publishing to npm..." +# --provenance links the build to its GitHub Actions source; requires id-token: write in the workflow. +# Only supported on registry.npmjs.org, not on GitHub Packages. NPM_AUTH_TOKEN="$NPM_AUTH_TOKEN" \ NODE_AUTH_TOKEN="$NPM_AUTH_TOKEN" \ - pnpm publish $PUBLISH_ARGS --@uipath:registry=https://registry.npmjs.org + pnpm publish "$@" --provenance --@uipath:registry=https://registry.npmjs.org echo "✓ Published to npm" echo "" echo "📦 Publishing to GitHub Package Registry..." NPM_AUTH_TOKEN="$GH_NPM_REGISTRY_TOKEN" \ NODE_AUTH_TOKEN="$GH_NPM_REGISTRY_TOKEN" \ - pnpm publish $PUBLISH_ARGS --@uipath:registry=https://npm.pkg.github.com + pnpm publish "$@" --@uipath:registry=https://npm.pkg.github.com echo "✓ Published to GitHub Package Registry" echo "" diff --git a/zizmor.yml b/zizmor.yml index e07290018..461d22867 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -4,3 +4,9 @@ rules: # Credentials needed for semantic-release to push version bumps # No artifacts uploaded in this workflow - safe to persist credentials - .github/workflows/release.yml + dependabot-cooldown: + ignore: + # The `cooldown` field is not part of the Dependabot v2 schema — GitHub rejects it. + # Supply-chain quarantine is enforced by pnpm's minimumReleaseAge + CODEOWNERS on + # pnpm-lock.yaml instead. Suppress this rule repo-wide. + - .github/dependabot.yml