Files
brave-core/.github/workflows/socket-fix.yml
T
Kyle Den Hartog 52b04a59b6 ci: use GitHub API to produce verified commits in socket-fix workflow (#35618)
* ci: use GitHub API to produce verified commits in socket-fix workflow

Replace git commit/push with the GitHub Git Data API (blobs, trees,
commits) so the resulting PR commit is signed and verified by GitHub,
matching the approach used in update-dep.yml.

* ci: disable socket fix CI mode to prevent auto-PR creation

* ci: address code review feedback on open-pr step

- Add set -eEo pipefail / shopt -s inherit_errexit
- Use git status --porcelain to catch new and deleted files
- Read file mode from git index instead of hardcoding 100644
- Use base64 -w 0 to avoid line-wrap corruption
- Handle existing branch on workflow rerun
- Handle existing PR on workflow rerun
- Support comma-separated issue_link for multiple GHSAs
- Include WDP/Leo PR links in body when refs are provided

* ci: address second round of code review feedback

- Fetch HEAD_SHA from master ref via API to guard against workflow
  dispatch on a non-master ref
- Switch to git status --porcelain=v1 -z (NUL-delimited) to handle
  filenames with spaces and rename/copy entries correctly
- Use @<(...) process substitution for blob content to avoid hitting
  shell argument length limits on large files like package-lock.json

* ci: fix gh pr create missing --head and rename tree deletion

- Add --head "$BRANCH" to gh pr create so the PR is opened from the
  API-created branch rather than the currently checked-out branch
- Emit a null-sha tree entry for the source path of rename operations
  so the old path is removed from the resulting commit
2026-04-18 08:48:31 +12:00

180 lines
7.5 KiB
YAML

name: Socket Fix by GHSA
on:
workflow_dispatch:
inputs:
ghsa_ids:
description: 'Comma-separated list of GHSA IDs (e.g. GHSA-442j-39wm-28r2,GHSA-7rx3-28cr-v5wh)'
required: true
type: string
issue_link:
description: 'Comma-separated links to related security issues (e.g. https://github.com/brave/brave-browser/issues/54048,https://github.com/brave/brave-browser/issues/54049)'
required: true
type: string
wdp_ref:
description: 'Commit hash for web-discovery-project DEPS update (optional)'
required: false
type: string
leo_ref:
description: 'Commit hash for @brave/leo package.json update (optional)'
required: false
type: string
permissions:
contents: write
pull-requests: write
jobs:
socket-fix:
runs-on: ubuntu-slim
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '24'
- name: Update WDP in DEPS
if: inputs.wdp_ref != ''
env:
WDP_REF: ${{ inputs.wdp_ref }}
run: |
[[ "$WDP_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "::error::Invalid wdp_ref: must be a 40-char hex SHA"; exit 1; }
sed -i -E "s|(\"vendor/web-discovery-project\"[^@]*@)[0-9a-f]{40}|\1${WDP_REF}|" DEPS
grep -q "$WDP_REF" DEPS || { echo "::error::Failed to update WDP hash in DEPS"; exit 1; }
- name: Update Leo in package.json
if: inputs.leo_ref != ''
env:
LEO_REF: ${{ inputs.leo_ref }}
run: |
[[ "$LEO_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "::error::Invalid leo_ref: must be a 40-char hex SHA"; exit 1; }
sed -i -E "s|(\"@brave/leo\"[^#]*#)[0-9a-f]{40}|\1${LEO_REF}|" package.json
grep -q "$LEO_REF" package.json || { echo "::error::Failed to update Leo hash in package.json"; exit 1; }
- name: Install Socket CLI
run: npm install -g @socketsecurity/cli
- name: Run Socket Fix
env:
GHSA_IDS: ${{ inputs.ghsa_ids }}
SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_SECURITY_API_KEY }}
SOCKET_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CI: ''
run: |
if ! [[ "$GHSA_IDS" =~ ^GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}(,[[:space:]]?GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4})*$ ]]; then
echo "::error::GHSA_IDS contains invalid entries. Expected format: GHSA-xxxx-xxxx-xxxx (comma or comma-space separated)"
exit 1
fi
IFS=',' read -ra IDS <<< "$GHSA_IDS"
for id in "${IDS[@]}"; do FLAGS+=(--id "${id// /}"); done
socket config set defaultOrg "brave"
socket fix . "${FLAGS[@]}"
- name: Open Pull Request
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GHSA_IDS: ${{ inputs.ghsa_ids }}
ISSUE_LINK: ${{ inputs.issue_link }}
WDP_REF: ${{ inputs.wdp_ref }}
LEO_REF: ${{ inputs.leo_ref }}
run: |
shopt -s inherit_errexit
set -eEo pipefail
if [ -z "$(git status --porcelain)" ]; then
echo "::error::No changes produced by socket fix."
exit 1
fi
BRANCH="socket-fix/$(echo "$GHSA_IDS" | tr ', ' '-' | tr -s '-' | cut -c1-60)"
HEAD_SHA=$(gh api "/repos/${GITHUB_REPOSITORY}/git/refs/heads/master" -q '.object.sha')
if gh api "repos/${GITHUB_REPOSITORY}/git/refs/heads/$BRANCH" -q .ref > /dev/null 2>&1; then
gh api --method PATCH "/repos/${GITHUB_REPOSITORY}/git/refs/heads/$BRANCH" \
--field sha="$HEAD_SHA" --field force=true
else
gh api --method POST /repos/${GITHUB_REPOSITORY}/git/refs \
--field ref="refs/heads/$BRANCH" --field sha="$HEAD_SHA"
fi
TREE_JSON="[]"
while IFS= read -r -d $'\0' line; do
XY="${line:0:2}"
FILE="${line:3}"
# For rename/copy entries the old path follows as the next NUL entry;
# emit a deletion for it so the source path is removed from the tree.
if [[ "${XY:0:1}" =~ [RC] ]]; then
IFS= read -r -d $'\0' _old_path
if [[ "${XY:0:1}" == "R" ]]; then
TREE_JSON=$(jq --arg path "$_old_path" \
'. += [{"path": $path, "mode": "100644", "type": "blob", "sha": null}]' \
<<< "$TREE_JSON")
fi
fi
if [[ "${XY:1:1}" == "D" ]]; then
TREE_JSON=$(jq --arg path "$FILE" \
'. += [{"path": $path, "mode": "100644", "type": "blob", "sha": null}]' \
<<< "$TREE_JSON")
else
MODE=$(git ls-files -s -- "$FILE" | awk 'NR==1 { print $1 }')
[[ -z "$MODE" ]] && MODE="100644"
if [[ "$MODE" == "120000" ]]; then
BLOB_SHA=$(gh api --method POST /repos/${GITHUB_REPOSITORY}/git/blobs \
--field content=@<(printf '%s' "$(readlink -- "$FILE")" | base64 -w 0) \
--field encoding=base64 \
-q .sha)
else
BLOB_SHA=$(gh api --method POST /repos/${GITHUB_REPOSITORY}/git/blobs \
--field content=@<(base64 -w 0 -- "$FILE") \
--field encoding=base64 \
-q .sha)
fi
TREE_JSON=$(jq --arg path "$FILE" --arg mode "$MODE" --arg sha "$BLOB_SHA" \
'. += [{"path": $path, "mode": $mode, "type": "blob", "sha": $sha}]' \
<<< "$TREE_JSON")
fi
done < <(git status --porcelain=v1 -z)
BASE_TREE=$(gh api /repos/${GITHUB_REPOSITORY}/git/commits/"$HEAD_SHA" -q .tree.sha)
NEW_TREE_SHA=$(jq -n \
--arg base_tree "$BASE_TREE" \
--argjson tree "$TREE_JSON" \
'{"base_tree": $base_tree, "tree": $tree}' | \
gh api --method POST /repos/${GITHUB_REPOSITORY}/git/trees --input - -q .sha)
NEW_COMMIT_SHA=$(jq -n \
--arg tree "$NEW_TREE_SHA" \
--arg parent "$HEAD_SHA" \
'{"message": "fix: address security advisories", "tree": $tree, "parents": [$parent]}' | \
gh api --method POST /repos/${GITHUB_REPOSITORY}/git/commits --input - -q .sha)
gh api --method PATCH /repos/${GITHUB_REPOSITORY}/git/refs/heads/"$BRANCH" \
--field sha="$NEW_COMMIT_SHA"
BODY="Addresses: $GHSA_IDS"
IFS=',' read -ra LINKS <<< "$ISSUE_LINK"
for link in "${LINKS[@]}"; do
BODY="$BODY"$'\n\n'"closes ${link// /}"
done
if [[ -n "$WDP_REF" ]]; then
WDP_PR=$(gh api "repos/brave/web-discovery-project/commits/$WDP_REF/pulls" -q '.[0].html_url' 2>/dev/null || true)
[[ -n "$WDP_PR" ]] && BODY="$BODY"$'\n\n'"WDP: $WDP_PR"
fi
if [[ -n "$LEO_REF" ]]; then
LEO_PR=$(gh api "repos/brave/leo/commits/$LEO_REF/pulls" -q '.[0].html_url' 2>/dev/null || true)
[[ -n "$LEO_PR" ]] && BODY="$BODY"$'\n\n'"Leo: $LEO_PR"
fi
if ! gh pr list --repo "${GITHUB_REPOSITORY}" --head "$BRANCH" --json number -q '.[].number' | grep -q .; then
gh pr create \
--repo "${GITHUB_REPOSITORY}" \
--title "fix: address security advisories" \
--body "$BODY" \
--head "$BRANCH" \
--base master \
--label "CI/skip" \
--label "security"
fi