diff --git a/.github/workflows/aimock-drift.yml b/.github/workflows/aimock-drift.yml index adee182b..e94fab56 100644 --- a/.github/workflows/aimock-drift.yml +++ b/.github/workflows/aimock-drift.yml @@ -22,8 +22,8 @@ jobs: drift: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm @@ -34,7 +34,7 @@ jobs: run: npx nx run examples-chat-angular:drift --skip-nx-cache - name: Open issue on drift if: failure() - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const { owner, repo } = context.repo; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abef8b76..d7a428b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,10 +33,10 @@ jobs: website_e2e: ${{ steps.scope.outputs.website_e2e }} posthog: ${{ steps.scope.outputs.posthog }} steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-node@v6.3.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm @@ -85,8 +85,8 @@ jobs: env: LIBS: chat,langgraph,ag-ui,render,a2ui,licensing,telemetry steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm @@ -104,10 +104,10 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.head_ref || github.sha }} - - uses: actions/setup-node@v6.3.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm @@ -137,8 +137,8 @@ jobs: if: github.event_name == 'push' || needs.ci-scope.outputs.cockpit == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm @@ -152,10 +152,10 @@ jobs: if: github.event_name == 'push' || needs.ci-scope.outputs.cockpit_examples == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-node@v6.3.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm @@ -183,8 +183,8 @@ jobs: if: github.event_name == 'push' || needs.ci-scope.outputs.cockpit_smoke == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm @@ -208,9 +208,9 @@ jobs: fi env: COCKPIT_SECRET_TOKEN: ${{ secrets.COCKPIT_SECRET_TOKEN }} - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 if: steps.integration_secret.outputs.enabled == 'true' - - uses: actions/setup-node@v6.3.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 if: steps.integration_secret.outputs.enabled == 'true' with: node-version: 22 @@ -228,8 +228,8 @@ jobs: if: github.event_name == 'push' || needs.ci-scope.outputs.cockpit_deploy_smoke == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm @@ -242,18 +242,18 @@ jobs: if: github.event_name == 'push' || needs.ci-scope.outputs.examples_chat == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm - name: Install uv - uses: astral-sh/setup-uv@v8.0.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: python-version: '3.12' - run: npm ci - name: Cache examples-chat python venv - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: examples/chat/python/.venv key: uv-venv-${{ runner.os }}-py3.12-${{ hashFiles('examples/chat/python/uv.lock') }} @@ -272,25 +272,25 @@ jobs: matrix: shard: [1, 2, 3, 4] steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm - name: Install uv - uses: astral-sh/setup-uv@v8.0.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: python-version: '3.12' - run: npm ci - name: Cache examples-chat python venv - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: examples/chat/python/.venv key: uv-venv-${{ runner.os }}-py3.12-${{ hashFiles('examples/chat/python/uv.lock') }} - working-directory: examples/chat/python run: uv sync - name: Cache Playwright browsers - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }} @@ -300,7 +300,7 @@ jobs: - run: npx nx e2e examples-chat-angular --skip-nx-cache -- --shard=${{ matrix.shard }}/4 - name: Upload Playwright trace on failure if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: examples-chat-e2e-trace-shard-${{ matrix.shard }} path: | @@ -332,25 +332,25 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 35 steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm - name: Install uv - uses: astral-sh/setup-uv@v8.0.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: python-version: '3.12' - run: npm ci - name: Cache examples-ag-ui python venv - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: examples/ag-ui/python/.venv key: uv-venv-${{ runner.os }}-py3.12-${{ hashFiles('examples/ag-ui/python/uv.lock') }} - working-directory: examples/ag-ui/python run: uv sync - name: Cache Playwright browsers - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }} @@ -360,7 +360,7 @@ jobs: - run: npx nx e2e examples-ag-ui-angular --skip-nx-cache - name: Upload Playwright trace on failure if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: examples-ag-ui-e2e-trace path: | @@ -376,10 +376,10 @@ jobs: outputs: caps: ${{ steps.matrix.outputs.caps }} steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-node@v6.3.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm @@ -415,18 +415,18 @@ jobs: matrix: cap: ${{ fromJson(needs.cockpit-e2e-dispatcher.outputs.caps) }} steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm - name: Install uv - uses: astral-sh/setup-uv@v8.0.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: python-version: '3.12' - run: npm ci - name: Cache cap python venv - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ matrix.cap.python }}/.venv key: uv-venv-${{ runner.os }}-py3.12-${{ matrix.cap.python }}-${{ hashFiles(format('{0}/uv.lock', matrix.cap.python)) }} @@ -434,7 +434,7 @@ jobs: working-directory: ${{ matrix.cap.python }} run: uv sync - name: Cache Playwright browsers - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }} @@ -445,7 +445,7 @@ jobs: run: npx nx e2e "${{ matrix.cap.angular }}" --skip-nx-cache - name: Upload Playwright trace on failure if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: cockpit-e2e-trace-${{ matrix.cap.angular }} path: | @@ -472,14 +472,14 @@ jobs: if: github.event_name == 'push' || needs.ci-scope.outputs.website_e2e == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm - run: npm ci - name: Cache Playwright browsers - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }} @@ -608,7 +608,7 @@ jobs: # Only deploy on pushes to main, not on pull requests if: github.ref == 'refs/heads/main' && github.event_name == 'push' steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Detect deploy-relevant changes @@ -658,7 +658,7 @@ jobs: examples_changed=true fi echo "changed=$examples_changed" >> "$GITHUB_OUTPUT" - - uses: actions/setup-node@v6.3.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 if: steps.deploy_preflight.outputs.relevant == 'true' || steps.examples_changed.outputs.changed == 'true' with: node-version: 22 @@ -711,7 +711,7 @@ jobs: echo "cockpit=$cockpit_changed" >> "$GITHUB_OUTPUT" - name: Cache Playwright browsers if: steps.affected.outputs.website == 'true' - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }} @@ -792,8 +792,8 @@ jobs: echo "::error::examples/chat — e2e finished with ${{ needs.examples-chat-e2e.result }}; refusing to deploy the canonical demo." exit 1 fi - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm @@ -858,7 +858,7 @@ jobs: runs-on: ubuntu-latest if: ${{ always() && !cancelled() && github.ref == 'refs/heads/main' && github.event_name == 'push' }} steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Require ag-ui demo prerequisite jobs @@ -887,14 +887,14 @@ jobs: if [ "$ag_ui_changed" != "true" ]; then echo "::notice::No ag-ui demo files changed; skipping ag-ui demo deploy." fi - - uses: actions/setup-node@v6.3.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 if: steps.ag_ui_changed.outputs.changed == 'true' with: node-version: 22 cache: npm - name: Install uv if: steps.ag_ui_changed.outputs.changed == 'true' - uses: astral-sh/setup-uv@v8.0.0 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: python-version: '3.12' - if: steps.ag_ui_changed.outputs.changed == 'true' @@ -932,8 +932,8 @@ jobs: runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' && github.event_name == 'push' steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm @@ -943,7 +943,7 @@ jobs: env: LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }} - name: Cache Playwright browsers - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }} @@ -964,7 +964,7 @@ jobs: if: github.event_name == 'push' || needs.ci-scope.outputs.posthog == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 - name: Detect PostHog-relevant changes @@ -990,7 +990,7 @@ jobs: if [ "$posthog_relevant" != "true" ]; then echo "::notice::No PostHog tooling files changed — skipping dependency setup and drift check." fi - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 if: steps.posthog_preflight.outputs.relevant == 'true' with: node-version: '20' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..6974f258 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,38 @@ +name: CodeQL + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: '27 4 * * 1' + +permissions: + contents: read + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + permissions: + security-events: write + actions: read + contents: read + strategy: + fail-fast: false + matrix: + language: ['javascript-typescript', 'python'] + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Initialize CodeQL + uses: github/codeql-action/init@dd903d2e4f5405488e5ef1422510ee31c8b32357 # v3.36.2 + with: + languages: ${{ matrix.language }} + - name: Autobuild + uses: github/codeql-action/autobuild@dd903d2e4f5405488e5ef1422510ee31c8b32357 # v3.36.2 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@dd903d2e4f5405488e5ef1422510ee31c8b32357 # v3.36.2 + with: + category: '/language:${{ matrix.language }}' diff --git a/.github/workflows/deploy-ag-ui.yml b/.github/workflows/deploy-ag-ui.yml index 5b5ea20e..bf2347cb 100644 --- a/.github/workflows/deploy-ag-ui.yml +++ b/.github/workflows/deploy-ag-ui.yml @@ -25,9 +25,9 @@ jobs: name: Deploy ag-ui-dev to Railway runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm diff --git a/.github/workflows/deploy-langgraph.yml b/.github/workflows/deploy-langgraph.yml index 1b0d8d94..5ead692e 100644 --- a/.github/workflows/deploy-langgraph.yml +++ b/.github/workflows/deploy-langgraph.yml @@ -32,16 +32,16 @@ jobs: name: Deploy shared cockpit-dev to LangGraph Cloud runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm - run: npm ci - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.12' @@ -49,7 +49,7 @@ jobs: run: pip install uv - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Generate shared deployment manifest run: npx tsx scripts/generate-shared-deployment-config.ts diff --git a/.github/workflows/posthog-quality.yml b/.github/workflows/posthog-quality.yml index 3effa5c7..ae6b7459 100644 --- a/.github/workflows/posthog-quality.yml +++ b/.github/workflows/posthog-quality.yml @@ -29,8 +29,8 @@ jobs: name: Live telemetry contract and coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 cache: npm diff --git a/.github/workflows/publish-middleware-npm.yml b/.github/workflows/publish-middleware-npm.yml index 57b491c4..bbdc1a06 100644 --- a/.github/workflows/publish-middleware-npm.yml +++ b/.github/workflows/publish-middleware-npm.yml @@ -48,6 +48,9 @@ concurrency: env: DO_NOT_TRACK: '1' +permissions: + contents: read + jobs: build-and-publish: name: Build and publish @threadplane/middleware @@ -59,12 +62,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6.0.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Node 24 ships npm 11+, which fully implements npm trusted publishing # over OIDC. (The rest of CI runs on Node 22; this job needs Node 24.) - name: Setup Node - uses: actions/setup-node@v6.3.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 24 cache: npm diff --git a/.github/workflows/publish-middleware-python.yml b/.github/workflows/publish-middleware-python.yml index 624235e7..01731870 100644 --- a/.github/workflows/publish-middleware-python.yml +++ b/.github/workflows/publish-middleware-python.yml @@ -39,6 +39,9 @@ concurrency: group: publish-middleware-python cancel-in-progress: false +permissions: + contents: read + jobs: build-and-publish: name: Build and publish threadplane-middleware @@ -50,10 +53,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 with: version: "latest" @@ -75,5 +78,7 @@ jobs: - name: Publish to PyPI (real release — OIDC trusted publishing) if: ${{ inputs.dry_run == false }} - working-directory: packages/threadplane-middleware - run: uv publish dist/* + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 + with: + packages-dir: packages/threadplane-middleware/dist + attestations: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cd50db8a..ffbeac5e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,6 +18,9 @@ concurrency: env: DO_NOT_TRACK: '1' +permissions: + contents: read + jobs: publish: runs-on: ubuntu-latest @@ -27,13 +30,13 @@ jobs: env: NPM_PUBLISHABLE_PROJECTS: chat,langgraph,ag-ui,render,a2ui,licensing,telemetry steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Node 24 ships npm 11+ which fully implements npm trusted publishing # over OIDC. Node 22 is LTS but locked at npm 10.x, which has only # partial trusted-publishing support and fails OIDC on this registry. # The rest of CI (lint/test/build) runs on Node 22; this workflow # uses Node 24 specifically for the publish step. - - uses: actions/setup-node@v6.3.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 24 cache: npm diff --git a/.github/workflows/release-provenance.yml b/.github/workflows/release-provenance.yml new file mode 100644 index 00000000..07189a7e --- /dev/null +++ b/.github/workflows/release-provenance.yml @@ -0,0 +1,55 @@ +name: Release provenance (SLSA) + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + build-artifacts: + runs-on: ubuntu-latest + permissions: + contents: write # gh release upload attaches tarballs to the release + outputs: + hashes: ${{ steps.hash.outputs.hashes }} + env: + NPM_PUBLISHABLE_PROJECTS: chat,langgraph,ag-ui,render,a2ui,licensing,telemetry + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 24 + cache: npm + - run: npm ci + - name: Build publishable projects + env: + CACHEPLANE_LICENSE_PUBLIC_KEY: ${{ secrets.CACHEPLANE_LICENSE_PUBLIC_KEY }} + run: npx nx run-many -t build --projects="$NPM_PUBLISHABLE_PROJECTS" --skip-nx-cache + - name: Pack tarballs + run: | + mkdir -p release-artifacts + for p in chat langgraph ag-ui render a2ui licensing telemetry; do + npm pack "dist/libs/$p" --pack-destination release-artifacts + done + - name: Generate subject hashes + id: hash + run: | + cd release-artifacts + echo "hashes=$(sha256sum ./*.tgz | base64 -w0)" >> "$GITHUB_OUTPUT" + - name: Upload tarballs to the release + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload "${{ github.event.release.tag_name }}" -- release-artifacts/*.tgz --clobber + + provenance: + needs: [build-artifacts] + permissions: + actions: read + id-token: write + contents: write + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 + with: + base64-subjects: ${{ needs.build-artifacts.outputs.hashes }} + upload-assets: true diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 00000000..78daa0fa --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,41 @@ +name: Scorecard supply-chain security + +on: + branch_protection_rule: + schedule: + - cron: '18 5 * * 2' + push: + branches: [main] + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + security-events: write + id-token: write + contents: read + actions: read + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Run analysis + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + - name: Upload artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + - name: Upload to code-scanning + uses: github/codeql-action/upload-sarif@dd903d2e4f5405488e5ef1422510ee31c8b32357 # v3.36.2 + with: + sarif_file: results.sarif diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..17e0f61a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +# Contributing + +## Signed commits + +`main` requires signed commits. Configure SSH commit signing once: + +```bash +git config --global gpg.format ssh +git config --global user.signingkey ~/.ssh/id_ed25519.pub +git config --global commit.gpgsign true +git config --global tag.gpgsign true +``` + +Then add the same public key as a **Signing Key** at +. Commits merged through the GitHub UI and +bot commits (Renovate, Dependabot) are signed automatically. diff --git a/README.md b/README.md index ef3f12f1..7122b059 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,9 @@ LangGraph + + OpenSSF Scorecard +

--- diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..3ebda5e3 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +threadplane publishes patch-level releases of the `@threadplane/*` packages. +Only the latest published version of each package is supported with security +fixes. + +| Package scope | Supported | +| ------------- | --------- | +| `@threadplane/*` (latest) | ✅ | +| older versions | ❌ | + +## Reporting a Vulnerability + +Please report security issues privately via GitHub's +[private vulnerability reporting](https://github.com/cacheplane/angular-agent-framework/security/advisories/new). + +We aim to acknowledge reports within 5 business days and to provide a +remediation timeline after triage. Please do not open public issues for +security vulnerabilities. diff --git a/docs/superpowers/plans/2026-06-18-hvtrust-supply-chain-hardening.md b/docs/superpowers/plans/2026-06-18-hvtrust-supply-chain-hardening.md new file mode 100644 index 00000000..2ce71de5 --- /dev/null +++ b/docs/superpowers/plans/2026-06-18-hvtrust-supply-chain-hardening.md @@ -0,0 +1,727 @@ +# HVTrust Supply-Chain Hardening Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Raise threadplane's HVTracker "HVTrust" score by closing supply-chain evidence gaps with genuine hardening — OSSF Scorecard publishing, security/SAST/dependency config, pinned Actions, enforced commit signing, and SLSA-attested releases. + +**Architecture:** Pure repository + CI configuration. No application code changes. Work is sequenced into four PR-sized groups; group 1 alone makes the OSSF Scorecard measurable. "Tests" are verification commands: YAML validity (`actionlint`), `gh api` assertions on repo/branch settings, and `scorecard` score deltas. + +**Tech Stack:** GitHub Actions, OSSF Scorecard, CodeQL, Renovate, `pinact` (Action SHA pinning), SSH commit/tag signing, `slsa-github-generator`, npm OIDC trusted publishing (already in place), PyPI trusted publishing. + +**Spec:** [docs/superpowers/specs/2026-06-18-hvtrust-supply-chain-hardening-design.md](../specs/2026-06-18-hvtrust-supply-chain-hardening-design.md) + +**Manual owner steps (not automatable, called out where relevant):** install the Renovate GitHub App (Task 7); configure local SSH signing key on the maintainer machine (Task 9); optionally create a `SCORECARD_TOKEN` secret (noted in Task 5). + +--- + +## File Structure + +| File | Responsibility | Task | +|---|---|---| +| `.github/workflows/scorecard.yml` | Run + publish OSSF Scorecard | 5 | +| `.github/workflows/codeql.yml` | CodeQL SAST for JS/TS + Python | 4 | +| `SECURITY.md` | Vulnerability disclosure policy | 2 | +| `.github/workflows/publish.yml` | + top-level least-privilege perms | 3 | +| `.github/workflows/publish-middleware-npm.yml` | + top-level least-privilege perms | 3 | +| `.github/workflows/publish-middleware-python.yml` | + top-level perms; + PyPI attestations | 3, 13 | +| `renovate.json` | Dependency updates + Action SHA pinning | 7 | +| all `.github/workflows/*.yml` | Actions pinned by commit SHA | 8 | +| `CONTRIBUTING.md` | Document SSH commit/tag signing setup | 9 | +| `README.md` | OSSF Scorecard badge | 5 | +| `.github/workflows/release-provenance.yml` | SLSA provenance for Release artifacts | 12 | + +--- + +## Preconditions + +- [ ] **Confirm tooling is available** (install on demand in later tasks; just verify access now): + +Run: +```bash +gh auth status +command -v docker || echo "docker missing (needed for scorecard local run)" +npx --yes actionlint --version 2>/dev/null || echo "actionlint via npx ok" +``` +Expected: `gh` authenticated; note whether docker exists (Task 1 needs it or falls back to reading the first published run). + +--- + +## Group 1 — Make Scorecard measurable (hardening + publishing) + +### Task 1: Capture OSSF Scorecard baseline + +**Files:** +- Create: `docs/superpowers/audits/2026-06-18-scorecard-baseline.md` + +- [ ] **Step 1: Run Scorecard against the repo** + +Run (requires a token with public-repo read; classic PAT or `gh auth token`): +```bash +export GITHUB_AUTH_TOKEN=$(gh auth token) +docker run -e GITHUB_AUTH_TOKEN \ + gcr.io/openssf/scorecard:stable \ + --repo=github.com/cacheplane/angular-agent-framework --format=json > /tmp/scorecard-baseline.json +``` +If docker is unavailable, skip the run and record "baseline deferred to first published run" instead. + +- [ ] **Step 2: Record per-check scores** + +Run: +```bash +python3 -c "import json;d=json.load(open('/tmp/scorecard-baseline.json'));print('AGGREGATE',d['score']);[print(c['name'],c['score']) for c in d['checks']]" +``` + +- [ ] **Step 3: Write the baseline audit file** + +Create `docs/superpowers/audits/2026-06-18-scorecard-baseline.md` containing the aggregate score and the per-check table from Step 2 (paste the actual numbers). This is the before-state we measure deltas against in Task 14. + +- [ ] **Step 4: Commit** + +```bash +git add docs/superpowers/audits/2026-06-18-scorecard-baseline.md +git commit -m "docs(audit): OSSF Scorecard baseline before hardening" +``` + +### Task 2: SECURITY.md + private vulnerability reporting + +**Files:** +- Create: `SECURITY.md` + +- [ ] **Step 1: Create `SECURITY.md`** + +```markdown +# Security Policy + +## Supported Versions + +threadplane publishes patch-level releases of the `@threadplane/*` packages. +Only the latest published version of each package is supported with security +fixes. + +| Package scope | Supported | +| ------------- | --------- | +| `@threadplane/*` (latest) | ✅ | +| older versions | ❌ | + +## Reporting a Vulnerability + +Please report security issues privately via GitHub's +[private vulnerability reporting](https://github.com/cacheplane/angular-agent-framework/security/advisories/new). + +We aim to acknowledge reports within 5 business days and to provide a +remediation timeline after triage. Please do not open public issues for +security vulnerabilities. +``` + +- [ ] **Step 2: Enable private vulnerability reporting (repo setting)** + +Run: +```bash +gh api -X PUT repos/cacheplane/angular-agent-framework/private-vulnerability-reporting +``` +Expected: HTTP 204 (no output) or a JSON confirming enabled. + +- [ ] **Step 3: Verify the file is valid markdown and the setting took** + +Run: +```bash +test -f SECURITY.md && echo "SECURITY.md present" +gh api repos/cacheplane/angular-agent-framework/private-vulnerability-reporting --jq '.enabled' +``` +Expected: `SECURITY.md present` and `true`. + +- [ ] **Step 4: Commit** + +```bash +git add SECURITY.md +git commit -m "docs(security): add SECURITY.md vulnerability disclosure policy" +``` + +### Task 3: Top-level least-privilege permissions on publish workflows + +**Files:** +- Modify: `.github/workflows/publish.yml` (add top-level `permissions:` after the `env:` block, before `jobs:`) +- Modify: `.github/workflows/publish-middleware-npm.yml` +- Modify: `.github/workflows/publish-middleware-python.yml` + +- [ ] **Step 1: Add top-level `permissions` to `publish.yml`** + +Insert immediately before the `jobs:` line: +```yaml +permissions: + contents: read + +jobs: +``` +The existing job-level block (`contents: read` + `id-token: write`) stays as-is — job-level elevation is correct and is what Token-Permissions rewards. + +- [ ] **Step 2: Add the same top-level block to `publish-middleware-npm.yml` and `publish-middleware-python.yml`** + +Insert before each file's `jobs:` line: +```yaml +permissions: + contents: read + +jobs: +``` + +- [ ] **Step 3: Validate all three workflows parse** + +Run: +```bash +npx --yes actionlint .github/workflows/publish.yml .github/workflows/publish-middleware-npm.yml .github/workflows/publish-middleware-python.yml +``` +Expected: no output (exit 0). Note: `actionlint` may warn on shellcheck items unrelated to this change; only permission/syntax errors are blockers. + +- [ ] **Step 4: Confirm each file now has a top-level `permissions:`** + +Run: +```bash +for f in publish publish-middleware-npm publish-middleware-python; do + awk '/^permissions:/{found=1} /^jobs:/{print FILENAME, (found?"OK":"MISSING"); exit}' .github/workflows/$f.yml +done +``` +Expected: three `OK` lines. + +- [ ] **Step 5: Commit** + +```bash +git add .github/workflows/publish.yml .github/workflows/publish-middleware-npm.yml .github/workflows/publish-middleware-python.yml +git commit -m "ci: set least-privilege top-level token permissions on publish workflows" +``` + +### Task 4: CodeQL SAST workflow + +**Files:** +- Create: `.github/workflows/codeql.yml` + +- [ ] **Step 1: Create `.github/workflows/codeql.yml`** + +```yaml +name: CodeQL + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: '27 4 * * 1' + +permissions: + contents: read + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + permissions: + security-events: write + actions: read + contents: read + strategy: + fail-fast: false + matrix: + language: ['javascript-typescript', 'python'] + steps: + - name: Checkout + uses: actions/checkout@v6.0.2 + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: '/language:${{ matrix.language }}' +``` +(Actions left at tags here; Task 8 pins every Action by SHA across all workflows.) + +- [ ] **Step 2: Validate the workflow parses** + +Run: +```bash +npx --yes actionlint .github/workflows/codeql.yml +``` +Expected: no output (exit 0). + +- [ ] **Step 3: Commit** + +```bash +git add .github/workflows/codeql.yml +git commit -m "ci: add CodeQL SAST for javascript-typescript and python" +``` + +### Task 5: OSSF Scorecard workflow + README badge + +**Files:** +- Create: `.github/workflows/scorecard.yml` +- Modify: `README.md` (add badge near the top badge row) + +- [ ] **Step 1: Create `.github/workflows/scorecard.yml`** + +```yaml +name: Scorecard supply-chain security + +on: + branch_protection_rule: + schedule: + - cron: '18 5 * * 2' + push: + branches: [main] + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + security-events: write + id-token: write + contents: read + actions: read + steps: + - name: Checkout code + uses: actions/checkout@v6.0.2 + with: + persist-credentials: false + - name: Run analysis + uses: ossf/scorecard-action@v2.4.0 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + - name: Upload to code-scanning + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: results.sarif +``` +**Optional follow-up (note in PR description, do not block):** to unlock the Branch-Protection check, create a fine-grained read PAT secret `SCORECARD_TOKEN` and add `repo_token: ${{ secrets.SCORECARD_TOKEN }}` to the "Run analysis" step. The default `GITHUB_TOKEN` cannot read branch protection. + +- [ ] **Step 2: Add the badge to `README.md`** + +Find the existing badge row near the top of `README.md` (the line(s) containing other shield badges). Add, on its own line in that row: +```markdown +[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/cacheplane/angular-agent-framework/badge)](https://scorecard.dev/viewer/?uri=github.com/cacheplane/angular-agent-framework) +``` +If no badge row exists, add this line immediately under the top-level `# ` heading. + +- [ ] **Step 3: Validate the workflow parses** + +Run: +```bash +npx --yes actionlint .github/workflows/scorecard.yml +``` +Expected: no output (exit 0). + +- [ ] **Step 4: Commit** + +```bash +git add .github/workflows/scorecard.yml README.md +git commit -m "ci: add OSSF Scorecard workflow with public results + README badge" +``` + +### Task 6: Repo posture settings + +**Files:** none (GitHub repo settings via API) + +- [ ] **Step 1: Enable secret scanning + push protection + Dependabot security updates** + +Run: +```bash +gh api -X PATCH repos/cacheplane/angular-agent-framework \ + -f 'security_and_analysis[secret_scanning][status]=enabled' \ + -f 'security_and_analysis[secret_scanning_push_protection][status]=enabled' \ + -f 'security_and_analysis[dependabot_security_updates][status]=enabled' +``` +Expected: JSON response; no error. + +- [ ] **Step 2: Verify all three are enabled** + +Run: +```bash +gh api repos/cacheplane/angular-agent-framework --jq '.security_and_analysis | {secret_scanning:.secret_scanning.status, push_protection:.secret_scanning_push_protection.status, dependabot:.dependabot_security_updates.status}' +``` +Expected: all three `enabled`. + +- [ ] **Step 3: Record the change** + +No file to commit. Note in the PR description that repo posture settings were enabled (these are account-side, not in git). + +--- + +## Group 2 — Renovate + Action SHA pinning + +### Task 7: Renovate configuration + +**Files:** +- Create: `renovate.json` + +- [ ] **Step 1: Create `renovate.json`** + +```json +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + "helpers:pinGitHubActionDigests", + ":dependencyDashboard", + ":semanticCommits" + ], + "schedule": ["before 9am on monday"], + "prConcurrentLimit": 5, + "packageRules": [ + { + "matchManagers": ["github-actions"], + "groupName": "github-actions", + "pinDigests": true + }, + { + "matchManagers": ["npm"], + "matchUpdateTypes": ["minor", "patch"], + "groupName": "npm minor+patch" + } + ] +} +``` + +- [ ] **Step 2: Validate the config** + +Run: +```bash +npx --yes --package renovate -- renovate-config-validator renovate.json +``` +Expected: `Validating renovate.json` ... `Config validated successfully`. + +- [ ] **Step 3: Commit** + +```bash +git add renovate.json +git commit -m "ci: add Renovate config with GitHub Action digest pinning" +``` + +- [ ] **Step 4: MANUAL OWNER STEP — install the Renovate GitHub App** + +Install the Renovate app on `cacheplane/angular-agent-framework` at https://github.com/apps/renovate (or confirm the org already has it). This activates the config. Document completion in the PR; nothing to commit. + +### Task 8: Pin all GitHub Actions by commit SHA + +**Files:** +- Modify: every `.github/workflows/*.yml` + +- [ ] **Step 1: Pin all Actions by SHA with `pinact`** + +Run: +```bash +go install github.com/suzuki-shunsuke/pinact/cmd/pinact@latest 2>/dev/null || \ + brew install pinact +pinact run +``` +If neither `go` nor `brew` is available, use the Docker image: +```bash +docker run --rm -v "$PWD":/work -w /work -e GITHUB_TOKEN=$(gh auth token) \ + ghcr.io/suzuki-shunsuke/pinact:latest run +``` +This rewrites every `uses: org/action@vX` to `uses: org/action@<40-char-sha> # vX` in place. + +- [ ] **Step 2: Verify no Action remains unpinned** + +Run: +```bash +grep -rhE "uses: " .github/workflows/ | grep -vE "@[0-9a-f]{40}" | grep -E "@v?[0-9]" || echo "ALL PINNED" +``` +Expected: `ALL PINNED`. + +- [ ] **Step 3: Validate all workflows still parse** + +Run: +```bash +npx --yes actionlint .github/workflows/*.yml +``` +Expected: exit 0 (pre-existing shellcheck warnings, if any, are not blockers). + +- [ ] **Step 4: Commit** + +```bash +git add .github/workflows +git commit -m "ci: pin all GitHub Actions to commit SHAs" +``` + +--- + +## Group 3 — Commit signing + enforcement + +### Task 9: SSH commit/tag signing setup + CONTRIBUTING docs + +**Files:** +- Create or Modify: `CONTRIBUTING.md` (add a "Signed commits" section; create the file if absent) + +- [ ] **Step 1: MANUAL OWNER STEP — configure local SSH signing** + +On the maintainer machine: +```bash +git config --global gpg.format ssh +git config --global user.signingkey ~/.ssh/id_ed25519.pub # existing key +git config --global commit.gpgsign true +git config --global tag.gpgsign true +``` +Then add the SAME public key as a **Signing Key** (not just Authentication Key) at +https://github.com/settings/ssh/new with key type "Signing Key". + +- [ ] **Step 2: Verify a signed commit is produced** + +Run (in this repo, on a throwaway file): +```bash +git commit --allow-empty -m "chore: verify signing" -S +git log -1 --show-signature 2>&1 | grep -i "good \"git\" signature\|Good signature" && echo "SIGNED OK" +git reset --soft HEAD~1 # undo the throwaway commit +``` +Expected: `SIGNED OK`. + +- [ ] **Step 3: Document in `CONTRIBUTING.md`** + +Add (create the file with a `# Contributing` heading if it doesn't exist) a section: +```markdown +## Signed commits + +`main` requires signed commits. Configure SSH commit signing once: + +\`\`\`bash +git config --global gpg.format ssh +git config --global user.signingkey ~/.ssh/id_ed25519.pub +git config --global commit.gpgsign true +git config --global tag.gpgsign true +\`\`\` + +Then add the same public key as a **Signing Key** at +. Commits merged through the GitHub UI and +bot commits (Renovate, Dependabot) are signed automatically. +``` + +- [ ] **Step 4: Commit (signed)** + +```bash +git add CONTRIBUTING.md +git commit -m "docs(contributing): document required SSH commit signing" +``` + +### Task 10: Enforce required signatures on `main` + +**Files:** none (branch protection via API) + +- [ ] **Step 1: Enable required signatures on `main`** + +Run: +```bash +gh api -X POST repos/cacheplane/angular-agent-framework/branches/main/protection/required_signatures \ + -H "Accept: application/vnd.github+json" +``` +Expected: JSON with `"enabled": true`. + +- [ ] **Step 2: Verify enforcement is active** + +Run: +```bash +gh api repos/cacheplane/angular-agent-framework/branches/main/protection/required_signatures --jq '.enabled' +``` +Expected: `true`. + +- [ ] **Step 3: Record** + +No file to commit. Note in PR description that `main` now requires signed commits. + +--- + +## Group 4 — SLSA release artifacts + +### Task 11: Confirm signed tags flow through `nx release` + +**Files:** none (relies on Task 9 `tag.gpgsign=true`) + +- [ ] **Step 1: Verify a dry-run tag would be signed** + +Run: +```bash +git config --get tag.gpgsign +git tag -s _sigcheck -m "sig check" && git tag -v _sigcheck 2>&1 | grep -i "good signature" && echo "TAG SIGNING OK" +git tag -d _sigcheck +``` +Expected: `true`, then `TAG SIGNING OK`. + +- [ ] **Step 2: No commit** (configuration is local/global from Task 9). Note completion in PR description. + +### Task 12: SLSA provenance for GitHub Release artifacts + +**Files:** +- Create: `.github/workflows/release-provenance.yml` + +- [ ] **Step 1: Create `.github/workflows/release-provenance.yml`** + +```yaml +name: Release provenance (SLSA) + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + build-artifacts: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + hashes: ${{ steps.hash.outputs.hashes }} + env: + NPM_PUBLISHABLE_PROJECTS: chat,langgraph,ag-ui,render,a2ui,licensing,telemetry + steps: + - uses: actions/checkout@v6.0.2 + - uses: actions/setup-node@v6.3.0 + with: + node-version: 24 + cache: npm + - run: npm ci + - name: Build publishable projects + env: + CACHEPLANE_LICENSE_PUBLIC_KEY: ${{ secrets.CACHEPLANE_LICENSE_PUBLIC_KEY }} + run: npx nx run-many -t build --projects=$NPM_PUBLISHABLE_PROJECTS --skip-nx-cache + - name: Pack tarballs + run: | + mkdir -p release-artifacts + for p in chat langgraph ag-ui render a2ui licensing telemetry; do + npm pack "dist/libs/$p" --pack-destination release-artifacts + done + - name: Generate subject hashes + id: hash + run: | + cd release-artifacts + echo "hashes=$(sha256sum *.tgz | base64 -w0)" >> "$GITHUB_OUTPUT" + - name: Upload tarballs to the release + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload "${{ github.event.release.tag_name }}" release-artifacts/*.tgz --clobber + + provenance: + needs: [build-artifacts] + permissions: + actions: read + id-token: write + contents: write + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 + with: + base64-subjects: ${{ needs.build-artifacts.outputs.hashes }} + upload-assets: true +``` +This produces a signed `*.intoto.jsonl` provenance attached to the GitHub Release — what +Scorecard's Signed-Releases check inspects. (Actions pinned by SHA in the next Renovate +run / Task 8 re-run; the reusable SLSA workflow is referenced by its release tag as required +by the generator.) + +- [ ] **Step 2: Validate the workflow parses** + +Run: +```bash +npx --yes actionlint .github/workflows/release-provenance.yml +``` +Expected: exit 0. (`actionlint` may not resolve the reusable-workflow outputs of the SLSA call — a `workflow_call` warning there is acceptable.) + +- [ ] **Step 3: Commit** + +```bash +git add .github/workflows/release-provenance.yml +git commit -m "ci: generate SLSA provenance for GitHub Release artifacts" +``` + +### Task 13: Enable PyPI attestations + +**Files:** +- Modify: `.github/workflows/publish-middleware-python.yml` + +- [ ] **Step 1: Inspect the current publish step** + +Run: +```bash +grep -nE "pypa/gh-action-pypi-publish|twine|python -m build|attestations" .github/workflows/publish-middleware-python.yml +``` +Determine whether it uses `pypa/gh-action-pypi-publish` (supports `attestations: true`) or raw `twine`. + +- [ ] **Step 2a: If it uses `pypa/gh-action-pypi-publish`** — add `attestations: true` under its `with:`: + +```yaml + - name: Publish to PyPI (real release — OIDC trusted publishing) + uses: pypa/gh-action-pypi-publish@release/v1 + with: + attestations: true +``` + +- [ ] **Step 2b: If it uses raw `twine`** — switch the publish step to `pypa/gh-action-pypi-publish@release/v1` with `attestations: true` (it defaults to OIDC trusted publishing, matching the existing `id-token: write`). Keep any existing `packages-dir` pointing at the built `dist/`. + +- [ ] **Step 3: Validate** + +Run: +```bash +npx --yes actionlint .github/workflows/publish-middleware-python.yml +``` +Expected: exit 0. + +- [ ] **Step 4: Commit** + +```bash +git add .github/workflows/publish-middleware-python.yml +git commit -m "ci: enable PyPI build provenance attestations on middleware publish" +``` + +### Task 14: Final verification — Scorecard delta + +**Files:** +- Modify: `docs/superpowers/audits/2026-06-18-scorecard-baseline.md` (append an "after" section) + +- [ ] **Step 1: Re-run Scorecard** (after Group 1–2 are merged to `main`; the published run is authoritative) + +Run: +```bash +export GITHUB_AUTH_TOKEN=$(gh auth token) +docker run -e GITHUB_AUTH_TOKEN gcr.io/openssf/scorecard:stable \ + --repo=github.com/cacheplane/angular-agent-framework --format=json > /tmp/scorecard-after.json +python3 -c "import json;d=json.load(open('/tmp/scorecard-after.json'));print('AGGREGATE',d['score']);[print(c['name'],c['score']) for c in d['checks']]" +``` + +- [ ] **Step 2: Append the delta to the audit file** + +Add an "After hardening" table next to the baseline, and confirm these checks improved: +`Security-Policy`, `SAST`, `Token-Permissions`, `Pinned-Dependencies`, `Dependency-Update-Tool`, +`Signed-Releases`. Note any check still at 0 with a one-line reason. + +- [ ] **Step 3: Confirm the badge renders** + +Run: +```bash +curl -sI "https://api.scorecard.dev/projects/github.com/cacheplane/angular-agent-framework/badge" | head -1 +``` +Expected: `HTTP/2 200` (may take up to one scheduled run after first publish to populate). + +- [ ] **Step 4: Commit** + +```bash +git add docs/superpowers/audits/2026-06-18-scorecard-baseline.md +git commit -m "docs(audit): record OSSF Scorecard delta after hardening" +``` + +--- + +## Self-Review notes + +- **Spec coverage:** Scorecard workflow (T5), SECURITY.md + private reporting (T2), CodeQL (T4), Renovate + SHA pinning (T7, T8), token permissions (T3), repo settings (T6), commit signing + enforcement (T9, T10), signed tags + SLSA artifacts (T11, T12), PyPI attestations (T13), verification (T1, T14). All spec sections mapped. +- **Out of scope (per spec), intentionally absent:** HVTracker registration, Adoption optimization, Fuzzing, CII-Best-Practices. +- **Sequencing guard:** Task 8 pins Actions in workflows created by Tasks 4, 5, 12. Task 14 must run only after Groups 1–2 land on `main`. diff --git a/docs/superpowers/specs/2026-06-18-hvtrust-supply-chain-hardening-design.md b/docs/superpowers/specs/2026-06-18-hvtrust-supply-chain-hardening-design.md new file mode 100644 index 00000000..e81e4daf --- /dev/null +++ b/docs/superpowers/specs/2026-06-18-hvtrust-supply-chain-hardening-design.md @@ -0,0 +1,145 @@ +# HVTrust Supply-Chain Hardening — Design + +**Date:** 2026-06-18 +**Status:** Approved (brainstorm), pending implementation plan +**Owner:** Brian Love + +## Background + +[HVTracker](https://github.com/YugantM/hvtracker) (live at hvtracker.net) is a public +trust registry that ranks open-source AI agent projects — threadplane's exact category — +by an **HVTrust** score (0–100). It deliberately rewards *verifiable supply-chain evidence* +over GitHub stars. + +### How HVTrust is computed + +From `compute_trust_score()` in HVTracker's `fetch_and_build.py`: + +``` +HVTrust = clamp( gate( confidence × Σ(weightᵢ · dimᵢ) − penalties ) ) +``` + +Five weighted dimensions (each a 0–1 sub-score × weight): + +| Dimension | Weight | Inputs | +|---|---|---| +| Safety / Integrity | 25 | `0.5·OSSF-Scorecard + 0.3·has_provenance + 0.2·signed_commits_ratio` | +| Identity / Provenance | 18 | `0.6·listed_in_registry + 0.4·has_provenance` | +| Transparency | 17 | `0.5·license_present + 0.5·OSSF-Scorecard` | +| Maintenance | 20 | `0.6·freshness(180d) + 0.4·log(weekly_commits)` | +| Adoption | 20 | `0.6·log(stars/100k) + 0.4·log(downloads/1M)`, capped | + +Modifiers: **confidence** = present÷applicable signal types (floored 0.4); +**penalties** = −10 if no push in >365 days; **gate** caps delisted/rejected at 25, +legacy at 70. **Evidence grade** is a band of the final score: A ≥80, B ≥65, C ≥50, D <50. + +### threadplane's measured baseline (2026-06-18) + +| Signal | Value | +|---|---| +| License | MIT ✓ | +| npm provenance (SLSA attestations) | **Present ✓** on all published `@threadplane/*` | +| Last push / freshness | today (max) | +| Weekly commits | ~29 (843 / 90d) | +| Stars | 99 | +| Weekly downloads (scope) | ~450 across 8 published packages | +| Commit verification on `main` | All recent commits `verification.reason: valid` (GitHub squash-merge signing) | +| OSSF Scorecard | **Not run** | +| Listed on HVTracker | No (handled separately by owner) | +| Repo hardening | No SECURITY.md, no Dependabot/Renovate, no CodeQL, Actions pinned by tag not SHA | + +**Computed today (confidence ≈ 1.0):** ~49 (auto-crawl, not listed, no Scorecard) → Grade D; +~70 once listed + Scorecard ~5.0 → Grade B. The dominant lever is the **OSSF Scorecard**, +whose score feeds both Safety (×0.5) and Transparency (×0.5) — ~21 points of weight. + +## Goal + +Raise threadplane's HVTrust by closing *evidence-publishing* gaps with genuine +supply-chain improvements. No score gaming. Registering with HVTracker and chasing +Adoption are explicitly **out of scope**. + +## Traceability + +| Change | OSSF Scorecard check | HVTrust effect | +|---|---|---| +| Scorecard workflow + `publish_results` | (enables measurement + badge) | Makes Safety/Transparency computable; HVTracker ingests it | +| `SECURITY.md` + private vuln reporting | Security-Policy (0→10) | ↑ Scorecard → Safety/Transparency | +| CodeQL workflow | SAST (0→10) | ↑ Scorecard | +| Renovate + pin Actions by SHA | Dependency-Update-Tool + Pinned-Dependencies | ↑ Scorecard | +| Top-level `permissions:` on 3 publish workflows | Token-Permissions | ↑ Scorecard | +| Secret scanning + push protection + Dependabot security updates | (repo posture) | ↑ Scorecard | +| Require signed commits on `main` + SSH-sign local | (locks signed ratio) | Safety ×0.2 held at max | +| Signed git tags + SLSA provenance on Release artifacts | Signed-Releases (0→high) | ↑ Scorecard; Safety/Identity provenance | + +## Components + +### 1. OSSF Scorecard workflow (`.github/workflows/scorecard.yml`) +- `ossf/scorecard-action`, **pinned by SHA**. +- Triggers: weekly `schedule`, `push` to `main`, `branch_protection_rule`. +- Least-privilege perms: `security-events: write`, `id-token: write`, `contents: read`, `actions: read`. +- `publish_results: true` (publishes to the public OpenSSF API — required for the badge and + for HVTracker to read). SARIF uploaded to GitHub code scanning. +- Add the Scorecard badge to `README.md`. +- **Optional follow-up:** a fine-grained read `SCORECARD_TOKEN` secret to unlock the + Branch-Protection check (the default `GITHUB_TOKEN` can't read branch protection). + Flagged, not required for the first pass. + +### 2. Hardening files +- `SECURITY.md` — supported versions + private reporting via GitHub Security Advisories. + Enable **Private vulnerability reporting** in repo settings. +- `.github/workflows/codeql.yml` — CodeQL for `javascript-typescript` **and** `python` + (the repo ships the Python middleware). Pinned by SHA, minimal perms. +- Add top-level `permissions: contents: read` to `publish.yml`, `publish-middleware-npm.yml`, + `publish-middleware-python.yml` (the only 3 of 8 workflows lacking it; job-level + `id-token: write` already elevates where needed). + +### 3. Renovate (`renovate.json`) +- Extends `config:recommended` + `helpers:pinGitHubActionDigests` (pins every Action by SHA + and keeps the SHAs current — satisfies Dependency-Update-Tool **and** Pinned-Dependencies). +- Grouped, scheduled updates to limit PR noise. +- Initial bulk SHA-pinning pass across all 8 existing workflows (via `pinact` locally) so we + don't wait on Renovate's first run. +- **Manual step (owner):** install the Renovate GitHub App on the repo. The config lands in + the PR. + +### 4. Commit signing — enforce + sign local +- Local SSH commit signing: `gpg.format=ssh`, `commit.gpgsign=true`, existing SSH key as + `user.signingkey`; add that key as a **signing key** on GitHub. Capture exact setup in + `CONTRIBUTING.md`. +- Enforce `required_signatures` on `main` branch protection (`gh api`). Safe: GitHub + squash-merges, Renovate-app commits, and Dependabot commits are all auto-signed; only stray + unsigned *direct* pushes to `main` are rejected (intended). + +### 5. Release signing — full SLSA artifacts +- Signed tags: `tag.gpgsign=true` so `nx release` tags are SSH-signed (piggybacks on #4). + Existing npm provenance untouched. +- GitHub Release provenance: add `slsa-framework/slsa-github-generator` (generic generator) + to the tag-triggered release flow — hash the built artifacts (npm pack tarballs of the + publishable libs), generate a signed `*.intoto.jsonl`, attach it to the GitHub Release. + This is what Scorecard's Signed-Releases check inspects (Release assets), complementing the + registry-side npm provenance. +- PyPI: enable attestations in the Python publish (`gh-action-pypi-publish` with + `attestations: true`). +- Heaviest component; sequenced last. + +## Sequencing + +1. Hardening files + token perms + Scorecard workflow + repo settings *(makes Scorecard measurable)* +2. Renovate + SHA pinning +3. Commit signing + enforcement +4. SLSA release artifacts + +Each step is an independent PR-sized chunk. + +## Verification + +- Run `scorecard` locally (or read the first published run) before/after to confirm check deltas. +- Confirm the badge renders in `README.md`. +- Confirm a dry-run release produces and attaches the SLSA provenance `*.intoto.jsonl`. +- Confirm `required_signatures` is active via `gh api .../branches/main/protection`. + +## Out of scope + +- Registering with HVTracker (owner handling separately). +- Adoption optimization (log-capped; not worth the effort). +- Fuzzing and CII-Best-Practices Scorecard checks (high effort, low marginal HVTrust return). diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..7c24ad44 --- /dev/null +++ b/renovate.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + "helpers:pinGitHubActionDigests", + ":dependencyDashboard", + ":semanticCommits" + ], + "schedule": ["before 9am on monday"], + "prConcurrentLimit": 5, + "packageRules": [ + { + "matchManagers": ["github-actions"], + "groupName": "github-actions", + "pinDigests": true + }, + { + "matchManagers": ["npm"], + "matchUpdateTypes": ["minor", "patch"], + "groupName": "npm minor+patch" + } + ] +}