.agents/skills/triage-dependabot-alerts/SKILL.md
On-demand triage of Dependabot dependency alerts for activepieces/activepieces. Produces a
review-ready report per affected package, and proposes version-bump fixes that are proven
non-breaking before any PR. The user reviews and decides per package: approve the bump /
dismiss / escalate.
For human-reported vulnerabilities (Security tab advisories), use the
triage-security-advisories skill. The two share the .security-triage/ workspace. Dependency
CVEs are already public, so there is no scope-check against SECURITY.md and fixes may use normal
PRs (not the private-fork flow).
Write all artifacts to .security-triage/ (gitignored). Dependabot data is less sensitive than
private reports, but keep the workspace consistent and never commit triage artifacts.
mkdir -p .security-triage
gh api -H "Accept: application/vnd.github+json" "/repos/activepieces/activepieces/dependabot/alerts?state=open" --paginate > .security-triage/dependabot.json
gh api --paginate emits one concatenated array per page — always merge:
jq -s 'add' .security-triage/dependabot.json > .tmp && mv .tmp .security-triage/dependabot.json
Then validate the result is real data, not an error object. A 404/403 body is
{message,documentation_url,status}, and jq 'length' on it returns 3 — which looks like
"3 alerts". Guard explicitly:
jq -e 'type=="array"' .security-triage/dependabot.json >/dev/null \
|| echo "Not an array — Dependabot fetch failed (likely token scope). Stopping."
If it is not an array, the fetch failed — almost always token scope. Don't pre-gate on a scope
check: repo scope alone returned the full backlog in practice, so just try the fetch first.
Only if it 403/404s or returns a non-array does the token likely need security_events — add it
with gh auth refresh -s security_events (web-OAuth gho_ login) or mint a new classic token with
repo + security_events and gh auth login --with-token (PAT, since refresh can't add scopes to
a PAT) — then retry. Stop until the fetch returns an array.
A large backlog (100s of alerts) collapses to a small set of distinct vulnerabilities — the same CVE is reported once per manifest that declares the package, and a monorepo with a single hoisted lockfile installs one copy. Triage the distinct set, not the raw alerts. Dedupe first:
# distinct (package, advisory) — the real triage unit
jq -r 'group_by(.security_advisory.ghsa_id + "|" + .dependency.package.name)
| map({pkg:.[0].dependency.package.name, sev:.[0].security_advisory.severity,
ghsa:.[0].security_advisory.ghsa_id, n:length}) | .[]
| "\(.sev)\t\(.pkg)\t\(.ghsa)\tx\(.n)"' .security-triage/dependabot.json | sort
By default focus on critical + high (ask the user if they want medium/low too — a big backlog is rarely worth triaging end-to-end). Then group by package for the rest of the workflow: one bump usually clears every alert for a package across all manifests.
Read the full vulnerable-range set per advisory, not just the first. Each advisory's
security_advisory.vulnerabilities[] has one entry per affected major line (e.g. form-data
patches <2.5.6 and >=4.0.0,<4.0.6; undici patches separate 6.x / 7.x / 8.x lines; a <6.27
range has no lower bound and also covers 5.x). A table built from vulnerabilities[0] silently
understates exposure — pull them all:
jq -r '.[] | select(.security_advisory.ghsa_id=="<GHSA>")
| .security_advisory.vulnerabilities[]
| " range=\(.vulnerable_version_range) patched=\(.first_patched_version.identifier // "none")"' \
.security-triage/dependabot.json | sort -u
Census the lockfile in the main thread — do not trust subagent recall. Before spawning subagents, list every installed copy of each package straight from the lockfile. This is authoritative; subagents routinely under-count duplicate transitive copies (in practice two subagents reported a single copy where the lockfile held three):
grep -oE '"<pkg>@[0-9][^"]*"' bun.lock | sort -u # every installed version, incl. transitive dupes
Spawn one subagent per package (or per distinct advisory) — never one per raw alert. Each confirms the alert is real for THIS repo:
node_modules/** paths when grepping manifests.form-data 4.0.4 clears <4.0.4 but is still vulnerable to the <4.0.6 advisory). LLM guesses
about installed versions are unreliable; always check the lockfile.Detect the package manager from the repo (packageManager field + which lockfile exists:
bun.lock → bun, package-lock.json → npm, pnpm-lock.yaml → pnpm, yarn.lock → yarn). This
repo is bun. Use the matching tooling in Step 4.
Emit a verdict per package/advisory: AFFECTED (vulnerable path used) / NOT_AFFECTED (dep
present but vulnerable API unused) / NO_FIX_YET (no patched version anywhere) / DEV_ONLY
(build/test-time only).
Write a review-ready markdown file per affected package under
.security-triage/reports/dependabot-<package>.md (not one per alert — that does not scale).
Each lists: the package's advisories + vulnerable ranges, installed version(s) from the lockfile,
first patched version(s), usage verdict + the call sites, severity, the constituent alert IDs,
and a recommendation.
Then write the consolidated .security-triage/DEPENDABOT-TRIAGE-SUMMARY.md (the review
artifact — a distinct name from the advisories skill's TRIAGE-SUMMARY.md, which shares this
workspace; do not clobber it):
verdict tally (AFFECTED / NOT_AFFECTED / NO_FIX_YET / DEV_ONLY), a table grouped by severity +
verdict (package, severity, installed → fix, verdict, reachability, alert count), and any
shared-dependency patterns (one bump resolves several alerts, or several alerts share a
manifest).
Present the headline verdicts in chat, most attacker-reachable first. Surface DEV_ONLY
findings in a separate group — a critical CVSS in a dev-only test runner should not bury an
attacker-reachable high in production code.
After the user picks which packages to fix:
Single lockfile → one batched PR. In a monorepo with one shared lockfile (bun/npm/pnpm), per-package branches all conflict on the lockfile. Bump all approved packages in one isolated worktree and open one PR — never a worktree/PR per package.
bun install). Direct deps here are usually exact-pinned, so
bump them in every manifest that declares them — a find … -not -path '*/node_modules/*' -exec sed -i 's/"<pkg>": "<old>"/"<pkg>": "<new>"/' sweep is appropriate for an exact pin.resolutions/overrides entry
(e.g. "axios": "1.16.0"). bun honours only FLAT keys — scoped keys like
"superagent/form-data": "x" are silently ignored, so a flat pin forces every copy.form-data 2.x in superagent,
undici 5.x in a vendor client) → do NOT force it. The repo's tests don't exercise that
SDK's code path, so a forced cross-major bump cannot be proven non-breaking. Leave it and
document it as a residual in the package report + PR body (which copy, which advisory, why
not forced). Fixing the attacker-reachable direct deps is the bar; transitive SDK-internal
copies wait for the SDK to update.
Confirm the final tree with the census grep.samlify 2.10→2.13, a
minor, broke tsc by narrowing a parameter type). Flag majors as highest-risk, but never skip
the build on a "low-risk" bump.npx turbo run build lint test --filter=<pkg> … (one --filter per affected package).packages/core/shared,
packages/server/utils) trips the repo's version-bump rule — patch-bump those packages too
(once per branch, not per edit).NO_FIX_YET packages get no bump PR. Instead produce a risk-acceptance / mitigation writeup in
the package report: where it's reachable, any existing sandboxing, and the options (accept
residual risk with justification, patch locally via patch-package/bun patch, or swap the
library). If the only available fix is an external source (e.g. SheetJS off npm → CDN tarball),
flag the self-hosting rule — an install that depends on a third-party CDN at build time may
break zero-setup self-hosting; call it out for a maintainer decision.
Dependency CVEs are public, so a normal PR is fine (no private-fork flow). Patch-bump scope — do not bundle unrelated upgrades.
Everything lands in .security-triage/ (gitignored): dependabot.json, DEPENDABOT-TRIAGE-SUMMARY.md, and
one report per affected package under reports/.