Skip to content
Merged
Show file tree
Hide file tree
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 Oct 17, 2025
ec6c6c9
ci: add AI-driven security-review workflows
xenoscopic Oct 23, 2025
2bf017d
ci: relocate update-pins under cmd/ci and add Copyright statements
xenoscopic Oct 23, 2025
6c1b2b2
ci: clean up types for CI helper commands
xenoscopic Oct 23, 2025
be61be6
ci: switch to manual triggers for differential reviews and pin updates
xenoscopic Oct 23, 2025
d0ea2d8
ci: add new security-reviewer agent
xenoscopic Oct 26, 2025
47f9dfe
ci: reorder security-reviewer report template
xenoscopic Oct 26, 2025
6cc2f11
ci: fixup security-reviewer label handling
xenoscopic Oct 26, 2025
f651d2e
ci: remove stale environment variable constants
xenoscopic Oct 26, 2025
f2fd09d
ci: rename and simplify security-review workflows
xenoscopic Oct 26, 2025
3548425
agents/sec-rev: adjust Go build directories
xenoscopic Oct 26, 2025
bcf2e49
agents/sec-rev: refactor prompt rendering and simplify config surface
xenoscopic Oct 27, 2025
7537df3
agents/sec-rev: make mode concept implicit
xenoscopic Oct 27, 2025
b78a31a
agents/sec-rev: replace LiteLLM with a custom proxy
xenoscopic Oct 27, 2025
7d7b4b0
agents/sec-rev: hide healthcheck logging in proxy
xenoscopic Oct 27, 2025
b402ff6
agents/sec-rev: minor tweaks to Claude/Codex invocation and prompts
xenoscopic Oct 27, 2025
9239ef3
agents/sec-rev: simplify proxy URL handling
xenoscopic Oct 27, 2025
331764a
agents/sec-rev: rely solely on container sandboxing for Codex
xenoscopic Oct 27, 2025
0a7a5e2
agents/sec-rev: use textual output for Codex and add Claude output note
xenoscopic Oct 27, 2025
adfadf7
agents/sec-rev: parse extra arguments more robustly
xenoscopic Oct 27, 2025
9aa96fd
agents/sec-rev: additional consistency and cleanup fixes for reviewer
xenoscopic Oct 27, 2025
2c3230c
agents/sec-rev: bump proxy timeout for inference calls
xenoscopic Oct 27, 2025
525e59f
agents/sec-rev: add overall timeout to reviews
xenoscopic Oct 28, 2025
8cb53f4
ci: add concurrency group spec to update-pins
xenoscopic Oct 28, 2025
52f41cc
agents/sec-rev: final tweaks to agent code after review
xenoscopic Oct 28, 2025
bfd3a5a
ci: final workflow tweaks before merging
xenoscopic Oct 28, 2025
4e538ca
agents/sec-rev: adjust healthcheck path
xenoscopic Oct 28, 2025
170843f
ci: switch security-review workflow to use GitHub checks
xenoscopic Oct 28, 2025
0d7746c
ci: clean up workflows and supporting commands
xenoscopic Oct 29, 2025
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
446 changes: 446 additions & 0 deletions .github/workflows/security-review-changes.yaml

Large diffs are not rendered by default.

129 changes: 129 additions & 0 deletions .github/workflows/security-review-manual.yaml
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
192 changes: 192 additions & 0 deletions .github/workflows/update-pins.yaml
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
8 changes: 8 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ tasks:
desc: Clean build artifacts for servers
cmd: go run ./cmd/clean {{.CLI_ARGS}}

ci:
desc: Run CI helper utilities
cmd: go run ./cmd/ci {{.CLI_ARGS}}

import:
desc: Import a server into the registry
cmd: docker mcp catalog import ./catalogs/{{.CLI_ARGS}}/catalog.yaml
Expand All @@ -38,3 +42,7 @@ tasks:
unittest:
desc: Run Go unit tests
cmd: go test ./...

security-reviewer:
desc: Run the security reviewer agent
cmd: go run ./cmd/security-reviewer {{.CLI_ARGS}}
Loading