Skip to content

Commit 699b2d7

Browse files
authored
ci(security): phase-1 CI hardening — affected-area tests, least privilege, action pinning, dependency governance (#336)
* ci(security): run deterministic eval tests in affected-area CI (Task 04) The build-check job validated build + suite structure but never ran the tests, and its summary referenced a `test:ci` script that did not exist. Make affected-area CI meaningful: - Add `test:ci` to evals/framework running the offline, deterministic Vitest allowlist (7 files / 112 tests) — no model, network, or paid execution. - Add a required "Run deterministic tests" step to build-check; a failure fails the job and the existing PR summary gate (which keys on build-check.result) fails closed. - Correct the summary note to state test:ci runs the deterministic allowlist and that model/network agent suites are excluded on PRs. The broader deterministic suite has 3 pre-existing failing files (framework-confidence, logger, test-runner; confirmed failing before this work) which are intentionally out of the allowlist and tracked separately. Validated locally: build, validate:suites:all, and test:ci all exit 0. * ci(security): least privilege permissions + pin actions to SHAs (Task 05) Harden all 9 GitHub Actions workflows: Least privilege: - Add explicit top-level `permissions: contents: read` to installer-checks, validate-test-suites, and opencode (opencode's job keeps the writes it needs; the top level now defaults to read). - Every workflow now declares an explicit top-level permissions block. - Write scopes retained only where justified (releases, doc/registry commits, follow-up PRs, and sync-docs' issues.create). Immutable action pinning: - Pin every `uses:` to a full 40-char commit SHA with a version comment, including first-party actions/* (checkout, github-script, setup-node, upload-artifact) and third-party (oven-sh/setup-bun). - Replace the two mutable refs that could change under us at any push: ludeeus/action-shellcheck@master and sst/opencode/github@latest. Record all SHAs, versions, and permission justifications in docs/maintenance/repository-recovery/action-pin-inventory.md. Verified: all 9 workflows parse as valid YAML; no floating tag/branch refs remain; every workflow has a top-level permissions block. * docs(security): add SECURITY.md and private-reporting checklist (Task 06) - Add root SECURITY.md: supported versions (0.7.x), private reporting via GitHub's "Report a vulnerability", best-effort response expectations, coordinated disclosure, and no-secret guidance. - Add private-vulnerability-reporting checklist that separates repository file work (this PR) from GitHub settings changes that require their own approval (Task 07: enabling private vulnerability reporting). No external links to validate; the reporting path uses the repo Security tab. SECURITY.md documents the private reporting path ahead of Task 07 enabling it. * ci(security): add dependency governance and ownership controls (Task 08) - .github/dependabot.yml: weekly grouped github-actions updates (keeps the Task 05 SHA pins current). Scoped to actions only — the primary lockfile is bun.lock and Dependabot's npm ecosystem would desync it; npm dependency risk is covered by dependency-review below. - .github/workflows/dependency-review.yml: runs on PRs with contents: read, SHA-pinned actions, fail-on-severity: high to block newly introduced high-severity vulnerabilities (exceptions documented inline). - .github/CODEOWNERS: default owner @darrenhinde with explicit ownership of security-sensitive paths (workflows, dependabot, CODEOWNERS, SECURITY.md, scripts/validation). Verified: dependabot.yml and dependency-review.yml are valid YAML; dependency-review actions are SHA-pinned with contents: read; CODEOWNERS owner is the repository owner. * docs(security): record GitHub security settings evidence (Task 07) Enable private vulnerability reporting and record read-back evidence. Settings changed (each approved immediately before, verified by read-back): - Private vulnerability reporting: enabled=false -> enabled=true. The Security tab now offers "Report a vulnerability", making the path documented in SECURITY.md live. - Dependency Graph / Dependabot alerts: 404 -> 204. This was an undocumented prerequisite of Task 08's dependency-review.yml, which failed with "Dependency graph is not enabled"; the Dependency Review check on PR #336 went fail -> pass after enabling it. No other repository setting was modified; secret scanning and push protection are unchanged, and dependabot_security_updates remains disabled (out of scope, would need its own approval). Both changes are reversible via the corresponding DELETE calls. Marks Part B of the private-vulnerability-reporting checklist complete.
1 parent 926d68d commit 699b2d7

17 files changed

Lines changed: 331 additions & 35 deletions

.github/CODEOWNERS

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Code owners for OpenAgentsControl
2+
#
3+
# Owners are automatically requested for review on PRs that touch matching
4+
# paths. Security-sensitive paths are listed explicitly so they always require
5+
# a maintainer review.
6+
7+
# Default owner for everything in the repo.
8+
* @darrenhinde
9+
10+
# CI/CD workflows — untrusted-input execution surface.
11+
/.github/workflows/ @darrenhinde
12+
/.github/dependabot.yml @darrenhinde
13+
/.github/CODEOWNERS @darrenhinde
14+
15+
# Security policy and reporting.
16+
/SECURITY.md @darrenhinde
17+
18+
# PR validation and detection scripts consumed by CI.
19+
/scripts/validation/ @darrenhinde

.github/dependabot.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
version: 2
2+
3+
updates:
4+
# GitHub Actions — keeps workflow action versions (and their SHA pins) current.
5+
# Scoped to actions only: the repo's primary lockfile is bun.lock, and
6+
# Dependabot's npm ecosystem would update package-lock.json without syncing
7+
# bun.lock, causing lockfile drift. Dependency (npm) vulnerabilities are
8+
# instead caught at PR time by dependency-review.yml.
9+
- package-ecosystem: github-actions
10+
directory: /
11+
schedule:
12+
interval: weekly
13+
open-pull-requests-limit: 5
14+
groups:
15+
github-actions:
16+
patterns:
17+
- "*"
18+
commit-message:
19+
prefix: "ci"

.github/workflows/create-release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ jobs:
2727

2828
steps:
2929
- name: Checkout code
30-
uses: actions/checkout@v4
30+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
3131
with:
3232
fetch-depth: 2
3333

3434
- name: Check if this was a version bump PR merge
3535
id: check
36-
uses: actions/github-script@v7
36+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
3737
with:
3838
script: |
3939
const fs = require('fs');
@@ -83,7 +83,7 @@ jobs:
8383

8484
steps:
8585
- name: Checkout code
86-
uses: actions/checkout@v4
86+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
8787
with:
8888
fetch-depth: 0
8989

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Dependency Review
2+
3+
on:
4+
pull_request:
5+
branches: [main, dev]
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
dependency-review:
12+
name: Dependency Review
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
17+
with:
18+
persist-credentials: false
19+
20+
- name: Review dependencies
21+
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4
22+
with:
23+
# Block PRs that introduce newly known high-severity (or worse)
24+
# vulnerabilities. Document any exception inline in the PR that adds
25+
# an allow-ghsas / allow-dependencies-licenses entry here.
26+
fail-on-severity: high

.github/workflows/installer-checks.yml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@ on:
1515
- 'update.sh'
1616
workflow_dispatch:
1717

18+
permissions:
19+
contents: read
20+
1821
jobs:
1922
shellcheck:
2023
name: ShellCheck Analysis
2124
runs-on: ubuntu-latest
2225

2326
steps:
2427
- name: Checkout code
25-
uses: actions/checkout@v4
28+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
2629

2730
- name: Run ShellCheck on install.sh
28-
uses: ludeeus/action-shellcheck@master
31+
uses: ludeeus/action-shellcheck@00b27aa7cb85167568cb48a3838b75f4265f2bca # master (resolved 2026-07-15)
2932
with:
3033
scandir: '.'
3134
additional_files: 'install.sh update.sh'
@@ -43,7 +46,7 @@ jobs:
4346

4447
steps:
4548
- name: Checkout code
46-
uses: actions/checkout@v4
49+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
4750

4851
- name: Check install.sh syntax
4952
run: bash -n install.sh
@@ -73,7 +76,7 @@ jobs:
7376

7477
steps:
7578
- name: Checkout code
76-
uses: actions/checkout@v4
79+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
7780

7881
- name: Install jq (Ubuntu)
7982
if: matrix.os == 'ubuntu-latest'
@@ -102,7 +105,7 @@ jobs:
102105

103106
steps:
104107
- name: Checkout code
105-
uses: actions/checkout@v4
108+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
106109

107110
- name: Install jq (Ubuntu)
108111
if: matrix.os == 'ubuntu-latest'
@@ -133,7 +136,7 @@ jobs:
133136

134137
steps:
135138
- name: Checkout code
136-
uses: actions/checkout@v4
139+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
137140

138141
- name: Install jq (Ubuntu)
139142
if: matrix.os == 'ubuntu-latest'
@@ -162,7 +165,7 @@ jobs:
162165

163166
steps:
164167
- name: Checkout code
165-
uses: actions/checkout@v4
168+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
166169

167170
- name: Install dependencies
168171
run: sudo apt-get install -y jq curl

.github/workflows/opencode.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ on:
44
issue_comment:
55
types: [created]
66

7+
permissions:
8+
contents: read
9+
710
jobs:
811
opencode:
912
if: |
@@ -19,12 +22,12 @@ jobs:
1922
issues: write
2023
steps:
2124
- name: Checkout repository
22-
uses: actions/checkout@v4
25+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
2326
with:
2427
fetch-depth: 1
2528

2629
- name: Run opencode
27-
uses: sst/opencode/github@latest
30+
uses: sst/opencode/github@77fc88c8ade8e5a620ebbe1197f3a572d29ae91a # latest (resolved 2026-07-15)
2831
env:
2932
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
3033
with:

.github/workflows/post-merge-pr.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ jobs:
2525

2626
steps:
2727
- name: Checkout code
28-
uses: actions/checkout@v4
28+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
2929
with:
3030
fetch-depth: 2
3131

3232
- name: Check if this was a version bump PR
3333
id: check_pr_labels
34-
uses: actions/github-script@v7
34+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
3535
with:
3636
script: |
3737
// Get the commit that triggered this workflow
@@ -106,12 +106,12 @@ jobs:
106106
107107
steps:
108108
- name: Checkout code
109-
uses: actions/checkout@v4
109+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
110110
with:
111111
fetch-depth: 0
112112

113113
- name: Setup Node.js
114-
uses: actions/setup-node@v4
114+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
115115
with:
116116
node-version: '20'
117117

.github/workflows/pr-checks.yml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
steps:
1919
- name: Check PR title format
2020
id: validate
21-
uses: actions/github-script@v7
21+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
2222
with:
2323
script: |
2424
const prTitle = context.payload.pull_request.title;
@@ -163,7 +163,7 @@ jobs:
163163

164164
steps:
165165
- name: Checkout code
166-
uses: actions/checkout@v4
166+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
167167
with:
168168
fetch-depth: 0
169169
persist-credentials: false
@@ -192,12 +192,12 @@ jobs:
192192
193193
steps:
194194
- name: Checkout code
195-
uses: actions/checkout@v4
195+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
196196
with:
197197
persist-credentials: false
198198

199199
- name: Setup Node.js
200-
uses: actions/setup-node@v4
200+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
201201
with:
202202
node-version: '20'
203203
cache: 'npm'
@@ -214,17 +214,22 @@ jobs:
214214
- name: Validate test suites
215215
working-directory: evals/framework
216216
run: npm run validate:suites:all
217-
217+
218+
- name: Run deterministic tests
219+
working-directory: evals/framework
220+
run: npm run test:ci
221+
218222
- name: Summary
219223
if: success()
220224
run: |
221225
echo "## ✅ Build Check Passed" >> $GITHUB_STEP_SUMMARY
222226
echo "" >> $GITHUB_STEP_SUMMARY
223227
echo "- ✅ TypeScript compilation successful" >> $GITHUB_STEP_SUMMARY
224228
echo "- ✅ Test suite validation passed" >> $GITHUB_STEP_SUMMARY
229+
echo "- ✅ Deterministic tests passed (\`npm run test:ci\`)" >> $GITHUB_STEP_SUMMARY
225230
echo "" >> $GITHUB_STEP_SUMMARY
226-
echo "**Note:** Full agent tests are not run on PRs to save time and costs." >> $GITHUB_STEP_SUMMARY
227-
echo "Maintainers can run \`npm run test:ci\` locally if needed." >> $GITHUB_STEP_SUMMARY
231+
echo "**Note:** \`test:ci\` runs the offline, deterministic Vitest allowlist only." >> $GITHUB_STEP_SUMMARY
232+
echo "Model- or network-dependent agent tests (\`eval:sdk\`, integration suites) are not run on PRs." >> $GITHUB_STEP_SUMMARY
228233
229234
- name: Failure summary
230235
if: failure()

.github/workflows/sync-docs.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232

3333
steps:
3434
- name: Checkout repository
35-
uses: actions/checkout@v4
35+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
3636
with:
3737
fetch-depth: 2
3838

@@ -87,7 +87,7 @@ jobs:
8787

8888
steps:
8989
- name: Checkout repository
90-
uses: actions/checkout@v4
90+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
9191
with:
9292
fetch-depth: 0
9393

@@ -105,7 +105,7 @@ jobs:
105105
106106
- name: Create sync issue for OpenCode
107107
id: create_issue
108-
uses: actions/github-script@v7
108+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
109109
with:
110110
script: |
111111
const issue = await github.rest.issues.create({
@@ -195,7 +195,7 @@ jobs:
195195

196196
steps:
197197
- name: Checkout repository
198-
uses: actions/checkout@v4
198+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
199199

200200
- name: Delete branch if created
201201
run: |
@@ -207,7 +207,7 @@ jobs:
207207
208208
- name: Comment on issue
209209
if: needs.sync-documentation.outputs.issue_number
210-
uses: actions/github-script@v7
210+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
211211
with:
212212
script: |
213213
await github.rest.issues.createComment({

.github/workflows/update-registry.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818

1919
steps:
2020
- name: Checkout repository
21-
uses: actions/checkout@v4
21+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
2222
with:
2323
fetch-depth: 0
2424

0 commit comments

Comments
 (0)