Skip to content

new_repository_created #44

new_repository_created

new_repository_created #44

name: 'Onboard New Repository with SAST'
on:
workflow_dispatch:
inputs:
organization:
description: 'Organization name (e.g., MetaMask)'
required: true
type: string
repository:
description: 'Repository name (e.g., snaps)'
required: true
type: string
repository_dispatch:
types: [new_repository_created]
jobs:
create-sast-pr:
runs-on: ubuntu-latest
environment: onboarding
permissions:
contents: read
steps:
- name: Checkout scanner action repository
uses: actions/checkout@v4
with:
path: scanner-repo
- name: Parse target repository inputs
id: target
run: |
validate_name() {
local value="$1"
local label="$2"
local max_len="$3"
if [ -z "$value" ]; then
echo "::error::$label is empty"
exit 1
fi
if [ "${#value}" -gt "$max_len" ]; then
echo "::error::$label exceeds maximum length of $max_len characters"
exit 1
fi
if ! echo "$value" | grep -qE '^[a-zA-Z0-9._-]+$'; then
echo "::error::$label contains invalid characters (only alphanumeric, dots, hyphens, and underscores are allowed)"
exit 1
fi
}
if [ "$EVENT_NAME" = "repository_dispatch" ]; then
ORG="$EVENT_ORG"
REPO_NAME="$EVENT_REPO"
else
ORG="$INPUT_ORG"
REPO_NAME="$INPUT_REPO"
fi
validate_name "$ORG" "Organization" 39
validate_name "$REPO_NAME" "Repository" 100
{
echo "organization=$ORG"
echo "repo_name=$REPO_NAME"
echo "repository=$ORG/$REPO_NAME"
} >> "$GITHUB_OUTPUT"
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
EVENT_ORG: ${{ github.event.client_payload.organization }}
EVENT_REPO: ${{ github.event.client_payload.repository }}
INPUT_ORG: ${{ inputs.organization }}
INPUT_REPO: ${{ inputs.repository }}
- name: Generate GitHub App token
id: app_token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.ONBOARDING_APP_ID }}
private-key: ${{ secrets.ONBOARDING_APP_PRIVATE_KEY }}
owner: ${{ steps.target.outputs.organization }}
repositories: ${{ steps.target.outputs.repo_name }}
- name: Detect default branch
id: detect_branch
run: |
echo "Detecting default branch for $REPO..."
BASE_BRANCH=$(gh api "repos/$REPO" --jq '.default_branch' 2>/dev/null) || BASE_BRANCH=""
if [ -z "$BASE_BRANCH" ] || [ "$BASE_BRANCH" = "null" ]; then
echo "Repository is empty or default branch not found. Defaulting to 'main'"
BASE_BRANCH="main"
fi
if ! echo "$BASE_BRANCH" | grep -qE '^[a-zA-Z0-9._/-]+$'; then
echo "::error::Branch name contains invalid characters (only alphanumeric, dots, hyphens, slashes, and underscores are allowed)"
exit 1
fi
echo "base_branch=$BASE_BRANCH" >> "$GITHUB_OUTPUT"
shell: bash
env:
GH_TOKEN: ${{ steps.app_token.outputs.token }}
REPO: ${{ steps.target.outputs.repository }}
- name: Check for opt-out file
id: check_opt_out
run: |
if gh api "repos/$REPO/contents/.github/no-security-scanner?ref=$BASE_BRANCH" > /dev/null 2>&1; then
echo "Repository has opted out via .github/no-security-scanner"
echo "opted_out=true" >> "$GITHUB_OUTPUT"
else
echo "opted_out=false" >> "$GITHUB_OUTPUT"
fi
shell: bash
env:
GH_TOKEN: ${{ steps.app_token.outputs.token }}
REPO: ${{ steps.target.outputs.repository }}
BASE_BRANCH: ${{ steps.detect_branch.outputs.base_branch }}
- name: Skip onboarding (repository opted out)
if: steps.check_opt_out.outputs.opted_out == 'true'
run: |
echo "::notice::Skipping onboarding — repository $REPO has a .github/no-security-scanner opt-out file"
env:
REPO: ${{ steps.target.outputs.repository }}
- name: Check if target repository is empty
if: steps.check_opt_out.outputs.opted_out != 'true'
id: check_empty
run: |
if ! BRANCHES=$(gh api "repos/$REPO/branches" --jq 'length' 2>&1); then
echo "::error::GitHub API call failed while listing branches for $REPO: $BRANCHES"
exit 1
fi
if ! echo "$BRANCHES" | grep -qE '^[0-9]+$'; then
echo "::error::Unexpected API response when listing branches for $REPO: $BRANCHES"
exit 1
fi
if [ "$BRANCHES" = "0" ]; then
IS_EMPTY="true"
echo "Repository is empty (no branches found)"
else
IS_EMPTY="false"
echo "Repository has $BRANCHES branch(es)"
fi
echo "is_empty=$IS_EMPTY" >> "$GITHUB_OUTPUT"
shell: bash
env:
GH_TOKEN: ${{ steps.app_token.outputs.token }}
REPO: ${{ steps.target.outputs.repository }}
- name: Checkout target repository
if: steps.check_opt_out.outputs.opted_out != 'true' && steps.check_empty.outputs.is_empty == 'false'
uses: actions/checkout@v4
with:
repository: ${{ steps.target.outputs.repository }}
token: ${{ steps.app_token.outputs.token }}
path: target-repo
ref: ${{ steps.detect_branch.outputs.base_branch }}
- name: Initialize empty repository locally
if: steps.check_opt_out.outputs.opted_out != 'true' && steps.check_empty.outputs.is_empty == 'true'
run: |
mkdir -p target-repo
cd target-repo
git init
git remote add origin "https://x-access-token:${APP_TOKEN}@github.com/${REPO}.git"
shell: bash
env:
APP_TOKEN: ${{ steps.app_token.outputs.token }}
REPO: ${{ steps.target.outputs.repository }}
- name: Create branch and add SAST workflow
id: create_branch
if: steps.check_opt_out.outputs.opted_out != 'true'
working-directory: target-repo
env:
IS_EMPTY: ${{ steps.check_empty.outputs.is_empty }}
BASE_BRANCH: ${{ steps.detect_branch.outputs.base_branch }}
run: |
git config user.name "MetaMask Security Bot"
git config user.email "security-bot@metamask.io"
if [ "$IS_EMPTY" = "true" ]; then
# For empty repos, create initial commit on main
BRANCH_NAME="$BASE_BRANCH"
else
# For existing repos, create a feature branch
BRANCH_NAME="security/add-sast-scanner"
git checkout -b "$BRANCH_NAME"
fi
mkdir -p .github/workflows
sed "s|{ DEFAULT_BRANCH }|$BASE_BRANCH|g" \
../scanner-repo/.github/templates/security-code-scanner.yml \
> .github/workflows/security-code-scanner.yml
git add .github/workflows/security-code-scanner.yml
if git diff --cached --quiet; then
echo "::notice::Workflow file already exists and matches — nothing to commit"
echo "skipped=true" >> "$GITHUB_OUTPUT"
exit 0
fi
git commit -m "chore: add MetaMask Security Code Scanner workflow
This PR adds the MetaMask Security Code Scanner workflow to enable
automated security scanning of the codebase.
The scanner will run on:
- Push to $BASE_BRANCH branch
- Pull requests to $BASE_BRANCH branch
- Manual workflow dispatch
To configure the scanner for your repository's specific needs,
please review the workflow file and adjust as necessary."
if [ "$IS_EMPTY" = "true" ]; then
git branch -M "$BRANCH_NAME"
fi
git push -u origin "$BRANCH_NAME"
echo "skipped=false" >> "$GITHUB_OUTPUT"
shell: bash
- name: Create Pull Request
if: steps.check_opt_out.outputs.opted_out != 'true' && steps.check_empty.outputs.is_empty == 'false' && steps.create_branch.outputs.skipped != 'true'
working-directory: target-repo
env:
GH_TOKEN: ${{ steps.app_token.outputs.token }}
REPO_NAME: ${{ steps.target.outputs.repository }}
BASE_BRANCH: ${{ steps.detect_branch.outputs.base_branch }}
run: |
# Extract owner and repo name for URL construction
OWNER=$(echo "$REPO_NAME" | cut -d'/' -f1)
REPO=$(echo "$REPO_NAME" | cut -d'/' -f2)
SECURITY_URL="https://github.com/${OWNER}/${REPO}/security/code-scanning"
# Read PR body template and substitute variables
PR_BODY=$(cat ../scanner-repo/.github/templates/onboarding-pr-body-automated.md)
PR_BODY="${PR_BODY//\{\{SECURITY_SCANNING_URL\}\}/$SECURITY_URL}"
gh pr create \
--title "🔒 Add MetaMask Security Code Scanner" \
--body "$PR_BODY" \
--base "$BASE_BRANCH" \
--head "security/add-sast-scanner"
shell: bash
- name: Output PR URL
if: steps.check_opt_out.outputs.opted_out != 'true' && steps.check_empty.outputs.is_empty == 'false' && steps.create_branch.outputs.skipped != 'true'
working-directory: target-repo
env:
GH_TOKEN: ${{ steps.app_token.outputs.token }}
run: |
PR_URL=$(gh pr view security/add-sast-scanner --json url -q .url)
echo "✅ Pull Request created: $PR_URL"
echo "PR_URL=$PR_URL" >> "$GITHUB_OUTPUT"
shell: bash
- name: Output commit info for empty repo
if: steps.check_opt_out.outputs.opted_out != 'true' && steps.check_empty.outputs.is_empty == 'true' && steps.create_branch.outputs.skipped != 'true'
run: |
echo "✅ Initial commit pushed to https://github.com/$REPO/tree/$BASE_BRANCH"
echo "Repository was empty - workflow file added directly to $BASE_BRANCH branch"
shell: bash
env:
REPO: ${{ steps.target.outputs.repository }}
BASE_BRANCH: ${{ steps.detect_branch.outputs.base_branch }}
- name: Post to Slack channel on failure
if: ${{ failure() && env.SLACK_WEBHOOK_URL != '' }}
uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
with:
payload: |
{
"text": "Onboarding failed for ${{ steps.target.outputs.repository }} - Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.APPSEC_BOT_SLACK_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK