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
4 changes: 4 additions & 0 deletions .github/branch-protection.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@ Recommended protection for `main`:
`needs-human` / `blocked` gates. Applying these settings remains a separate,
explicitly approved maintainer action.

CodeQL and Dependency Review begin as reporting checks. A maintainer decides
whether to require them only after their initial pull-request and `main` runs
have succeeded.

For autonomous PRs, keep maintainer spot checks enabled until at least 10
Rafiki PRs have passed review, CI, and audit-log recording without scope drift.
38 changes: 38 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
version: 2

updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
groups:
root-npm:
patterns:
- "*"

- package-ecosystem: npm
directory: /frontend
schedule:
interval: weekly
groups:
frontend-npm:
patterns:
- "*"

- package-ecosystem: pip
directory: /
schedule:
interval: weekly
groups:
python:
patterns:
- "*"

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
github-actions:
patterns:
- "*"
2 changes: 1 addition & 1 deletion .github/workflows/agentic-dev-loop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
LOOP_PAUSED: ${{ vars.LOOP_PAUSED || 'false' }}
QUALITY_LABELS: ${{ inputs.quality_labels || '' }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/agentic-issue-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
}}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- name: Ensure standard labels
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/agentic-pr-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- name: Ensure standard labels
run: python3 scripts/agentic/ensure_labels.py
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/agentic-traceability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- name: Fetch PR policy metadata
run: |
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ jobs:

steps:
- name: Check out repository
uses: actions/checkout@v6
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- name: Set up Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: "22.12"

- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.11"

Expand Down Expand Up @@ -52,11 +52,11 @@ jobs:

steps:
- name: Check out repository
uses: actions/checkout@v6
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0

- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40 changes: 40 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CodeQL

on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: "23 9 * * 1"

permissions:
contents: read

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language:
- javascript-typescript
- python
steps:
- name: Check out repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- name: Initialize CodeQL
uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with:
languages: ${{ matrix.language }}
build-mode: none

- name: Analyze
uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
21 changes: 21 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Dependency Review

on:
pull_request:
branches:
- main

permissions:
contents: read

jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- name: Review dependency changes
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
fail-on-severity: high
98 changes: 98 additions & 0 deletions tests/agentic/test_workflows.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import re
import subprocess
from pathlib import Path

Expand Down Expand Up @@ -194,6 +195,103 @@ def test_ci_names_security_verification_and_runs_it_before_deterministic_checks(
)


def test_active_workflow_actions_use_immutable_release_pins():
pin = re.compile(
r"^\s*(?:-\s*)?uses:\s+[^@\s]+@(?P<sha>[0-9a-f]{40})\s+#\s+"
r"(?P<tag>v\d+\.\d+\.\d+)\s*$"
)
uses_lines = []

for path in sorted((ROOT / ".github" / "workflows").glob("*.yml")):
for line_number, line in enumerate(path.read_text().splitlines(), start=1):
if re.match(r"^\s*(?:-\s*)?uses:", line):
uses_lines.append((path, line_number, line))

assert uses_lines
for path, line_number, line in uses_lines:
assert pin.match(line), f"{path.name}:{line_number}: {line}"


def test_dependabot_groups_every_supported_dependency_surface_weekly():
path = ROOT / ".github" / "dependabot.yml"
config = yaml.safe_load(path.read_text())
updates = {
(entry["package-ecosystem"], entry["directory"]): entry
for entry in config["updates"]
}

assert config["version"] == 2
assert set(updates) == {
("npm", "/"),
("npm", "/frontend"),
("pip", "/"),
("github-actions", "/"),
}
for entry in updates.values():
assert entry["schedule"] == {"interval": "weekly"}
assert len(entry["groups"]) == 1
assert next(iter(entry["groups"].values()))["patterns"] == ["*"]

text = path.read_text().lower()
assert "auto-merge" not in text
assert "automerge" not in text


def test_codeql_scans_supported_languages_on_pr_main_and_weekly_schedule():
workflow = _workflow("codeql.yml")
analyze = workflow["jobs"]["analyze"]
steps = analyze["steps"]
init = next(step for step in steps if step.get("name") == "Initialize CodeQL")

assert workflow["on"]["push"]["branches"] == ["main"]
assert workflow["on"]["pull_request"]["branches"] == ["main"]
assert workflow["on"]["schedule"]
assert workflow["permissions"] == {"contents": "read"}
assert analyze["permissions"] == {
"contents": "read",
"security-events": "write",
}
assert analyze["strategy"] == {
"fail-fast": False,
"matrix": {"language": ["javascript-typescript", "python"]},
}
assert init["with"] == {
"languages": "${{ matrix.language }}",
"build-mode": "none",
}
assert any(
step.get("name") == "Analyze"
and step["uses"].startswith("github/codeql-action/analyze@")
for step in steps
)


def test_dependency_review_runs_on_main_pull_requests_with_read_only_access():
workflow = _workflow("dependency-review.yml")
job = workflow["jobs"]["dependency-review"]
review = next(
step for step in job["steps"] if step.get("name") == "Review dependency changes"
)

assert workflow["on"] == {"pull_request": {"branches": ["main"]}}
assert workflow["permissions"] == {"contents": "read"}
assert "permissions" not in job
assert review["with"] == {"fail-on-severity": "high"}


def test_security_monitoring_yaml_parses_without_provider_credentials():
paths = [ROOT / ".github" / "dependabot.yml"]
paths.extend(sorted((ROOT / ".github" / "workflows").glob("*.yml")))

for path in paths:
assert isinstance(yaml.safe_load(path.read_text()), dict), path.name

for name in ("codeql.yml", "dependency-review.yml"):
text = (ROOT / ".github" / "workflows" / name).read_text()
assert "secrets." not in text
assert "API_KEY" not in text


def test_verify_script_covers_every_deterministic_ci_gate():
package = json.loads((ROOT / "package.json").read_text())
package_lock = json.loads((ROOT / "package-lock.json").read_text())
Expand Down
Loading