-
Notifications
You must be signed in to change notification settings - Fork 396
ci: add update-pins tool and review workflows #338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
f9493b8
ci: add update-pins tool and workflow
xenoscopic ec6c6c9
ci: add AI-driven security-review workflows
xenoscopic 2bf017d
ci: relocate update-pins under cmd/ci and add Copyright statements
xenoscopic 6c1b2b2
ci: clean up types for CI helper commands
xenoscopic be61be6
ci: switch to manual triggers for differential reviews and pin updates
xenoscopic d0ea2d8
ci: add new security-reviewer agent
xenoscopic 47f9dfe
ci: reorder security-reviewer report template
xenoscopic 6cc2f11
ci: fixup security-reviewer label handling
xenoscopic f651d2e
ci: remove stale environment variable constants
xenoscopic f2fd09d
ci: rename and simplify security-review workflows
xenoscopic 3548425
agents/sec-rev: adjust Go build directories
xenoscopic bcf2e49
agents/sec-rev: refactor prompt rendering and simplify config surface
xenoscopic 7537df3
agents/sec-rev: make mode concept implicit
xenoscopic b78a31a
agents/sec-rev: replace LiteLLM with a custom proxy
xenoscopic 7d7b4b0
agents/sec-rev: hide healthcheck logging in proxy
xenoscopic b402ff6
agents/sec-rev: minor tweaks to Claude/Codex invocation and prompts
xenoscopic 9239ef3
agents/sec-rev: simplify proxy URL handling
xenoscopic 331764a
agents/sec-rev: rely solely on container sandboxing for Codex
xenoscopic 0a7a5e2
agents/sec-rev: use textual output for Codex and add Claude output note
xenoscopic adfadf7
agents/sec-rev: parse extra arguments more robustly
xenoscopic 9aa96fd
agents/sec-rev: additional consistency and cleanup fixes for reviewer
xenoscopic 2c3230c
agents/sec-rev: bump proxy timeout for inference calls
xenoscopic 525e59f
agents/sec-rev: add overall timeout to reviews
xenoscopic 8cb53f4
ci: add concurrency group spec to update-pins
xenoscopic 52f41cc
agents/sec-rev: final tweaks to agent code after review
xenoscopic bfd3a5a
ci: final workflow tweaks before merging
xenoscopic 4e538ca
agents/sec-rev: adjust healthcheck path
xenoscopic 170843f
ci: switch security-review workflow to use GitHub checks
xenoscopic 0d7746c
ci: clean up workflows and supporting commands
xenoscopic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| name: Security Review (Manual) | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| servers: | ||
| description: "Comma-separated list of local server names to audit (leave blank for all)." | ||
| required: false | ||
| default: "" | ||
| agent: | ||
| description: "Optional reviewer agent (claude or codex)." | ||
| required: false | ||
| default: "" | ||
| model: | ||
| description: "Optional reviewer model override." | ||
| required: false | ||
| default: "" | ||
| timeout_secs: | ||
| description: "Optional reviewer timeout in seconds (defaults to 1800)." | ||
| required: false | ||
| default: "" | ||
|
|
||
| concurrency: | ||
| group: security-review-manual-${{ github.run_id }} | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| full-audit: | ||
| name: Execute Full Audit | ||
| runs-on: ubuntu-24.04 | ||
| permissions: | ||
| contents: read | ||
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Install Go | ||
| uses: actions/setup-go@v5 | ||
| with: | ||
| go-version-file: go.mod | ||
|
|
||
| - name: Install Task | ||
| uses: arduino/setup-task@v2 | ||
| with: | ||
| version: 3.x | ||
| repo-token: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Collect audit targets | ||
| id: collect | ||
| run: | | ||
| set -euo pipefail | ||
| task ci -- collect-full-audit \ | ||
| --workspace "${{ github.workspace }}" \ | ||
| --servers "${{ github.event.inputs.servers }}" \ | ||
| --output-json audit-targets.json | ||
|
|
||
| if jq -e '. | length > 0' audit-targets.json >/dev/null; then | ||
| echo "has_targets=true" >> "$GITHUB_OUTPUT" | ||
| echo "targets=audit-targets.json" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "No audit targets identified; exiting." >&2 | ||
| echo "has_targets=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Run security reviews | ||
| if: steps.collect.outputs.has_targets == 'true' | ||
| env: | ||
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | ||
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | ||
| REVIEW_AGENT_INPUT: ${{ github.event.inputs.agent }} | ||
| REVIEW_MODEL_INPUT: ${{ github.event.inputs.model }} | ||
| REVIEW_TIMEOUT_INPUT: ${{ github.event.inputs.timeout_secs }} | ||
| run: | | ||
| set -uo pipefail | ||
| agent="${REVIEW_AGENT_INPUT:-}" | ||
| if [ -z "$agent" ]; then | ||
| agent="claude" | ||
| fi | ||
|
|
||
| model="${REVIEW_MODEL_INPUT:-}" | ||
|
|
||
| timeout_secs="${REVIEW_TIMEOUT_INPUT:-1800}" | ||
|
|
||
| mkdir -p reports | ||
|
|
||
| while read -r target; do | ||
| server=$(echo "$target" | jq -r '.server') | ||
| project=$(echo "$target" | jq -r '.project') | ||
| head_commit=$(echo "$target" | jq -r '.commit') | ||
|
|
||
| if [ -z "$project" ] || [ "$project" = "null" ]; then | ||
| echo "Skipping $server: missing project URL." >&2 | ||
| continue | ||
| fi | ||
| if [ -z "$head_commit" ] || [ "$head_commit" = "null" ]; then | ||
| echo "Skipping $server: missing commit information." >&2 | ||
| continue | ||
| fi | ||
|
|
||
| report_path="reports/${server}.md" | ||
| labels_path="reports/${server}-labels.txt" | ||
| cmd=(task security-reviewer -- \ | ||
| --agent "$agent" \ | ||
| --repo "$project" \ | ||
| --head "$head_commit" \ | ||
| --target-label "$server" \ | ||
| --output "$report_path" \ | ||
| --labels-output "$labels_path" \ | ||
| --timeout "$timeout_secs") | ||
|
|
||
| if [ -n "$model" ]; then | ||
| cmd+=(--model "$model") | ||
| fi | ||
|
|
||
| if ! "${cmd[@]}"; then | ||
| echo "Security review failed for $server" >&2 | ||
| fi | ||
| done < <(jq -c '.[]' "${{ steps.collect.outputs.targets }}") | ||
|
|
||
| - name: Upload security reports | ||
| if: steps.collect.outputs.has_targets == 'true' | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: security-reports | ||
| path: reports/ | ||
| if-no-files-found: warn |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| name: Update MCP Server Version Pins | ||
|
|
||
| on: | ||
| # schedule: | ||
| # - cron: "0 5 * * *" | ||
| workflow_dispatch: | ||
| inputs: | ||
| max_new_prs: | ||
| description: "Maximum number of new pull requests to create (leave blank for unlimited)." | ||
| required: false | ||
| default: "" | ||
| servers: | ||
| description: "Comma-separated list of servers to update (leave blank for all)." | ||
| required: false | ||
| default: "" | ||
|
|
||
| concurrency: | ||
| group: update-pins | ||
| cancel-in-progress: false | ||
|
|
||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| update-pins: | ||
| runs-on: ubuntu-24.04 | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Configure Git user | ||
| run: | | ||
| git config user.name "docker-mcp-bot" | ||
| git config user.email "[email protected]" | ||
|
|
||
| - name: Install Go | ||
| uses: actions/setup-go@v5 | ||
| with: | ||
| go-version-file: go.mod | ||
|
|
||
| - name: Install Task | ||
| uses: arduino/setup-task@v2 | ||
| with: | ||
| version: 3.x | ||
| repo-token: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Update pinned commits | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| task ci -- update-pins | ||
|
|
||
| - name: Collect per-server patches | ||
| id: prepare | ||
| env: | ||
| ALLOWED_SERVERS: ${{ github.event.inputs.servers || '' }} | ||
| run: | | ||
| # Gather the diff for each modified server YAML and store it as an | ||
| # individual patch file so we can open one PR per server. | ||
| mkdir -p patches | ||
| changed_files=$(git status --porcelain | awk '$2 ~ /^servers\/.*\/server.yaml$/ {print $2}') | ||
| if [ -z "$changed_files" ]; then | ||
| echo "changed=false" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
|
|
||
| allowed_servers=$(echo "$ALLOWED_SERVERS" | tr '[:upper:]' '[:lower:]' | tr -d ' ') | ||
| server_list=() | ||
| for file in $changed_files; do | ||
| server=$(basename "$(dirname "$file")") | ||
| server_lc=$(echo "$server" | tr '[:upper:]' '[:lower:]') | ||
|
|
||
| if [ -n "$allowed_servers" ]; then | ||
| if ! echo ",$allowed_servers," | grep -q ",$server_lc,"; then | ||
| continue | ||
| fi | ||
| fi | ||
|
|
||
| git diff -- "$file" > "patches/${server}.patch" | ||
| server_list+=("$server") | ||
| done | ||
|
|
||
| if [ ${#server_list[@]} -eq 0 ]; then | ||
| echo "No servers matched the provided filter; exiting." >&2 | ||
| git checkout -- servers | ||
| echo "changed=false" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Reset the working tree so we can apply patches one-at-a-time. | ||
| git checkout -- servers | ||
|
|
||
| # Expose the server list to later steps. | ||
| printf '%s\n' "${server_list[@]}" | paste -sd',' - > patches/servers.txt | ||
| echo "changed=true" >> "$GITHUB_OUTPUT" | ||
| echo "servers=$(cat patches/servers.txt)" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Create or update pull requests | ||
| if: steps.prepare.outputs.changed == 'true' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| MAX_NEW_PRS: ${{ github.event.inputs.max_new_prs || '' }} | ||
| run: | | ||
| IFS=',' read -ra SERVERS <<< "${{ steps.prepare.outputs.servers }}" | ||
| new_pr_limit=$(echo "$MAX_NEW_PRS" | tr -d ' ') | ||
| if [ -n "$new_pr_limit" ] && ! [[ "$new_pr_limit" =~ ^[0-9]+$ ]]; then | ||
| echo "Invalid max_new_prs value: $new_pr_limit" >&2 | ||
| exit 1 | ||
| fi | ||
| new_pr_count=0 | ||
|
|
||
| for server in "${SERVERS[@]}"; do | ||
| patch="patches/${server}.patch" | ||
| if [ ! -s "$patch" ]; then | ||
| echo "No patch found for $server, skipping." | ||
| continue | ||
| fi | ||
|
|
||
| # Look up the new commit hash in the patch so we can decide whether | ||
| # an existing automation branch already covers it. | ||
| new_commit=$(awk '/^\+.*commit:/{print $2}' "$patch" | tail -n1) | ||
| branch="automation/update-pin-${server}" | ||
|
|
||
| # Start from a clean copy of main for each server so branches do not | ||
| # interfere with one another. | ||
| git checkout main | ||
| git fetch origin main | ||
| git reset --hard origin/main | ||
|
|
||
| # Check if we've hit the new PR limit before doing any work. | ||
| branch_exists=false | ||
| if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then | ||
| branch_exists=true | ||
| git fetch origin "$branch" | ||
| existing_commit=$(git show "origin/${branch}:servers/${server}/server.yaml" 2>/dev/null | awk '/commit:/{print $2}' | tail -n1) | ||
| if [ -n "$existing_commit" ] && [ "$existing_commit" = "$new_commit" ]; then | ||
| echo "Existing PR for $server already pins ${existing_commit}; skipping." | ||
| continue | ||
| fi | ||
| fi | ||
|
|
||
| # Check PR limit for new branches only. | ||
| if [ "$branch_exists" = false ] && [ -n "$new_pr_limit" ] && [ "$new_pr_count" -ge "$new_pr_limit" ]; then | ||
| echo "New PR quota reached ($new_pr_limit); skipping $server." | ||
| continue | ||
| fi | ||
|
|
||
| # Apply the patch onto a fresh branch for this server. | ||
| git checkout -B "$branch" origin/main | ||
| if ! git apply "$patch"; then | ||
| echo "Failed to apply patch for $server, skipping." | ||
| continue | ||
| fi | ||
|
|
||
| if git diff --quiet; then | ||
| echo "No changes after applying patch for $server, skipping." | ||
| continue | ||
| fi | ||
|
|
||
| # Commit the server YAML change and force-push the automation branch. | ||
| git add "servers/${server}/server.yaml" | ||
| git commit -m "chore: update pin for ${server}" | ||
| if ! git push --force origin "$branch"; then | ||
| echo "Failed to push branch for $server, skipping." >&2 | ||
| continue | ||
| fi | ||
|
|
||
| # Create or update the PR dedicated to this server. | ||
| if gh pr view --head "$branch" >/dev/null 2>&1; then | ||
| if ! gh pr edit "$branch" \ | ||
| --title "chore: update pin for ${server}" \ | ||
| --body "Automated commit pin update for ${server}." 2>&1; then | ||
| echo "Failed to update PR for $server" >&2 | ||
| fi | ||
| else | ||
| if gh pr create \ | ||
| --title "chore: update pin for ${server}" \ | ||
| --body "Automated commit pin update for ${server}." \ | ||
| --base main \ | ||
| --head "$branch" 2>&1; then | ||
| new_pr_count=$((new_pr_count + 1)) | ||
| else | ||
| echo "Failed to create PR for $server" >&2 | ||
| fi | ||
| fi | ||
| done | ||
|
|
||
| # Leave the repository in a clean state. | ||
| git checkout main | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.