diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index 32ae448264..c3844e39b0 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -4,5 +4,5 @@ jobs: actionlint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: rhysd/actionlint@v1.7.11 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: rhysd/actionlint@393031adb9afb225ee52ae2ccd7a5af5525e03e8 # v1.7.11 diff --git a/.github/workflows/ci-image.yml b/.github/workflows/ci-image.yml index 00d38637c5..86b0d03666 100644 --- a/.github/workflows/ci-image.yml +++ b/.github/workflows/ci-image.yml @@ -19,18 +19,18 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # Copy lockfile + package.json into Docker build context - run: cp package.json .github/docker/ - - uses: docker/login-action@v3 + - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: docker/build-push-action@v6 + - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: .github/docker file: .github/docker/Dockerfile.ci diff --git a/.github/workflows/cve-lite.yml b/.github/workflows/cve-lite.yml index c72e0e6afc..c33a375fac 100644 --- a/.github/workflows/cve-lite.yml +++ b/.github/workflows/cve-lite.yml @@ -21,10 +21,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Set up Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: '22' @@ -48,14 +48,14 @@ jobs: - name: Upload SARIF to Code Scanning if: always() && hashFiles('*.sarif') != '' - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@411bbbe57033eedfc1a82d68c01345aa96c737d7 # v4 with: sarif_file: cve-lite-*.sarif category: cve-lite - name: Upload HTML report artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: cve-lite-report path: cve-report/ diff --git a/.github/workflows/evals-periodic.yml b/.github/workflows/evals-periodic.yml index 20035c453f..999b3264e3 100644 --- a/.github/workflows/evals-periodic.yml +++ b/.github/workflows/evals-periodic.yml @@ -22,12 +22,12 @@ jobs: outputs: image-tag: ${{ steps.meta.outputs.tag }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - id: meta run: echo "tag=${{ env.IMAGE }}:${{ hashFiles('.github/docker/Dockerfile.ci', 'package.json') }}" >> "$GITHUB_OUTPUT" - - uses: docker/login-action@v3 + - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -46,7 +46,7 @@ jobs: run: cp package.json .github/docker/ - if: steps.check.outputs.exists == 'false' - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: .github/docker file: .github/docker/Dockerfile.ci @@ -88,7 +88,7 @@ jobs: - name: e2e-gemini file: test/gemini-e2e.test.ts steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 @@ -122,7 +122,7 @@ jobs: - name: Upload eval results if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: eval-periodic-${{ matrix.suite.name }} path: ~/.gstack-dev/evals/*.json diff --git a/.github/workflows/evals.yml b/.github/workflows/evals.yml index a7b1fd9970..56f3efba79 100644 --- a/.github/workflows/evals.yml +++ b/.github/workflows/evals.yml @@ -22,12 +22,12 @@ jobs: outputs: image-tag: ${{ steps.meta.outputs.tag }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - id: meta run: echo "tag=${{ env.IMAGE }}:${{ hashFiles('.github/docker/Dockerfile.ci', 'package.json') }}" >> "$GITHUB_OUTPUT" - - uses: docker/login-action@v3 + - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -46,7 +46,7 @@ jobs: run: cp package.json .github/docker/ - if: steps.check.outputs.exists == 'false' - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: .github/docker file: .github/docker/Dockerfile.ci @@ -95,7 +95,7 @@ jobs: - name: e2e-gemini file: test/gemini-e2e.test.ts steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 @@ -140,7 +140,7 @@ jobs: - name: Upload eval results if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: eval-${{ matrix.suite.name }} path: ~/.gstack-dev/evals/*.json @@ -155,12 +155,12 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 1 - name: Download all eval artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: pattern: eval-* path: /tmp/eval-results diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index d3db71b3f7..219313c1e3 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -9,10 +9,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Self-hosted Renovate - uses: renovatebot/github-action@v41 + uses: renovatebot/github-action@7e1c0fa7cfd2c3e91b27cdd87ae09a6a0fafb5f2 # v41.0.0 with: configurationFile: .github/renovate.json5 token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/skill-docs.yml b/.github/workflows/skill-docs.yml index 34ea7f8e9b..5d0534d21e 100644 --- a/.github/workflows/skill-docs.yml +++ b/.github/workflows/skill-docs.yml @@ -4,8 +4,8 @@ jobs: check-freshness: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - run: bun install - name: Check Claude host freshness run: bun run gen:skill-docs diff --git a/.github/workflows/skillspector.yml b/.github/workflows/skillspector.yml new file mode 100644 index 0000000000..7f19503d7b --- /dev/null +++ b/.github/workflows/skillspector.yml @@ -0,0 +1,133 @@ +name: SkillSpector + +on: + pull_request: + paths: + - '**/SKILL.md' + - '.agents/**' + - '.factory/**' + - 'AGENTS.md' + - '**/agents/*.yaml' + - '**/agents/*.yml' + - '**/.codex-plugin/plugin.json' + push: + branches: [main, master] + paths: + - '**/SKILL.md' + - '.agents/**' + - '.factory/**' + - 'AGENTS.md' + - '**/agents/*.yaml' + - '**/agents/*.yml' + - '**/.codex-plugin/plugin.json' + +permissions: + contents: read + +jobs: + scan: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: '3.12' + + - name: Set up uv + uses: astral-sh/setup-uv@d0d8abe699bfb85fec6de9f7adb5ae17292296ff # v6 + + - name: Install SkillSpector + run: | + git clone --depth 1 https://github.com/NVIDIA/SkillSpector.git /tmp/SkillSpector + uv venv .skillspector-venv --python 3.12 + uv pip install --python .skillspector-venv/bin/python /tmp/SkillSpector + + - name: Scan changed skills + env: + EVENT_NAME: ${{ github.event_name }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + BEFORE_SHA: ${{ github.event.before }} + run: | + set -euo pipefail + + if [ "$EVENT_NAME" = "pull_request" ]; then + BASE_SHA="$PR_BASE_SHA" + else + BASE_SHA="$BEFORE_SHA" + fi + + if [ -z "${BASE_SHA:-}" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then + BASE_SHA="$(git rev-list --max-parents=0 HEAD | tail -n 1)" + fi + + git diff --name-only --diff-filter=ACMR "$BASE_SHA" "$GITHUB_SHA" > /tmp/skillspector-changed.txt + + python3 - <<'PY' + from pathlib import Path + + repo = Path.cwd() + changed = (Path("/tmp/skillspector-changed.txt").read_text().splitlines()) + roots = [] + seen = set() + + for rel in changed: + rel = rel.strip() + if not rel: + continue + current = (repo / rel) + current = current if current.is_dir() else current.parent + + while current != repo.parent and current != current.parent: + if (current / "SKILL.md").is_file(): + root = str(current.relative_to(repo)) + if root not in seen: + seen.add(root) + roots.append(root) + break + if current == repo: + break + current = current.parent + + Path("/tmp/skillspector-targets.txt").write_text("\n".join(roots) + ("\n" if roots else "")) + print(f"skill_dirs={len(roots)}") + for root in roots: + print(root) + PY + + if [ ! -s /tmp/skillspector-targets.txt ]; then + echo "No changed skill roots detected; skipping scan." + exit 0 + fi + + while IFS= read -r dir; do + [ -n "$dir" ] || continue + echo "Scanning $dir" + ./.skillspector-venv/bin/skillspector scan "$dir" --no-llm --format json --output /tmp/skillspector-report.json + python3 - <<'PY' + import json + import sys + + with open("/tmp/skillspector-report.json", "r", encoding="utf-8") as fh: + data = json.load(fh) + + findings = data.get("findings", []) + severities = {str(f.get("severity", "")).upper() for f in findings} + score = int(data.get("risk_score", 0)) + blocked = score >= 70 or "CRITICAL" in severities or "HIGH" in severities + + print(f"risk_score={score}") + print(f"severities={sorted(s for s in severities if s)}") + print(f"findings={len(findings)}") + + if blocked: + print("SkillSpector gate failed") + sys.exit(1) + PY + done < /tmp/skillspector-targets.txt