Add Auto-PR Command - #1379
Conversation
- New `autofix` package: SBOM-based descriptor locator, dependency updater via jfrog-cli-security package updaters, git branch/commit/ push via frogbot GitManager, VCS-agnostic PR creation via vcsclient - Supports Maven, Npm, Pnpm, Go, Pip; technology inferred from PURL - New `autofix/action.yml` subdirectory action (`uses: jfrog/frogbot/autofix@v3`) with dedicated inputs: component-name, affected-version, fix-version, branch-name, commit-hash - New `.github/workflows/frogbot-auto-fix.yml` workflow_dispatch template - Registered `auto-fix` (alias `af`) command in commands.go - action/src/main.ts routes to execAutoFix() when command input is auto-fix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rd the workspace - Restore autopr/action.yml so jfrog/frogbot/autopr@v3 resolves again and routes to execAutoPr through the auto-pr command default, and add a CI smoke job that invokes the action by local path so a missing manifest fails the build. - Resolve branch-name to the repository default branch in the workflow, matching the documented contract instead of landing in detached HEAD. - Refuse to run auto-pr on a dirty worktree, and scope untracked-file cleanup to files that appeared after the updater ran, so a reused or local workspace never loses developer work. - Fail rather than warn when cleanup fails, so a partially cleaned workspace is not swept into the fix commit. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…coverage Use the root action.yml for Auto-PR inputs, reuse security-cli BOM and PyPI helpers, split Run into focused steps, skip expected Auto-PR cases from error telemetry, and cover the success path plus not-found and transitive branches. Co-authored-by: Cursor <cursoragent@cursor.com>
…ace safety - initialize the auto-pr git manager with the repository remote URL so remote branch checks and pushes work - tolerate a missing config profile instead of panicking on env-var-only runs - commit only tracked changes so files created during a run are never deleted or swept into the fix commit - treat transitive dependencies as expected skips rather than telemetry errors - pass only descriptor paths proven direct when a component appears both directly and transitively - post pull request body overflow as follow-up comments - apply PyPI and Maven name normalization only to their own ecosystems
| func (a *AutoPrCmd) applyFixAndCreatePullRequest(run autoPrRun, repository utils.Repository, client vcsclient.VcsClient, gitManager autoPrGitManager) error { | ||
| if err := gitManager.CreateBranchAndCheckout(run.fixBranchName, false); err != nil { | ||
| return fmt.Errorf("failed to create fix branch '%s': %w", run.fixBranchName, err) | ||
| } | ||
| update := a.runUpdater | ||
| if update == nil { | ||
| update = runUpdater | ||
| } | ||
| if err := update(run.componentName, run.affectedVersion, run.fixVersion, run.tech, true, run.descriptorPaths); err != nil { | ||
| return err | ||
| } | ||
| commitMessage := gitManager.GenerateCommitMessage(run.componentName, run.fixVersion) | ||
| if err := gitManager.AddTrackedAndCommit(commitMessage, run.componentName); err != nil { | ||
| var errNoChanges *utils.ErrNothingToCommit | ||
| if errors.As(err, &errNoChanges) { | ||
| log.Info(err.Error()) | ||
| return &ErrAutoPrSkipped{Reason: err.Error()} | ||
| } | ||
| return fmt.Errorf("failed to commit changes: %w", err) | ||
| } |
There was a problem hiding this comment.
Important: After CreateBranchAndCheckout there is no restore on updater/commit/push failure.
The caller is left on a local fix branch with tracked modifications (and any untracked updater/SBOM leftovers). A later retry then fails the clean-worktree check.
Please restore the original branch and discard Auto-PR mutations on those error paths (or run in a disposable worktree/clone). Success can keep the new branch; failure should not.
| func (gm *GitManager) AddTrackedAndCommit(commitMessage, impactedDependencyName string) error { | ||
| worktree, err := gm.localGitRepository.Worktree() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| status, err := worktree.Status() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| hasTrackedChanges := false | ||
| for fileName, fileStatus := range status { | ||
| if fileStatus.Worktree == git.Untracked || fileStatus.Staging == git.Added { | ||
| continue | ||
| } | ||
| if _, err = worktree.Add(fileName); err != nil { | ||
| return err | ||
| } | ||
| hasTrackedChanges = true | ||
| } | ||
| if !hasTrackedChanges { | ||
| return &ErrNothingToCommit{PackageName: impactedDependencyName} | ||
| } | ||
| return gm.commit(commitMessage) | ||
| } | ||
|
|
There was a problem hiding this comment.
Important: AddTrackedAndCommit correctly avoids committing untracked files, but those files are never cleaned up.
SBOM/plugin/updater leftovers remain after both success and failure, so the worktree is dirty and the next auto-pr run is rejected.
Either isolate the run in a disposable worktree, or restore the exact pre-run state (tracked + untracked) without deleting files that existed before the command started.
| } | ||
|
|
||
| func (a *AutoPrCmd) applyFixAndCreatePullRequest(run autoPrRun, repository utils.Repository, client vcsclient.VcsClient, gitManager autoPrGitManager) error { | ||
| if err := gitManager.CreateBranchAndCheckout(run.fixBranchName, false); err != nil { |
There was a problem hiding this comment.
Important: The fix branch is created from the current HEAD, not from run.baseBranch / commit-hash.
JF_COMMIT_HASH is only appended to the PR body; nothing checks out that commit. JF_GIT_BASE_BRANCH is used as the PR target, not as the git parent. A clean checkout on the wrong branch (or a SHA that does not match the finding) produces a PR with unrelated commits.
Please branch from the requested base (and check out commit-hash when it is set), or fail if HEAD is not that revision.
| if match.purlType == "" { | ||
| match.purlType = compType | ||
| } | ||
| relation := bomIndex.GetComponentRelation(component.BOMRef) | ||
| isDirect := relation == cdxutils.RootRelation || relation == cdxutils.DirectRelation | ||
| if isDirect { | ||
| match.isDirectFromRoot = true | ||
| } | ||
|
|
||
| for _, location := range results.CdxEvidencesToLocations(component) { | ||
| if location.File == "" { | ||
| continue | ||
| } | ||
| if isDirect { | ||
| if directSeen[location.File] { | ||
| continue | ||
| } | ||
| directSeen[location.File] = true | ||
| directPaths = append(directPaths, location.File) | ||
| } else { | ||
| if transitiveSeen[location.File] { | ||
| continue | ||
| } | ||
| transitiveSeen[location.File] = true | ||
| transitivePaths = append(transitivePaths, location.File) | ||
| } | ||
| log.Debug(fmt.Sprintf("Found descriptor: %s", location.File)) | ||
| } | ||
| } | ||
| match.descriptorPaths = directPaths | ||
| if !match.isDirectFromRoot { | ||
| match.descriptorPaths = transitivePaths | ||
| } |
There was a problem hiding this comment.
Important: Same bare name+version across ecosystems can still be merged.
purlType is taken from the first match, while direct descriptor paths are collected from every matching component. In a polyglot repo that can pick the wrong updater.
If more than one PURL type matches, fail with an explicit “ambiguous component” error (or require a typed Xray component ID). A fixture with npm + pypi sharing a name/version would lock this in.
| } | ||
| } | ||
|
|
||
| func TestInitializeGitManager_WithNilConfigProfileAndConfiguredRemote(t *testing.T) { |
There was a problem hiding this comment.
Minor: This only asserts a non-nil GitManager.
It does not check remoteGitUrl / BranchExistsInRemote, so the previous empty-remote regression could come back unnoticed. Please assert the configured clone URL (or a list/push against a real local remote).
Add Auto-PR command
Adds Auto-PR as a third Frogbot command alongside
scan-pull-requestandscan-repository.Given a single vulnerable component, its affected version, and the version to upgrade to, Auto-PR locates the dependency descriptor that declares it, applies the version bump, and opens a fix pull request — without running a full repository scan first.
This makes it possible to drive a targeted fix from anywhere a CVE is already known: a Xray/JFrog Platform finding, a security ticket, or a manual
workflow_dispatch.How it works
GetCompatiblePackageUpdaterfactory fromjfrog-cli-security, so Auto-PR andscan-repositorybump versions through exactly the same updaters.vcsclient, so it works on GitHub, GitLab, Bitbucket, and Azure Repos. The PR body reuses the standard Frogbot fix-PR content.Transitive dependencies are rejected with a clear message, since a manifest edit alone cannot fix them.
Supported package managers
Maven, npm, pnpm, Go, pip, and Docker.
npmvspnpmis disambiguated from workspace markers rather than the PURL, which cannot distinguish them.