Browse Source

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.
Darren Hinde 2 weeks ago
parent
commit
699b2d7769

+ 19 - 0
.github/CODEOWNERS

@@ -0,0 +1,19 @@
+# Code owners for OpenAgentsControl
+#
+# Owners are automatically requested for review on PRs that touch matching
+# paths. Security-sensitive paths are listed explicitly so they always require
+# a maintainer review.
+
+# Default owner for everything in the repo.
+*                                   @darrenhinde
+
+# CI/CD workflows — untrusted-input execution surface.
+/.github/workflows/                 @darrenhinde
+/.github/dependabot.yml             @darrenhinde
+/.github/CODEOWNERS                 @darrenhinde
+
+# Security policy and reporting.
+/SECURITY.md                        @darrenhinde
+
+# PR validation and detection scripts consumed by CI.
+/scripts/validation/                @darrenhinde

+ 19 - 0
.github/dependabot.yml

@@ -0,0 +1,19 @@
+version: 2
+
+updates:
+  # GitHub Actions — keeps workflow action versions (and their SHA pins) current.
+  # Scoped to actions only: the repo's primary lockfile is bun.lock, and
+  # Dependabot's npm ecosystem would update package-lock.json without syncing
+  # bun.lock, causing lockfile drift. Dependency (npm) vulnerabilities are
+  # instead caught at PR time by dependency-review.yml.
+  - package-ecosystem: github-actions
+    directory: /
+    schedule:
+      interval: weekly
+    open-pull-requests-limit: 5
+    groups:
+      github-actions:
+        patterns:
+          - "*"
+    commit-message:
+      prefix: "ci"

+ 3 - 3
.github/workflows/create-release.yml

@@ -27,13 +27,13 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 2
       
       - name: Check if this was a version bump PR merge
         id: check
-        uses: actions/github-script@v7
+        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
         with:
           script: |
             const fs = require('fs');
@@ -83,7 +83,7 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 0
       

+ 26 - 0
.github/workflows/dependency-review.yml

@@ -0,0 +1,26 @@
+name: Dependency Review
+
+on:
+  pull_request:
+    branches: [main, dev]
+
+permissions:
+  contents: read
+
+jobs:
+  dependency-review:
+    name: Dependency Review
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+        with:
+          persist-credentials: false
+
+      - name: Review dependencies
+        uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4
+        with:
+          # Block PRs that introduce newly known high-severity (or worse)
+          # vulnerabilities. Document any exception inline in the PR that adds
+          # an allow-ghsas / allow-dependencies-licenses entry here.
+          fail-on-severity: high

+ 10 - 7
.github/workflows/installer-checks.yml

@@ -15,6 +15,9 @@ on:
       - 'update.sh'
   workflow_dispatch:
 
+permissions:
+  contents: read
+
 jobs:
   shellcheck:
     name: ShellCheck Analysis
@@ -22,10 +25,10 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
       
       - name: Run ShellCheck on install.sh
-        uses: ludeeus/action-shellcheck@master
+        uses: ludeeus/action-shellcheck@00b27aa7cb85167568cb48a3838b75f4265f2bca # master (resolved 2026-07-15)
         with:
           scandir: '.'
           additional_files: 'install.sh update.sh'
@@ -43,7 +46,7 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
       
       - name: Check install.sh syntax
         run: bash -n install.sh
@@ -73,7 +76,7 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
       
       - name: Install jq (Ubuntu)
         if: matrix.os == 'ubuntu-latest'
@@ -102,7 +105,7 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
       
       - name: Install jq (Ubuntu)
         if: matrix.os == 'ubuntu-latest'
@@ -133,7 +136,7 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
       
       - name: Install jq (Ubuntu)
         if: matrix.os == 'ubuntu-latest'
@@ -162,7 +165,7 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
       
       - name: Install dependencies
         run: sudo apt-get install -y jq curl

+ 5 - 2
.github/workflows/opencode.yml

@@ -4,6 +4,9 @@ on:
   issue_comment:
     types: [created]
 
+permissions:
+  contents: read
+
 jobs:
   opencode:
     if: |
@@ -19,12 +22,12 @@ jobs:
       issues: write
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 1
 
       - name: Run opencode
-        uses: sst/opencode/github@latest
+        uses: sst/opencode/github@77fc88c8ade8e5a620ebbe1197f3a572d29ae91a # latest (resolved 2026-07-15)
         env:
           ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
         with:

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

@@ -25,13 +25,13 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 2
       
       - name: Check if this was a version bump PR
         id: check_pr_labels
-        uses: actions/github-script@v7
+        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
         with:
           script: |
             // Get the commit that triggered this workflow
@@ -106,12 +106,12 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 0
       
       - name: Setup Node.js
-        uses: actions/setup-node@v4
+        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
         with:
           node-version: '20'
       

+ 12 - 7
.github/workflows/pr-checks.yml

@@ -18,7 +18,7 @@ jobs:
     steps:
       - name: Check PR title format
         id: validate
-        uses: actions/github-script@v7
+        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
         with:
           script: |
             const prTitle = context.payload.pull_request.title;
@@ -163,7 +163,7 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 0
           persist-credentials: false
@@ -192,12 +192,12 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           persist-credentials: false
       
       - name: Setup Node.js
-        uses: actions/setup-node@v4
+        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
         with:
           node-version: '20'
           cache: 'npm'
@@ -214,7 +214,11 @@ jobs:
       - name: Validate test suites
         working-directory: evals/framework
         run: npm run validate:suites:all
-      
+
+      - name: Run deterministic tests
+        working-directory: evals/framework
+        run: npm run test:ci
+
       - name: Summary
         if: success()
         run: |
@@ -222,9 +226,10 @@ jobs:
           echo "" >> $GITHUB_STEP_SUMMARY
           echo "- ✅ TypeScript compilation successful" >> $GITHUB_STEP_SUMMARY
           echo "- ✅ Test suite validation passed" >> $GITHUB_STEP_SUMMARY
+          echo "- ✅ Deterministic tests passed (\`npm run test:ci\`)" >> $GITHUB_STEP_SUMMARY
           echo "" >> $GITHUB_STEP_SUMMARY
-          echo "**Note:** Full agent tests are not run on PRs to save time and costs." >> $GITHUB_STEP_SUMMARY
-          echo "Maintainers can run \`npm run test:ci\` locally if needed." >> $GITHUB_STEP_SUMMARY
+          echo "**Note:** \`test:ci\` runs the offline, deterministic Vitest allowlist only." >> $GITHUB_STEP_SUMMARY
+          echo "Model- or network-dependent agent tests (\`eval:sdk\`, integration suites) are not run on PRs." >> $GITHUB_STEP_SUMMARY
       
       - name: Failure summary
         if: failure()

+ 5 - 5
.github/workflows/sync-docs.yml

@@ -32,7 +32,7 @@ jobs:
     
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 2
       
@@ -87,7 +87,7 @@ jobs:
     
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 0
       
@@ -105,7 +105,7 @@ jobs:
       
       - name: Create sync issue for OpenCode
         id: create_issue
-        uses: actions/github-script@v7
+        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
         with:
           script: |
             const issue = await github.rest.issues.create({
@@ -195,7 +195,7 @@ jobs:
     
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
       
       - name: Delete branch if created
         run: |
@@ -207,7 +207,7 @@ jobs:
       
       - name: Comment on issue
         if: needs.sync-documentation.outputs.issue_number
-        uses: actions/github-script@v7
+        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
         with:
           script: |
             await github.rest.issues.createComment({

+ 1 - 1
.github/workflows/update-registry.yml

@@ -18,7 +18,7 @@ jobs:
     
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 0
       

+ 2 - 2
.github/workflows/validate-registry.yml

@@ -21,7 +21,7 @@ jobs:
 
     steps:
       - name: Checkout code without persisted credentials
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 0
           persist-credentials: false
@@ -32,7 +32,7 @@ jobs:
           sudo apt-get install -y jq
 
       - name: Install Bun
-        uses: oven-sh/setup-bun@v2
+        uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
         with:
           bun-version: latest
 

+ 7 - 4
.github/workflows/validate-test-suites.yml

@@ -15,6 +15,9 @@ on:
       - '.github/workflows/validate-test-suites.yml'
   workflow_dispatch:
 
+permissions:
+  contents: read
+
 jobs:
   validate:
     name: Validate Test Suite Definitions
@@ -22,10 +25,10 @@ jobs:
     
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
       
       - name: Setup Node.js
-        uses: actions/setup-node@v4
+        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
         with:
           node-version: '20'
           cache: 'npm'
@@ -42,7 +45,7 @@ jobs:
       
       - name: Comment on PR (if validation failed)
         if: failure() && github.event_name == 'pull_request'
-        uses: actions/github-script@v7
+        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
         with:
           script: |
             github.rest.issues.createComment({
@@ -54,7 +57,7 @@ jobs:
       
       - name: Upload validation report
         if: always()
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
         with:
           name: suite-validation-report
           path: |

+ 58 - 0
SECURITY.md

@@ -0,0 +1,58 @@
+# Security Policy
+
+## Supported Versions
+
+Security fixes are provided for the latest released minor version. Older
+versions are not maintained; please upgrade before reporting.
+
+| Version | Supported |
+|---------|-----------|
+| 0.7.x   | ✅        |
+| < 0.7   | ❌        |
+
+## Reporting a Vulnerability
+
+**Please do not report security vulnerabilities through public GitHub issues,
+pull requests, or discussions.**
+
+Report vulnerabilities privately through GitHub's built-in private vulnerability
+reporting:
+
+1. Go to the repository's **Security** tab.
+2. Select **Report a vulnerability**.
+3. Provide a clear description, affected versions, reproduction steps, and
+   potential impact.
+
+This opens a private advisory visible only to the maintainers and to you.
+
+### What to include
+
+- The affected component and version.
+- Steps to reproduce, or a proof of concept.
+- The impact you believe the issue has.
+
+### What not to include
+
+- **Do not include real secrets, credentials, API keys, or access tokens** in
+  your report. If a secret has been exposed, say so and rotate it — do not paste
+  its value.
+
+## Response Expectations
+
+This project is maintained on a **best-effort basis**. Reports are reviewed and
+addressed as maintainer availability allows; there is no guaranteed response
+time. We will engage with the private advisory as we triage and work on a fix.
+
+## Coordinated Disclosure
+
+We follow coordinated disclosure. Please give the maintainers a reasonable
+opportunity to investigate and release a fix before disclosing the issue
+publicly. We will coordinate the timing and content of any public disclosure
+with you through the private advisory.
+
+## Scope
+
+This policy covers the code in this repository. Vulnerabilities in third-party
+dependencies should be reported to the respective upstream projects; if a
+dependency issue affects this project specifically, you may still report it here
+so we can track remediation.

+ 53 - 0
docs/maintenance/repository-recovery/action-pin-inventory.md

@@ -0,0 +1,53 @@
+# GitHub Actions Pin & Permissions Inventory
+
+_Repository-recovery Task 05 — least privilege + immutable action pinning._
+_Resolved: 2026-07-15._
+
+## Third-party / external action pins
+
+Every `uses:` reference is pinned to a full 40-character commit SHA, with the
+human-readable version retained as a trailing comment. SHAs were resolved via
+`gh api repos/<owner>/<repo>/commits/<ref>` on the date above.
+
+| Action | Previous ref | Pinned SHA | Note |
+|--------|--------------|------------|------|
+| `actions/checkout` | `@v4` (19 uses) | `34e114876b0b11c390a56381ad16ebd13914f8d5` | GitHub-owned |
+| `actions/github-script` | `@v7` (6 uses) | `f28e40c7f34bde8b3046d885e986cb6290c5673b` | GitHub-owned |
+| `actions/setup-node` | `@v4` (3 uses) | `49933ea5288caeca8642d1e84afbd3f7d6820020` | GitHub-owned |
+| `actions/upload-artifact` | `@v4` (1 use) | `ea165f8d65b6e75b540449e92b4886f43607fa02` | GitHub-owned |
+| `oven-sh/setup-bun` | `@v2` (1 use) | `0c5077e51419868618aeaa5fe8019c62421857d6` | Matches the SHA already pinned by the other usage |
+| `ludeeus/action-shellcheck` | `@master` (1 use) | `00b27aa7cb85167568cb48a3838b75f4265f2bca` | **Was a mutable branch ref** — highest risk before pinning |
+| `sst/opencode/github` | `@latest` (1 use) | `77fc88c8ade8e5a620ebbe1197f3a572d29ae91a` | **Was a mutable ref** — freezes the OpenCode agent version; update deliberately |
+
+### Update guidance
+
+To bump a pinned action later: re-resolve the desired tag to its SHA
+(`gh api repos/<owner>/<repo>/commits/<tag> --jq .sha`), replace the SHA, and
+update the trailing version comment in the same edit. Never revert a pin back to
+a floating tag or branch.
+
+## Workflow permissions (least privilege)
+
+Every workflow now declares an explicit top-level `permissions:` block. Default
+is `contents: read`; write scopes remain only where a job demonstrably needs them.
+
+| Workflow | Top-level | Job-level writes | Justification |
+|----------|-----------|------------------|---------------|
+| `pr-checks.yml` | `contents: read` | — | read-only validation |
+| `validate-registry.yml` | `contents: read` | — | read-only validation |
+| `installer-checks.yml` | `contents: read` **(added)** | — | shellcheck / installer tests, read-only |
+| `validate-test-suites.yml` | `contents: read` **(added)** | — | suite validation, read-only |
+| `opencode.yml` | `contents: read` **(added)** | `id-token`, `contents`, `pull-requests`, `issues: write` (job) | `/oc` agent, gated to OWNER/MEMBER; job needs write to act on PRs/issues |
+| `create-release.yml` | `contents: write` | — | creates tags/releases |
+| `post-merge-pr.yml` | `contents: write`, `pull-requests: write` | — | opens follow-up PRs |
+| `sync-docs.yml` | `contents: write`, `pull-requests: write`, `issues: write` | — | commits doc branch, opens PR, creates a sync issue (`issues.create`) |
+| `update-registry.yml` | `contents: write` | — | commits registry updates |
+
+`issues: write` in `sync-docs.yml` is retained because the workflow calls
+`github.rest.issues.create` to open a sync-tracking issue.
+
+## Verification
+
+- All 9 workflow files parse as valid YAML.
+- No `uses:` reference remains on a floating tag or branch — all are 40-char SHAs.
+- Every workflow has an explicit top-level `permissions:` block.

+ 43 - 0
docs/maintenance/repository-recovery/private-vulnerability-reporting-checklist.md

@@ -0,0 +1,43 @@
+# Private Vulnerability Reporting — Setup Checklist
+
+_Repository-recovery Tasks 06–07. This checklist separates repository-file work
+(committable in a PR) from GitHub repository-settings work (changes live GitHub
+state and requires its own explicit approval)._
+
+## Part A — Repository files (Task 06, this PR)
+
+- [x] Publish `SECURITY.md` at the repository root with:
+  - [x] Supported versions.
+  - [x] Private reporting path (GitHub "Report a vulnerability").
+  - [x] Response expectations (best-effort).
+  - [x] Coordinated disclosure policy.
+  - [x] No-secret guidance.
+- [x] Publish this checklist.
+
+These are file changes only. They do **not** enable the private reporting
+feature — they document how to use it once enabled.
+
+## Part B — GitHub repository settings (Task 07, separate approval)
+
+> ⚠️ The following change live GitHub state, not files. Each requires explicit
+> approval immediately before it is made, and is verified by read-back.
+
+- [x] Enable **Private vulnerability reporting** _(done 2026-07-15)_
+  - Settings → Code security and analysis → Private vulnerability reporting → Enable.
+  - API: `PUT /repos/{owner}/{repo}/private-vulnerability-reporting`.
+  - Read-back: `GET /repos/{owner}/{repo}/private-vulnerability-reporting` shows `enabled: true`. ✅
+- [x] Confirm the **Security** tab now offers "Report a vulnerability" so the
+      path documented in `SECURITY.md` is live. ✅
+- [x] Record evidence in
+      `docs/maintenance/repository-recovery/private-vulnerability-reporting-evidence.md`. ✅
+- [x] **Dependency Graph** enabled (prerequisite discovered in Task 08 —
+      `dependency-review.yml` requires it). `PUT /repos/{owner}/{repo}/vulnerability-alerts`,
+      read-back `HTTP 204`. ✅
+
+## Notes
+
+- `SECURITY.md` references the private reporting path before Part B is applied.
+  There is a brief window where the documented path is not yet live; Part B
+  should follow promptly.
+- No other repository setting is changed as part of Part B — only private
+  vulnerability reporting.

+ 63 - 0
docs/maintenance/repository-recovery/private-vulnerability-reporting-evidence.md

@@ -0,0 +1,63 @@
+# GitHub Security Settings — Change Evidence
+
+_Repository-recovery Task 07 (private vulnerability reporting), plus the
+Dependency Graph prerequisite discovered during Task 08._
+_Repository: `darrenhinde/OpenAgentsControl` (public). Date: 2026-07-15._
+
+Each change below was explicitly approved immediately before it was made, and
+verified by read-back. No other repository setting was modified.
+
+## Change 1 — Dependency Graph / Dependabot alerts (Task 08 prerequisite)
+
+Discovered while adding `dependency-review.yml`: the Dependency Review action
+fails with _"Dependency review is not supported on this repository. Please ensure
+that Dependency graph is enabled"_ unless the Dependency Graph is on.
+
+| Step | Command | Result |
+|------|---------|--------|
+| Before | `GET /repos/darrenhinde/OpenAgentsControl/vulnerability-alerts` | `HTTP 404` (disabled) |
+| Enable | `PUT /repos/darrenhinde/OpenAgentsControl/vulnerability-alerts` | `HTTP 204` |
+| Read-back | `GET /repos/darrenhinde/OpenAgentsControl/vulnerability-alerts` | `HTTP 204` (enabled) |
+
+**Verification:** the `Dependency Review` check on PR #336 went from `fail` (4s,
+"Dependency graph is not enabled") to `pass` (9s) after this change.
+
+## Change 2 — Private vulnerability reporting (Task 07)
+
+Makes the reporting path documented in `SECURITY.md` live.
+
+| Step | Command | Result |
+|------|---------|--------|
+| Before | `GET /repos/darrenhinde/OpenAgentsControl/private-vulnerability-reporting` | `{"enabled": false}` |
+| Enable | `PUT /repos/darrenhinde/OpenAgentsControl/private-vulnerability-reporting` | `HTTP 204` |
+| Read-back | `GET /repos/darrenhinde/OpenAgentsControl/private-vulnerability-reporting` | `{"enabled": true}` |
+
+**Verification:** the repository Security tab now offers **Report a
+vulnerability**, so the path documented in `SECURITY.md` resolves.
+
+## Settings not changed
+
+Confirmed unchanged before and after (`GET /repos/{owner}/{repo}` →
+`security_and_analysis`):
+
+```json
+{
+  "dependabot_security_updates": { "status": "disabled" },
+  "secret_scanning": { "status": "enabled" },
+  "secret_scanning_non_provider_patterns": { "status": "disabled" },
+  "secret_scanning_push_protection": { "status": "enabled" },
+  "secret_scanning_validity_checks": { "status": "disabled" }
+}
+```
+
+Note: `dependabot_security_updates` (automated fix PRs) remains **disabled** —
+it was not in scope for Task 07 and would require its own approval. Dependabot
+alerts are now on via the Dependency Graph change above; `.github/dependabot.yml`
+is scoped to `github-actions` updates only.
+
+## Reversal
+
+Both changes are reversible with the corresponding `DELETE` calls:
+
+- `DELETE /repos/{owner}/{repo}/private-vulnerability-reporting`
+- `DELETE /repos/{owner}/{repo}/vulnerability-alerts`

+ 1 - 0
evals/framework/package.json

@@ -10,6 +10,7 @@
     "build:watch": "tsc --watch",
     "test": "vitest",
     "test:watch": "vitest --watch",
+    "test:ci": "vitest run src/__tests__/error-handling.test.ts src/evaluators/__tests__/evaluator-reliability.test.ts src/evaluators/__tests__/context-loading-evaluator.test.ts src/evaluators/__tests__/tool-usage-enhanced.test.ts src/sdk/__tests__/yaml-loader.test.ts src/sdk/__tests__/suite-validator.test.ts src/sdk/__tests__/validate-suites-cli.test.ts",
     "test:integration": "vitest --config vitest.integration.config.ts",
     "lint": "eslint src/**/*.ts",
     "lint:fix": "eslint src/**/*.ts --fix",