Skip to content

Commit d686eb1

Browse files
authored
feat: supply-chain monitoring + credential scanning + console settings
## Supply-chain monitoring (V1) - `installer/upstreams.yaml` + `installer/manifest.py` — single source of truth for every upstream (37 entries: 13 npm + 13 brew + 7 curl + 4 pypi). Every entry pinned by exact version + sha256 (curl) + last_audited. - `installer/steps/dependencies.py` + `prerequisites.py` — every install_* reads the manifest. `--ignore-scripts` denied by default; `better-sqlite3` is the only allow with documented justification. `_curl_pipe_with_hash_verify` helper does sha256-verified curl-then-execute with 0o600 temp files and cleanup on success/mismatch. Soft-pin path for vendor-managed endpoints (`claude.ai/install.sh`) warns + proceeds. `_verify_homebrew_tap` rejects formulas from unexpected taps before `brew install`. - `.mcp.json` + `pilot/.mcp.json` — every npx-launched MCP server pinned `@<version>`. - `install.sh` + `launcher/build.py` — bootstrap surface pinned: `uv` installer sha256-verified; every `--with` pinned (`rich==14.2.0`, `certifi==2026.4.22`, `PyYAML==6.0.3`, `cryptography==46.0.6`). - `scripts/check_manifest_drift.py` — regex drift checker (6 files, MCP cross-ref, `# noqa: drift-check` requires non-empty justification, strict semver, UnicodeDecodeError → Finding). - `.github/workflows/supply-chain.yml` — schema validation + drift gate on PR + push + nightly cron + manual dispatch. Lean (~50 lines). - `.github/renovate.json` — manifest-aware customManagers for npm/brew/curl/pypi; high-risk packages get individual PRs; non-GitHub curl entries get `needs-manual-bump` label; majors disabled; pinDigests off; devDeps grouped weekly; docs/site + docs/docusaurus skipped. - `release.yml` + `release-dev.yml` — `supply-chain-gate` job uses GitHub Checks API to block release tags when supply-chain is red (12-min poll handles same-push race). V1 explicit non-goals: OpenSSF Scorecard scoring loop, Socket.dev workflow step, branch-protection required check. ## Credential scanning hooks - `pilot/hooks/credential_scanner.py` + `_lib/secret_scanner.py` + `_lib/allow_tags.py` — block credentials in prompts, file reads, bash commands, and git commits. `[allow-secret]` / `[allow-all]` tags from user-role messages bypass per-call. - `pilot/rules/security-credentials.md`. ## Console + spec/fix workflow - Settings UI: Console + Security side-by-side (2-col grid), both before Spec Workflow Review Agents. Worker URL no longer truncates; cards stretch to equal height. - `launcher/model_config.py`: spec-implement + spec-verify default to `opus` (the v9 migration assumption). Max plan users now correctly land on `opus[1m]` instead of `sonnet[1m]` (Max doesn't include sonnet 1M). - `installer/steps/config_migration.py`: fresh installs now run all migrations (including v9's subscription-aware spec-implement/spec-verify defaulting) instead of skipping when config.json doesn't exist. - `pilot/skills/{fix,spec-plan,spec-verify}` — improved review flow. - `pilot/agents/{changes,spec}-review-codex.md` — Codex prompt templates. - `pilot/hooks/spec_stop_guard.py` — tighter stop-guard. ## Tests 2168 Python pass; 1485 console pass; drift checker green.
1 parent c28cb23 commit d686eb1

56 files changed

Lines changed: 6141 additions & 408 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"features": {
3+
"ghcr.io/braun-daniel/devcontainer-features/fzf:1": {
4+
"version": "1.0.0",
5+
"resolved": "ghcr.io/braun-daniel/devcontainer-features/fzf@sha256:07670aa4fce4a1976c68dc0c296021a9e79dfd9e6522a6eae09d0584c62637bc",
6+
"integrity": "sha256:07670aa4fce4a1976c68dc0c296021a9e79dfd9e6522a6eae09d0584c62637bc"
7+
},
8+
"ghcr.io/braun-daniel/devcontainer-features/spaceship:1": {
9+
"version": "1.0.0",
10+
"resolved": "ghcr.io/braun-daniel/devcontainer-features/spaceship@sha256:86c8632fa2a1ca07511d691500cbaf816fd5172af8bc1b957a14b9b23345379d",
11+
"integrity": "sha256:86c8632fa2a1ca07511d691500cbaf816fd5172af8bc1b957a14b9b23345379d"
12+
},
13+
"ghcr.io/devcontainers/features/common-utils:2": {
14+
"version": "2.5.7",
15+
"resolved": "ghcr.io/devcontainers/features/common-utils@sha256:dbf431d6b42d55cde50fa1df75c7f7c3999a90cde6d73f7a7071174b3c3d0cc4",
16+
"integrity": "sha256:dbf431d6b42d55cde50fa1df75c7f7c3999a90cde6d73f7a7071174b3c3d0cc4"
17+
},
18+
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
19+
"version": "1.9.1",
20+
"resolved": "ghcr.io/devcontainers/features/docker-outside-of-docker@sha256:dc89605f01ff2f24252c61f7c8ba2a58ccdbc14f2ebf87a7952d9e2b89834850",
21+
"integrity": "sha256:dc89605f01ff2f24252c61f7c8ba2a58ccdbc14f2ebf87a7952d9e2b89834850"
22+
},
23+
"ghcr.io/devcontainers/features/git:1": {
24+
"version": "1.3.5",
25+
"resolved": "ghcr.io/devcontainers/features/git@sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251",
26+
"integrity": "sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251"
27+
},
28+
"ghcr.io/devcontainers/features/github-cli:1": {
29+
"version": "1.1.0",
30+
"resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671",
31+
"integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671"
32+
},
33+
"ghcr.io/devcontainers/features/go:1": {
34+
"version": "1.3.4",
35+
"resolved": "ghcr.io/devcontainers/features/go@sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032",
36+
"integrity": "sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032"
37+
},
38+
"ghcr.io/devcontainers/features/node:1": {
39+
"version": "1.7.1",
40+
"resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6",
41+
"integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6"
42+
},
43+
"ghcr.io/devcontainers/features/python:1": {
44+
"version": "1.8.0",
45+
"resolved": "ghcr.io/devcontainers/features/python@sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511",
46+
"integrity": "sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511"
47+
},
48+
"ghcr.io/jungaretti/features/ripgrep:1": {
49+
"version": "1.0.1",
50+
"resolved": "ghcr.io/jungaretti/features/ripgrep@sha256:c0922b6f4a9184080c8435b6ef25983dee6c734733f0a749d852ad0d33de2253",
51+
"integrity": "sha256:c0922b6f4a9184080c8435b6ef25983dee6c734733f0a749d852ad0d33de2253"
52+
},
53+
"ghcr.io/meaningful-ooo/devcontainer-features/homebrew:2": {
54+
"version": "2.0.5",
55+
"resolved": "ghcr.io/meaningful-ooo/devcontainer-features/homebrew@sha256:c49ba0f6275bdd0474f4c7fcf37fc84b5739838b110c7da9c16d6a409ae48a86",
56+
"integrity": "sha256:c49ba0f6275bdd0474f4c7fcf37fc84b5739838b110c7da9c16d6a409ae48a86"
57+
},
58+
"ghcr.io/michidk/devcontainers-features/bun:1": {
59+
"version": "1.0.1",
60+
"resolved": "ghcr.io/michidk/devcontainers-features/bun@sha256:568cc553062d184932ba993db954d4ace2bda3907d129477ce174777ac686dbd",
61+
"integrity": "sha256:568cc553062d184932ba993db954d4ace2bda3907d129477ce174777ac686dbd"
62+
},
63+
"ghcr.io/va-h/devcontainers-features/uv:1": {
64+
"version": "1.1.4",
65+
"resolved": "ghcr.io/va-h/devcontainers-features/uv@sha256:a15737142539d150ef4d358e2d6a7424a0a2f3dc43b29a3aa1b50162a8b11bc1",
66+
"integrity": "sha256:a15737142539d150ef4d358e2d6a7424a0a2f3dc43b29a3aa1b50162a8b11bc1"
67+
}
68+
}
69+
}

.github/renovate.json

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
{
2+
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
3+
"extends": [
4+
"config:recommended",
5+
":semanticCommits"
6+
],
7+
"rangeStrategy": "pin",
8+
"automerge": false,
9+
"labels": ["supply-chain"],
10+
"prConcurrentLimit": 3,
11+
"prHourlyLimit": 1,
12+
"schedule": ["before 7am on monday"],
13+
"dependencyDashboard": true,
14+
"major": {
15+
"enabled": false
16+
},
17+
"pinDigests": false,
18+
"packageRules": [
19+
{
20+
"description": "High-risk packages get individual PRs with extra labels (no grouping).",
21+
"matchPackageNames": [
22+
"@colbymchenry/codegraph",
23+
"agent-browser",
24+
"rtk-installer",
25+
"golangci-lint-installer",
26+
"claude-code-installer"
27+
],
28+
"addLabels": ["supply-chain:high-risk"],
29+
"groupName": null,
30+
"automerge": false
31+
},
32+
{
33+
"description": "Lower-risk dev-dep bumps may be grouped into a single weekly PR.",
34+
"matchManagers": ["regex"],
35+
"matchFileNames": ["installer/upstreams.yaml"],
36+
"matchUpdateTypes": ["minor", "patch"],
37+
"groupName": "manifest dev-dep updates",
38+
"groupSlug": "manifest-dev-deps"
39+
},
40+
{
41+
"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.",
42+
"matchPackageNames": [
43+
"claude-code-installer",
44+
"uv-installer",
45+
"bun-installer"
46+
],
47+
"addLabels": ["needs-manual-bump"]
48+
},
49+
{
50+
"description": "Group all minor/patch devDependency bumps into a single weekly PR per package.json. Cuts noise on the marketing site / docusaurus / pilot devDeps.",
51+
"matchDepTypes": ["devDependencies"],
52+
"matchUpdateTypes": ["minor", "patch", "pin"],
53+
"groupName": "dev-dependencies (weekly)",
54+
"groupSlug": "dev-deps-weekly"
55+
},
56+
{
57+
"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.",
58+
"matchFileNames": [
59+
"docs/site/**",
60+
"docs/docusaurus/**"
61+
],
62+
"enabled": false
63+
}
64+
],
65+
"customManagers": [
66+
{
67+
"customType": "regex",
68+
"fileMatch": ["^installer/upstreams\\.yaml$"],
69+
"matchStrings": [
70+
"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]+)\"?"
71+
],
72+
"datasourceTemplate": "npm",
73+
"versioningTemplate": "npm"
74+
},
75+
{
76+
"customType": "regex",
77+
"fileMatch": ["^installer/upstreams\\.yaml$"],
78+
"matchStrings": [
79+
"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]+)\"?"
80+
],
81+
"datasourceTemplate": "pypi",
82+
"versioningTemplate": "pep440"
83+
},
84+
{
85+
"customType": "regex",
86+
"fileMatch": ["^installer/upstreams\\.yaml$"],
87+
"matchStrings": [
88+
"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]+)\"?"
89+
],
90+
"datasourceTemplate": "homebrew",
91+
"versioningTemplate": "loose"
92+
},
93+
{
94+
"customType": "regex",
95+
"fileMatch": ["^installer/upstreams\\.yaml$"],
96+
"matchStrings": [
97+
"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]+)\"?"
98+
],
99+
"datasourceTemplate": "github-tags",
100+
"versioningTemplate": "loose"
101+
}
102+
]
103+
}

.github/workflows/release-dev.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,48 @@ concurrency:
1212
cancel-in-progress: true
1313

1414
jobs:
15+
supply-chain-gate:
16+
# Cross-workflow `needs:` cannot link to supply-chain.yml. Poll the Checks
17+
# API for the `supply-chain` check on this SHA — both workflows start on
18+
# the same push, so we wait up to 12 min for it to complete.
19+
name: Supply-Chain Gate
20+
permissions:
21+
contents: read
22+
checks: read
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
26+
with:
27+
script: |
28+
const sleep = ms => new Promise(r => setTimeout(r, ms));
29+
const deadline = Date.now() + 12 * 60 * 1000;
30+
let last = null;
31+
while (Date.now() < deadline) {
32+
const { data } = await github.rest.checks.listForRef({
33+
...context.repo, ref: context.sha
34+
});
35+
const sc = data.check_runs.find(r => r.name === 'supply-chain');
36+
last = sc;
37+
if (sc && sc.status === 'completed') {
38+
if (sc.conclusion === 'success') return;
39+
core.setFailed(
40+
`supply-chain check concluded ${sc.conclusion} for ${context.sha}; prerelease blocked.`
41+
);
42+
return;
43+
}
44+
await sleep(20000);
45+
}
46+
core.setFailed(
47+
`supply-chain check did not complete within 12m for ${context.sha} ` +
48+
`(last status: ${last?.status ?? 'missing'}); prerelease blocked.`
49+
);
50+
1551
security-scan:
1652
name: Security Scan (Trivy)
1753
permissions:
1854
contents: read
1955
runs-on: ubuntu-latest
56+
needs: supply-chain-gate
2057
steps:
2158
- name: Checkout code
2259
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
@@ -65,6 +102,7 @@ jobs:
65102
permissions:
66103
contents: read
67104
runs-on: ubuntu-latest
105+
needs: supply-chain-gate
68106
steps:
69107
- name: Checkout code
70108
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
@@ -100,6 +138,7 @@ jobs:
100138
permissions:
101139
contents: read
102140
runs-on: ubuntu-latest
141+
needs: supply-chain-gate
103142
steps:
104143
- name: Checkout code
105144
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
@@ -132,6 +171,7 @@ jobs:
132171
permissions:
133172
contents: read
134173
runs-on: ubuntu-latest
174+
needs: supply-chain-gate
135175
steps:
136176
- name: Checkout code
137177
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

.github/workflows/release.yml

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,51 @@ jobs:
6060
echo "Not a release trigger commit - skipping"
6161
echo "should_run=false" >> "$GITHUB_OUTPUT"
6262
63+
supply-chain-gate:
64+
# Cross-workflow `needs:` doesn't link supply-chain.yml to this workflow.
65+
# Poll the Checks API for the `supply-chain` check on the release SHA and
66+
# fail when it concludes anything other than `success`. Both workflows
67+
# start on the same push, so we wait up to 12 min for it to land.
68+
name: Supply-Chain Gate
69+
permissions:
70+
contents: read
71+
checks: read
72+
runs-on: ubuntu-latest
73+
needs: check-trigger
74+
if: needs.check-trigger.outputs.should_run == 'true'
75+
steps:
76+
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
77+
with:
78+
script: |
79+
const sleep = ms => new Promise(r => setTimeout(r, ms));
80+
const deadline = Date.now() + 12 * 60 * 1000;
81+
let last = null;
82+
while (Date.now() < deadline) {
83+
const { data } = await github.rest.checks.listForRef({
84+
...context.repo, ref: context.sha
85+
});
86+
const sc = data.check_runs.find(r => r.name === 'supply-chain');
87+
last = sc;
88+
if (sc && sc.status === 'completed') {
89+
if (sc.conclusion === 'success') return;
90+
core.setFailed(
91+
`supply-chain check concluded ${sc.conclusion} for ${context.sha}; release blocked.`
92+
);
93+
return;
94+
}
95+
await sleep(20000);
96+
}
97+
core.setFailed(
98+
`supply-chain check did not complete within 12m for ${context.sha} ` +
99+
`(last status: ${last?.status ?? 'missing'}); release blocked.`
100+
);
101+
63102
security-scan:
64103
name: Security Scan (Trivy)
65104
permissions:
66105
contents: read
67106
runs-on: ubuntu-latest
68-
needs: check-trigger
107+
needs: [check-trigger, supply-chain-gate]
69108
if: needs.check-trigger.outputs.should_run == 'true'
70109
steps:
71110
- name: Checkout code
@@ -114,7 +153,7 @@ jobs:
114153
permissions:
115154
contents: read
116155
runs-on: ubuntu-latest
117-
needs: check-trigger
156+
needs: [check-trigger, supply-chain-gate]
118157
if: needs.check-trigger.outputs.should_run == 'true'
119158
steps:
120159
- name: Checkout code
@@ -149,7 +188,7 @@ jobs:
149188
permissions:
150189
contents: read
151190
runs-on: ubuntu-latest
152-
needs: check-trigger
191+
needs: [check-trigger, supply-chain-gate]
153192
if: needs.check-trigger.outputs.should_run == 'true'
154193
steps:
155194
- name: Checkout code
@@ -181,7 +220,7 @@ jobs:
181220
permissions:
182221
contents: read
183222
runs-on: ubuntu-latest
184-
needs: check-trigger
223+
needs: [check-trigger, supply-chain-gate]
185224
if: needs.check-trigger.outputs.should_run == 'true'
186225
steps:
187226
- name: Checkout code
@@ -227,7 +266,7 @@ jobs:
227266
permissions:
228267
contents: write
229268
runs-on: ubuntu-latest
230-
needs: check-trigger
269+
needs: [check-trigger, supply-chain-gate]
231270
if: needs.check-trigger.outputs.should_run == 'true'
232271
outputs:
233272
should_release: ${{ steps.check.outputs.should_release }}

0 commit comments

Comments
 (0)