Skip to content

feat: add migration eval hooks for security, quality, and audit - #13

Closed
AlexDeMichieli wants to merge 3 commits into
mainfrom
feat/migration-eval-hooks
Closed

feat: add migration eval hooks for security, quality, and audit#13
AlexDeMichieli wants to merge 3 commits into
mainfrom
feat/migration-eval-hooks

Conversation

@AlexDeMichieli

@AlexDeMichieli AlexDeMichieli commented Mar 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

The current migration-guardrails.md provides excellent guidance for Copilot agents, but relies entirely on prompt-based enforcement. As migrations scale to hundreds of repos, prompt-only guardrails face limitations:

  • No hard stops — Copilot can acknowledge a rule then violate it
  • No audit trail — No record of what the agent actually did
  • No automated quality gate — Human reviewers must manually verify every migration
  • Inconsistent enforcement — Different sessions may interpret rules differently

This PR adds Copilot coding agent hooks that mechanically enforce guardrails at runtime. Hooks fire at lifecycle events (session start/end, before/after each tool call) and can block dangerous operations, log all actions, and generate quality scorecards automatically.

Hooks Added

Hook Lifecycle Purpose
verify-ci-sources.sh sessionStart Fail fast if no CI source files (Jenkinsfile, .gitlab-ci.yml, etc.) exist
security-guard.sh preToolUse Block dangerous operations: rm -rf, external curl, source code edits, unpinned actions, hardcoded secrets, custom actions creation
audit-log.sh postToolUse Log every tool call to .github/ci-archive/migration-audit.jsonl
eval-migration.sh sessionEnd Generate quality scorecard at .github/ci-archive/migration-scorecard.md

Cloud Test Results

Tested on EMU enterprise (volcano-coffee) with repo alexdemichieli-migrations/jenkins-migration-test:

PR #5 — All 4 hooks confirmed working

Hook What It Does Status Evidence
verify-ci-sources.sh Scans repo for Jenkinsfile, .gitlab-ci.yml, .travis.yml, etc. Blocks session if none found. ✅ Working Session started (Jenkinsfile detected)
security-guard.sh Intercepts every tool call. Returns DENY for dangerous ops (rm -rf, external curl, source edits, unpinned actions). ✅ Working Blocked create/edit tool calls, agent fell back to bash
audit-log.sh Appends JSONL entry for every tool call with timestamp, tool name, result, and file path. ✅ Working migration-audit.jsonl committed with 20+ entries
eval-migration.sh Runs 11 quality checks and writes pass/fail/warn scorecard to file. ✅ Working migration-scorecard.md committed with 9 pass, 2 warn

Scorecard Output

Location: .github/ci-archive/migration-scorecard.md (automatically generated)

## Migration Eval Report

| Check | Result | Details |
|-------|--------|---------|
| Workflows created | ✅ Pass | 1 workflow file(s) |
| Originals archived | ✅ Pass | 3 file(s) archived |
| No originals remain | ✅ Pass | No CI source files outside archive |
| MIGRATION-README.md | ✅ Pass | Exists with 60 lines |
| No placeholders | ✅ Pass | No placeholder text found |
| Verified creators | ✅ Pass | All actions from verified creators |
| Permissions | ✅ Pass | Workflows declare permissions |
| Secrets documented | ✅ Pass | All secrets documented |

Score: 9 passed, 0 failed, 2 warnings
✅ Migration meets all required standards.

Security Guard Blocks (from audit log)

{"tool":"create","result":"denied","file":".github/workflows/ci.yml"}
{"tool":"edit","result":"denied","file":".github/workflows/ci.yml"}

The agent correctly fell back to bash commands after direct create/edit was blocked.

Guardrails Coverage

Enforced mechanically by hooks (14 guardrails):

  • ✅ No rm -rf commands
  • ✅ No external network calls (curl to non-GitHub URLs)
  • ✅ No source code modifications (.js, .ts, .py, etc.)
  • ✅ No hardcoded secrets in workflows
  • ✅ Actions must use SHA pinning
  • ✅ No custom actions creation (.github/actions/)
  • ✅ CI sources must exist before starting
  • ✅ Workflow files created
  • ✅ Originals archived
  • ✅ MIGRATION-README.md generated
  • ✅ No placeholder text in documentation
  • ✅ Actions from verified creators
  • ✅ Permissions declared
  • ✅ Secrets documented

Must remain as prompt guidance (6 guardrails):

  • 📝 5-phase migration process
  • 📝 Semantic mapping of CI constructs
  • 📝 Validation with actionlint + act
  • 📝 Original pipeline preserved in archive
  • 📝 Knowledge base consultation
  • 📝 Human review before merge

Files

.github/hooks/
├── migration-eval.json      # Hook configuration
├── verify-ci-sources.sh     # sessionStart hook
├── security-guard.sh        # preToolUse hook  
├── audit-log.sh             # postToolUse hook
└── eval-migration.sh        # sessionEnd hook

Test Artifacts

Add Copilot coding agent hooks that run at every lifecycle phase of a
migration agent session:

- sessionStart: verify-ci-sources.sh - fail fast if no CI files exist
- preToolUse: security-guard.sh - block dangerous commands, enforce policies
- postToolUse: audit-log.sh - JSONL audit trail of all tool calls
- sessionEnd: eval-migration.sh - 11-check quality scorecard

Tested against agbqwebqebqt/jenkins-migration-test PR #4:
  9 passed, 0 failed, 2 warnings
Copilot AI review requested due to automatic review settings March 20, 2026 14:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a set of Copilot migration-agent hook scripts and a hook configuration file to enforce migration guardrails (CI source presence, security restrictions, audit logging) and to generate a post-session migration quality scorecard.

Changes:

  • Add a sessionStart hook to detect whether the repo contains migratable CI/CD source configs.
  • Add a preToolUse security guard to restrict dangerous commands, restrict writable paths, and enforce workflow hardening.
  • Add postToolUse audit logging and a sessionEnd evaluation script that scores migration output against standards.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
.github/hooks/verify-ci-sources.sh Session start validator for presence of CI/CD source files.
.github/hooks/security-guard.sh Pre-tool-call policy enforcement for commands, file edits, secrets, and workflows.
.github/hooks/migration-eval.json Hook wiring for sessionStart/preToolUse/postToolUse/sessionEnd.
.github/hooks/eval-migration.sh Post-session migration scorecard checks (workflows, archive, pinning, permissions, docs).
.github/hooks/audit-log.sh JSONL audit trail for each tool invocation.
Comments suppressed due to low confidence (1)

.github/hooks/eval-migration.sh:347

  • grep -rn '^\s*-\?\s*uses:.*@[a-f0-9]\{40\}' uses \s which isn’t portable in ERE; this will likely undercount SHA-pinned uses: lines and make the “Version comments” check unreliable. Use [[:space:]] (or grep -P if available) for whitespace matching.
  while IFS= read -r line_num; do
    total=$((total + 1))
  done < <(grep -rn '^\s*-\?\s*uses:.*@[a-f0-9]\{40\}' "$WORKFLOWS_DIR"/ 2>/dev/null)


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

else
unpinned+=("$(echo "$line" | sed 's/.*uses: *//')")
fi
done < <(grep -rh '^\s*-\?\s*uses:' "$WORKFLOWS_DIR"/ 2>/dev/null | grep -v '#')
Comment on lines +24 to +28
FILE_PATH=$(echo "$INPUT" | jq -r '.toolArgs' | jq -r '.path // .filePath // empty' 2>/dev/null)
;;
bash)
# Try to extract meaningful context from bash commands (first 100 chars)
FILE_PATH=$(echo "$INPUT" | jq -r '.toolArgs' | jq -r '.command // empty' 2>/dev/null | head -c 100)
Comment on lines +16 to +22
set -euo pipefail

INPUT=$(cat)

TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName')
TOOL_ARGS=$(echo "$INPUT" | jq -r '.toolArgs')

Comment on lines +19 to +28
# CI source file patterns — at least one must exist for migration to proceed
CI_PATTERNS=(
"Jenkinsfile"
"*.jenkinsfile"
".gitlab-ci.yml"
".circleci/config.yml"
".travis.yml"
"azure-pipelines.yml"
"azure-pipelines/*.yml"
".drone.yml"
echo "Per migration guardrails: DO NOT create GitHub Actions" >&2
echo "workflows without a source CI/CD configuration file." >&2
echo "================================================================" >&2
exit 1
Comment on lines +170 to +176
# Search everywhere except .github/ci-archive/ and .git/
while IFS= read -r f; do
# Skip files inside the archive or .git
if [[ "$f" != *"$ARCHIVE_DIR"* && "$f" != *".git/"* ]]; then
found+=("$f")
fi
done < <(find . -path "./$ARCHIVE_DIR" -prune -o -path "./.git" -prune -o -name "$pattern" -print 2>/dev/null)
local output
output=$(actionlint "$WORKFLOWS_DIR"/*.yml "$WORKFLOWS_DIR"/*.yaml 2>&1 || true)
local errors
errors=$(echo "$output" | grep -c "error" 2>/dev/null || echo "0")
Comment on lines +326 to +330
done < <(grep -roh 'secrets\.[A-Z_]*' "$WORKFLOWS_DIR"/ 2>/dev/null | sed 's/secrets\.//' | sort -u)

if [[ ${#undocumented[@]} -eq 0 ]]; then
local total
total=$(grep -roh 'secrets\.[A-Z_]*' "$WORKFLOWS_DIR"/ 2>/dev/null | sed 's/secrets\.//' | sort -u | wc -l | tr -d ' ')
Comment on lines +35 to +45
# Block destructive system commands
if echo "$COMMAND" | grep -qE '(^|\s)(rm -rf /|sudo |mkfs |dd if=|chmod 777|chown )'; then
deny "Destructive system command blocked: $COMMAND"
fi

# Block external network calls to unknown hosts
# Allow: github.com, api.github.com (for MCP), localhost
if echo "$COMMAND" | grep -qE '(curl|wget|nc |ncat )\s' ; then
if ! echo "$COMMAND" | grep -qE '(github\.com|githubusercontent\.com|localhost|127\.0\.0\.1|actionlint)'; then
deny "External network call not permitted during migration. Only github.com and localhost are allowed."
fi

if [ -n "$CONTENT" ]; then
# Check for actions not pinned to SHAs (uses: org/action@v4 instead of @sha)
UNPINNED=$(echo "$CONTENT" | grep -E '^\s*-?\s*uses:' | grep -vE '@[a-f0-9]{40}' | grep -vE '^\s*#' || true)

Copilot AI commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Warning

This is an internal experiment to assess Copilot's ability to auto-approve PRs. Please 👍 this comment if the assessment below is correct and 👎 if not. Feedback in #f-ccr-auto-approve is appreciated!

Copilot thinks this PR is not ready to approve — see review comments for details.

@antgrutta

antgrutta commented Mar 22, 2026

Copy link
Copy Markdown
Collaborator

@AlexDeMichieli, I like many of the ideas we discussed, and I see those reflected here. This PR touches on a few different concerns, and I think the best path forward is to break them into separate, focused features while being deliberate about the technical direction.
As a first step, I suggest moving to agentic workflows, which would replace the issue based submit-repos.yml and overcome the limitations of the shared Enterprise agents. To help establish the pattern, could you provide an example of migrating a repository using agentic workflows and the existing agent definition files? Please include a sample hook as well. The CI file existence check that fails fast if none are found is a good example.
The goal is to define a solid baseline with an initial feature, ensure we are aligned on the approach, and then layer in more robust hooks as follow‑up work. Longer term, we want to move away from any additional per‑repository configuration, as this has proven confusing for developers in practice.
Preserving an experience with minimal disruption for developers and reducing the overall complexity of the migration needs to remain a primary design principle as we evolve this.

cc: @ssulei7

@AlexDeMichieli

Copy link
Copy Markdown
Collaborator Author

Closing this PR. While the hooks work well and were validated end-to-end in a live Copilot coding agent session (see test results in description), the current approach isn't ideal from a customer perspective — it requires pushing hook files into each target repository, which would mean creating a PR per repo before migrations can begin.

Key takeaways:

  • Copilot coding agent hooks are a great mechanism for enforcing migration quality and security criteria at runtime
  • The preToolUse hook can block dangerous operations in real time (confirmed in testing)
  • The sessionEnd hook can generate quality scorecards automatically (confirmed in testing)
  • However, there's no enterprise-level hooks configuration today — hooks must live in each repo's .github/hooks/ directory

A better path forward may involve working with product on enterprise-level hook support, or exploring alternative approaches that don't require per-repo setup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants