Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .devcontainer/devcontainer-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"features": {
"ghcr.io/braun-daniel/devcontainer-features/fzf:1": {
"version": "1.0.0",
"resolved": "ghcr.io/braun-daniel/devcontainer-features/fzf@sha256:07670aa4fce4a1976c68dc0c296021a9e79dfd9e6522a6eae09d0584c62637bc",
"integrity": "sha256:07670aa4fce4a1976c68dc0c296021a9e79dfd9e6522a6eae09d0584c62637bc"
},
"ghcr.io/braun-daniel/devcontainer-features/spaceship:1": {
"version": "1.0.0",
"resolved": "ghcr.io/braun-daniel/devcontainer-features/spaceship@sha256:86c8632fa2a1ca07511d691500cbaf816fd5172af8bc1b957a14b9b23345379d",
"integrity": "sha256:86c8632fa2a1ca07511d691500cbaf816fd5172af8bc1b957a14b9b23345379d"
},
"ghcr.io/devcontainers/features/common-utils:2": {
"version": "2.5.7",
"resolved": "ghcr.io/devcontainers/features/common-utils@sha256:dbf431d6b42d55cde50fa1df75c7f7c3999a90cde6d73f7a7071174b3c3d0cc4",
"integrity": "sha256:dbf431d6b42d55cde50fa1df75c7f7c3999a90cde6d73f7a7071174b3c3d0cc4"
},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
"version": "1.9.1",
"resolved": "ghcr.io/devcontainers/features/docker-outside-of-docker@sha256:dc89605f01ff2f24252c61f7c8ba2a58ccdbc14f2ebf87a7952d9e2b89834850",
"integrity": "sha256:dc89605f01ff2f24252c61f7c8ba2a58ccdbc14f2ebf87a7952d9e2b89834850"
},
"ghcr.io/devcontainers/features/git:1": {
"version": "1.3.5",
"resolved": "ghcr.io/devcontainers/features/git@sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251",
"integrity": "sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251"
},
"ghcr.io/devcontainers/features/github-cli:1": {
"version": "1.1.0",
"resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671",
"integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671"
},
"ghcr.io/devcontainers/features/go:1": {
"version": "1.3.4",
"resolved": "ghcr.io/devcontainers/features/go@sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032",
"integrity": "sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032"
},
"ghcr.io/devcontainers/features/node:1": {
"version": "1.7.1",
"resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6",
"integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6"
},
"ghcr.io/devcontainers/features/python:1": {
"version": "1.8.0",
"resolved": "ghcr.io/devcontainers/features/python@sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511",
"integrity": "sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511"
},
"ghcr.io/jungaretti/features/ripgrep:1": {
"version": "1.0.1",
"resolved": "ghcr.io/jungaretti/features/ripgrep@sha256:c0922b6f4a9184080c8435b6ef25983dee6c734733f0a749d852ad0d33de2253",
"integrity": "sha256:c0922b6f4a9184080c8435b6ef25983dee6c734733f0a749d852ad0d33de2253"
},
"ghcr.io/meaningful-ooo/devcontainer-features/homebrew:2": {
"version": "2.0.5",
"resolved": "ghcr.io/meaningful-ooo/devcontainer-features/homebrew@sha256:c49ba0f6275bdd0474f4c7fcf37fc84b5739838b110c7da9c16d6a409ae48a86",
"integrity": "sha256:c49ba0f6275bdd0474f4c7fcf37fc84b5739838b110c7da9c16d6a409ae48a86"
},
"ghcr.io/michidk/devcontainers-features/bun:1": {
"version": "1.0.1",
"resolved": "ghcr.io/michidk/devcontainers-features/bun@sha256:568cc553062d184932ba993db954d4ace2bda3907d129477ce174777ac686dbd",
"integrity": "sha256:568cc553062d184932ba993db954d4ace2bda3907d129477ce174777ac686dbd"
},
"ghcr.io/va-h/devcontainers-features/uv:1": {
"version": "1.1.4",
"resolved": "ghcr.io/va-h/devcontainers-features/uv@sha256:a15737142539d150ef4d358e2d6a7424a0a2f3dc43b29a3aa1b50162a8b11bc1",
"integrity": "sha256:a15737142539d150ef4d358e2d6a7424a0a2f3dc43b29a3aa1b50162a8b11bc1"
}
}
}
103 changes: 103 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":semanticCommits"
],
"rangeStrategy": "pin",
"automerge": false,
"labels": ["supply-chain"],
"prConcurrentLimit": 3,
"prHourlyLimit": 1,
"schedule": ["before 7am on monday"],
"dependencyDashboard": true,
"major": {
"enabled": false
},
"pinDigests": false,
"packageRules": [
{
"description": "High-risk packages get individual PRs with extra labels (no grouping).",
"matchPackageNames": [
"@colbymchenry/codegraph",
"agent-browser",
"rtk-installer",
"golangci-lint-installer",
"claude-code-installer"
],
"addLabels": ["supply-chain:high-risk"],
"groupName": null,
"automerge": false
},
{
"description": "Lower-risk dev-dep bumps may be grouped into a single weekly PR.",
"matchManagers": ["regex"],
"matchFileNames": ["installer/upstreams.yaml"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "manifest dev-dep updates",
"groupSlug": "manifest-dev-deps"
},
{
"description": "Curl entries that do not resolve to a GitHub repo (claude.ai/install.sh, astral.sh/uv/install.sh, bun.sh/install) cannot be auto-updated by Renovate's github-tags datasource. Flag them so the maintainer re-pins manually.",
"matchPackageNames": [
"claude-code-installer",
"uv-installer",
"bun-installer"
],
"addLabels": ["needs-manual-bump"]
},
{
"description": "Group all minor/patch devDependency bumps into a single weekly PR per package.json. Cuts noise on the marketing site / docusaurus / pilot devDeps.",
"matchDepTypes": ["devDependencies"],
"matchUpdateTypes": ["minor", "patch", "pin"],
"groupName": "dev-dependencies (weekly)",
"groupSlug": "dev-deps-weekly"
},
{
"description": "Skip the marketing site + docusaurus entirely — they are not on the supply-chain audit path. Re-enable per-package if a CVE forces it.",
"matchFileNames": [
"docs/site/**",
"docs/docusaurus/**"
],
"enabled": false
}
],
"customManagers": [
{
"customType": "regex",
"fileMatch": ["^installer/upstreams\\.yaml$"],
"matchStrings": [
"id:\\s+(?<depName>[\\w-]+)\\n(?:[^\\n]+\\n){0,8}?\\s+source_type:\\s+npm\\n(?:[^\\n]+\\n){0,8}?\\s+source_url:\\s+(?<packageName>\"?[^\"\\n]+\"?)\\n(?:[^\\n]+\\n){0,8}?\\s+version:\\s+\"?(?<currentValue>[^\"\\n]+)\"?"
],
"datasourceTemplate": "npm",
"versioningTemplate": "npm"
},
{
"customType": "regex",
"fileMatch": ["^installer/upstreams\\.yaml$"],
"matchStrings": [
"id:\\s+(?<depName>[\\w-]+)\\n(?:[^\\n]+\\n){0,8}?\\s+source_type:\\s+pypi\\n(?:[^\\n]+\\n){0,8}?\\s+source_url:\\s+(?<packageName>[^\\n]+)\\n(?:[^\\n]+\\n){0,8}?\\s+version:\\s+\"?(?<currentValue>[^\"\\n]+)\"?"
],
"datasourceTemplate": "pypi",
"versioningTemplate": "pep440"
},
{
"customType": "regex",
"fileMatch": ["^installer/upstreams\\.yaml$"],
"matchStrings": [
"id:\\s+(?<depName>[\\w-]+)\\n(?:[^\\n]+\\n){0,8}?\\s+source_type:\\s+brew\\n(?:[^\\n]+\\n){0,8}?\\s+brew_formula:\\s+\"?(?<packageName>[^\"\\n]+)\"?\\n(?:[^\\n]+\\n){0,8}?\\s+version:\\s+\"?(?<currentValue>[^\"\\n]+)\"?"
],
"datasourceTemplate": "homebrew",
"versioningTemplate": "loose"
},
{
"customType": "regex",
"fileMatch": ["^installer/upstreams\\.yaml$"],
"matchStrings": [
"id:\\s+(?<depName>[\\w-]+)\\n(?:[^\\n]+\\n){0,8}?\\s+source_type:\\s+curl\\n(?:[^\\n]+\\n){0,8}?\\s+source_url:\\s+https:\\/\\/(?:raw\\.)?github(?:usercontent)?\\.com\\/(?<packageName>[^\\/]+\\/[^\\/]+)\\/[^\\n]+\\n(?:[^\\n]+\\n){0,8}?\\s+version:\\s+\"?(?<currentValue>[^\"\\n]+)\"?"
],
"datasourceTemplate": "github-tags",
"versioningTemplate": "loose"
}
]
}
40 changes: 40 additions & 0 deletions .github/workflows/release-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,48 @@ concurrency:
cancel-in-progress: true

jobs:
supply-chain-gate:
# Cross-workflow `needs:` cannot link to supply-chain.yml. Poll the Checks
# API for the `supply-chain` check on this SHA — both workflows start on
# the same push, so we wait up to 12 min for it to complete.
name: Supply-Chain Gate
permissions:
contents: read
checks: read
runs-on: ubuntu-latest
Comment on lines +15 to +23

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add timeout-minutes to guard against hung API calls.

The script enforces a 12-minute programmatic deadline, but the job itself carries the 6-hour GitHub default. A single github.rest.checks.listForRef call that hangs indefinitely (e.g., during a GitHub API degradation event) would bypass the Date.now() deadline guard — it is only evaluated at the top of the loop, after the awaited call returns. Adding a job-level timeout ensures the runner is always released.

🛡️ Proposed fix
     name: Supply-Chain Gate
+    timeout-minutes: 15
     permissions:
       contents: read
       checks: read
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-dev.yml around lines 15 - 23, The Supply-Chain
Gate job (supply-chain-gate) lacks a job-level timeout, so add a timeout-minutes
setting under the job definition to guard against a hung
github.rest.checks.listForRef call; specifically, update the supply-chain-gate
job configuration to include a reasonable timeout-minutes value (e.g., 15) so
the runner is forcibly cancelled if the polling loop blocks indefinitely.

steps:
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const sleep = ms => new Promise(r => setTimeout(r, ms));
const deadline = Date.now() + 12 * 60 * 1000;
let last = null;
while (Date.now() < deadline) {
const { data } = await github.rest.checks.listForRef({
...context.repo, ref: context.sha
});
const sc = data.check_runs.find(r => r.name === 'supply-chain');
last = sc;
if (sc && sc.status === 'completed') {
if (sc.conclusion === 'success') return;
core.setFailed(
`supply-chain check concluded ${sc.conclusion} for ${context.sha}; prerelease blocked.`
);
return;
}
await sleep(20000);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
core.setFailed(
`supply-chain check did not complete within 12m for ${context.sha} ` +
`(last status: ${last?.status ?? 'missing'}); prerelease blocked.`
);

security-scan:
name: Security Scan (Trivy)
permissions:
contents: read
runs-on: ubuntu-latest
needs: supply-chain-gate
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
Expand Down Expand Up @@ -65,6 +102,7 @@ jobs:
permissions:
contents: read
runs-on: ubuntu-latest
needs: supply-chain-gate
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
Expand Down Expand Up @@ -100,6 +138,7 @@ jobs:
permissions:
contents: read
runs-on: ubuntu-latest
needs: supply-chain-gate
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
Expand Down Expand Up @@ -132,6 +171,7 @@ jobs:
permissions:
contents: read
runs-on: ubuntu-latest
needs: supply-chain-gate
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
Expand Down
49 changes: 44 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,51 @@ jobs:
echo "Not a release trigger commit - skipping"
echo "should_run=false" >> "$GITHUB_OUTPUT"

supply-chain-gate:
# Cross-workflow `needs:` doesn't link supply-chain.yml to this workflow.
# Poll the Checks API for the `supply-chain` check on the release SHA and
# fail when it concludes anything other than `success`. Both workflows
# start on the same push, so we wait up to 12 min for it to land.
name: Supply-Chain Gate
permissions:
contents: read
checks: read
runs-on: ubuntu-latest
needs: check-trigger
if: needs.check-trigger.outputs.should_run == 'true'
steps:
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const sleep = ms => new Promise(r => setTimeout(r, ms));
const deadline = Date.now() + 12 * 60 * 1000;
let last = null;
while (Date.now() < deadline) {
const { data } = await github.rest.checks.listForRef({
...context.repo, ref: context.sha
});
const sc = data.check_runs.find(r => r.name === 'supply-chain');
last = sc;
if (sc && sc.status === 'completed') {
if (sc.conclusion === 'success') return;
core.setFailed(
`supply-chain check concluded ${sc.conclusion} for ${context.sha}; release blocked.`
);
return;
}
await sleep(20000);
}
core.setFailed(
`supply-chain check did not complete within 12m for ${context.sha} ` +
`(last status: ${last?.status ?? 'missing'}); release blocked.`
);

security-scan:
name: Security Scan (Trivy)
permissions:
contents: read
runs-on: ubuntu-latest
needs: check-trigger
needs: [check-trigger, supply-chain-gate]
if: needs.check-trigger.outputs.should_run == 'true'
steps:
- name: Checkout code
Expand Down Expand Up @@ -114,7 +153,7 @@ jobs:
permissions:
contents: read
runs-on: ubuntu-latest
needs: check-trigger
needs: [check-trigger, supply-chain-gate]
if: needs.check-trigger.outputs.should_run == 'true'
steps:
- name: Checkout code
Expand Down Expand Up @@ -149,7 +188,7 @@ jobs:
permissions:
contents: read
runs-on: ubuntu-latest
needs: check-trigger
needs: [check-trigger, supply-chain-gate]
if: needs.check-trigger.outputs.should_run == 'true'
steps:
- name: Checkout code
Expand Down Expand Up @@ -181,7 +220,7 @@ jobs:
permissions:
contents: read
runs-on: ubuntu-latest
needs: check-trigger
needs: [check-trigger, supply-chain-gate]
if: needs.check-trigger.outputs.should_run == 'true'
steps:
- name: Checkout code
Expand Down Expand Up @@ -227,7 +266,7 @@ jobs:
permissions:
contents: write
runs-on: ubuntu-latest
needs: check-trigger
needs: [check-trigger, supply-chain-gate]
if: needs.check-trigger.outputs.should_run == 'true'
outputs:
should_release: ${{ steps.check.outputs.should_release }}
Expand Down
Loading
Loading