Fix Vulnerabilities — git_integration
Triage Dependabot alerts for services/apps/git_integration, validate the
safe fixes locally, and hand humans a reviewed merge list via Slack.
v1 scope — only these manifests:
| Manifest | Ecosystem | Toolchain |
|---|---|---|
services/apps/git_integration/uv.lock (+ pyproject.toml) |
pip | uv |
services/apps/git_integration/src/crowdgit/services/vulnerability_scanner/go.mod |
go | go |
services/apps/git_integration/src/crowdgit/services/software_value/go.mod |
go | go |
Alerts on any other manifest (pnpm-lock.yaml, frontend, etc.) are out of scope: mention the count in the final report, do nothing else with them.
Hard rules
- NEVER merge any PR. Not Dependabot's, not ones this skill opens. The output for merge-ready PRs is a Slack review message, the merge is a human's.
- NEVER dismiss or modify Dependabot alerts.
- Never trust repo CI as validation for this worker — repo CI does not run the Python worker's tests. A green Dependabot PR proves nothing here; local validation is the only gate.
- Uncertainty is always
needs-human, neversafe. - No major version bumps in the safe tier, ever.
- Deploy dispatch only on explicit user confirmation, and staging before production.
- Requires a clean
git status --porcelainfor the git_integration paths before applying any local fix.
Phase 0 — Preconditions & scope
gh auth statusworks andgit status --porcelainis clean forservices/apps/git_integration/.uvis on PATH (uv --version) and Docker is running (docker info) — Go and image-level validation happen by building the real git-integration image, no local go toolchain needed. If Docker is down, packages can still be triaged/classified but anything not fully validated goes in the Slack message under needs-human with "validation not run", never under safe to merge.- Ask the user (AskUserQuestion, multiselect): which severities to address — critical, high, medium/low — and which Slack channel the review summary should go to (free text via "Other" is fine). Default severities: critical + high.
Non-interactive invocation: only when the invocation itself explicitly
passes both parameters (e.g.
/fix-vulns-git-integration non-interactive severities=critical,high channel=#my-channel),
skip the questions and use them; the Slack summary is then the run's output
and is sent without interactive approval. Every other gate still holds: no
merging, no deploys, and if a summary covering the same findings was already
posted, post nothing. This skill ships with no schedule and no default
channel — anyone who wants periodic runs sets up their own scheduler passing
their own parameters.
Phase 1 — Fetch alerts and Dependabot PRs
gh api 'repos/linuxfoundation/crowd.dev/dependabot/alerts?state=open&per_page=100' --paginate \
-q '.[] | {n:.number, sev:.security_advisory.severity, ghsa:.security_advisory.ghsa_id,
cve:.security_advisory.cve_id, eco:.dependency.package.ecosystem,
pkg:.dependency.package.name, manifest:.dependency.manifest_path,
range:.security_vulnerability.vulnerable_version_range,
patched:.security_vulnerability.first_patched_version.identifier}'
Filter to the three in-scope manifests and the selected severities. Dedupe by
(package, manifest) with the package name lowercased — alerts mix casings
(GitPython vs gitpython). Keep every GHSA but plan one fix per
(package, manifest) pair — the same package can be vulnerable in both Go
modules at different versions and each go.mod needs its own bump; the target
version is the highest first_patched_version across that pair's alerts.
Alerts with no patched version are needs-human immediately.
Before triaging a package, check main's current lockfile/go.mod: if the installed version already satisfies the patched version, the alert is stale (Dependabot hasn't rescanned since a merge) — drop it from the run and note it in the report.
gh api 'repos/linuxfoundation/crowd.dev/pulls?state=open&per_page=100' --paginate \
-q '.[] | select(.head.ref | startswith("dependabot/"))
| {number, title, headRefName: .head.ref, headSha: .head.sha, url: .html_url}'
(gh pr list truncates at its --limit; paginate so old git_integration PRs
— often the stalest — are never missed and mistaken for "no PR". Record each
PR's headSha: it is the revision validation applies to.)
Match PRs to packages via headRefName (patterns:
dependabot/uv/services/apps/git_integration/<pkg>-<ver>,
dependabot/go_modules/services/apps/git_integration/src/crowdgit/services/<module>/<pkg>-<ver>).
Read the version each PR actually resolves from its diff (go.mod / uv.lock),
not from the branch name alone. A PR resolving below the required patched
version can never be safe — the alert would stay open; it still counts as
"exists" but goes in the report as a gap. A PR resolving above the advisory's
minimum shifts the review window: Phase 2 classification and changelog review
must cover current → the PR's resolved version, not just the advisory
minimum.
Phase 2 — Classify each package
Determine for every in-scope package:
- direct vs transitive — pip: direct if listed anywhere in
pyproject.toml—[project.dependencies]or[dependency-groups](dev deps are still direct); transitive only if it appears solely inuv.lock. go: direct if required without// indirect, whether inside arequire (...)block or a standalone single-linerequire. - bump size — patch/minor vs major, measured from the current version
(
uv.lock/go.mod) to the version the fix actually lands: the PR's resolved version when a PR exists, else the advisory's patched version. A PR that lands on a major is a major bump — never safe — regardless of how small the advisory's minimum patched version is. - usage surface — grep
services/apps/git_integration/src/crowdgitfor imports/usage of the package. For >3 packages, fan out one Explore agent per package in parallel; each must return positive evidence, not absence of doubt: where it's used, whether the changelog between current and target versions (WebFetch the release notes) mentions breaking changes or changed APIs the code touches.
Verdicts:
safe: patch/minor bump, patched version exists, and either transitive or direct with no breaking-change signal touching our usage.needs-human: major bump, no patched release, breaking-change signal, heavy direct usage of changed APIs (expect GitPython and aiohttp to land here often), or any uncertainty.
Phase 3 — Validate safe candidates locally
Work through safe packages sequentially. All commands run from
services/apps/git_integration (Python) or the Go module dir (Go).
Existing Dependabot PRs: validate them together, not one image build per
PR. Create a throwaway local branch off latest main, then for each safe
candidate PR merge its head into it
(git fetch origin <headRefName> && git merge FETCH_HEAD --no-edit) —
Dependabot branches are often weeks behind and validating the stale base
tests the wrong code; the combined merge also catches cross-bump conflicts
that per-PR validation misses. On merge conflict, don't write the PR off:
drop it from the combined branch and validate it individually against main
instead — two pip PRs both rewrite uv.lock, so a conflict between PRs is
expected and meaningless; only a PR that also conflicts with main alone is
needs-human ("PR needs rebase"). Run the full validation suite once on the
combined state; if it fails, bisect by re-validating PRs individually (still
merged with main). Then return to the original branch and delete the
throwaway branch. Never push local commits to any PR branch — local merges
are validation-only.
A combined green validates the PRs as a set. That is what the Slack message must present: "safe to merge together". If reviewers may cherry-pick only some of them, either validate those individually first or say in the message that the batch was validated jointly.
For PRs that end up in the safe tier: re-check the PR is still open and its
head is still the revision that was validated
(gh pr view <n> --json state,headRefOid — a teammate may have merged it,
or Dependabot may have force-pushed a new revision mid-run; if the head SHA
changed, revalidate before calling it safe). Then make it one click from
merge by updating the branch server-side:
gh api -X PUT repos/linuxfoundation/crowd.dev/pulls/<n>/update-branch -f expected_head_sha=<validated sha>
(equivalent to the "Update branch" button; a 422 "head ref does not exist"
usually means the PR was just merged, and a 422 mentioning the expected head
SHA means the head moved — re-check state and revalidate). The update creates
a new head: a merge of the validated SHA with main. That is the same state
validated locally only if main has not advanced since the validation
fetch — record main's SHA when creating the throwaway branch, then right
before calling update-branch run git fetch origin main (fetching only the
PR head does not refresh origin/main, and without this fetch the
comparison always trivially matches) and compare the recorded SHA against
the fresh origin/main; if main moved, re-merge and revalidate first. The Slack message must reference the
validated head SHA and note that the branch was then updated with that same
main.
No PR (typical for transitive pip deps — Dependabot often only alerts): apply the fix on a new branch off main:
pip transitive:
uv lock --upgrade-package '<pkg>==<target>'— pin to the reviewed target; classification (changelog review) only covered versions up to it.pip direct: bump the constraint in
pyproject.toml(uv add '<pkg>>=<target>'), thenuv lock.go (no host toolchain — run in the image the Dockerfile pins):
docker run --rm -v "$PWD":/w -w /w golang:1.25-alpine \ sh -c 'go get <pkg>@v<target> && go mod tidy'
Verify the resolved version in the lockfile/go.mod is exactly the reviewed target — if the resolver landed on anything newer, re-run Phase 2 classification against that version before treating the fix as safe.
Validation suite
Python:
uv sync --frozen --group dev(frozen so validation never rewrites the branch's committeduv.lock— an inconsistent lockfile must fail, not be silently regenerated before the image build),uv run ruff check src/, anduv run pytest src/test/ -v— skip/ignore tests that require live repos, credentials, or network (anything readingTEST_REPO_NAME/env creds); collect-only first (--collect-only) to see what's runnable. Add--ignore=<path>for every untracked file undersrc/test/(fromgit status --porcelain) — local WIP tests can import code that doesn't exist on the branch under validation and fail collection spuriously.Container build (validates Go and the shipped Python env on the real toolchain): from
services/apps/git_integration, rundocker compose -f ../../../scripts/services/git-integration.yaml build git-integration-dev(
make rebuild). The Dockerfile compiles both Go modules from the bumped go.mod/go.sum and doesuv sync --frozenfrom the bumped uv.lock — a failed build fails the package. This is the only Go gate; Go-only bumps don't need the host pytest suite, but always run the container build even for pip-only bumps.
On failure: for a local fix, revert that package's changes
(git checkout -- . scoped to the touched files) and mark it
failed-validation; for a PR checkout, just mark it. Continue the batch —
never let one failure abort the rest, never leave a half-applied fix in the
tree.
Ship local fixes: one branch per run, chore/git-integration-vuln-bumps
(suffix with date if taken), one commit per package following the repo's
commit workflow — git commit --signoff -S (DCO + signing are required, the
Probot DCO check blocks unsigned commits) with message
chore(deps): bump <pkg> from <old> to <new> in git_integration (CM-XXX).
Ask the user for the JIRA key — the commit workflow requires a CM ticket
even for untracked work, so if none exists ask the user to create one. With
a key, open the PR titled
chore(deps): git_integration vulnerability bumps (CM-XXX). Without one
(user declines, or non-interactive run), push the branch but do not open
the PR — report it as "branch pushed, PR pending JIRA ticket"; never open a
draft to sidestep the title lint. The PR body lists GHSA/CVE per package and the
validation evidence. Do not enable auto-merge.
Phase 4 — Slack review summary
Load Slack tooling with ToolSearch (select:mcp__claude_ai_Slack__slack_send_message).
Show the drafted message in the terminal, get user approval, then send to the
channel chosen in Phase 0. Never send without that approval, and if a
summary covering substantially the same findings was already posted (a
re-run, or iterating on the skill), don't post again — terminal report only.
Before listing a PR as safe to merge, re-confirm it is still open. Structure:
- Header: git_integration vuln triage, date, alert counts by severity.
- Safe to merge — each validated Dependabot PR: PR link, package old→new, GHSA/CVE + severity, one-line evidence (validation green, transitive/patch-only, usage summary).
- New PR opened — link to the batch PR from Phase 3, same detail.
- Needs human — package, severity, why (major bump / breaking signal / failed validation / no patched release).
- Footer: out-of-scope alert count remaining on other manifests.
Phase 5 — Deploy (optional, gated)
Only if the user, after merges happened (they merge, not this skill), asks to
deploy: gh workflow run lf-oracle-staging-deploy.yaml -f services="git-integration".
Production (lf-oracle-production-deploy.yaml) only on a second explicit
confirmation after staging is verified.
Final report (terminal)
| GHSA/CVE | Package | Manifest | Old → New | Verdict | Action | Validation |
|---|
Plus: Slack message link/status, PR opened (if any), needs-human reasoning, and the count of out-of-scope alerts left on other manifests.