Skip to content

Comments

Maven Auto Fix#1064

Open
orto17 wants to merge 5 commits intojfrog:v3_erfrom
orto17:maven-fix
Open

Maven Auto Fix#1064
orto17 wants to merge 5 commits intojfrog:v3_erfrom
orto17:maven-fix

Conversation

@orto17
Copy link
Contributor

@orto17 orto17 commented Feb 18, 2026

  • All tests passed. If this feature is not already covered by the tests, I added new tests.
  • This pull request is on the dev branch.
  • I used gofmt for formatting the code before submitting the pull request.
  • Update documentation about new features / new supported technologies

@orto17 orto17 added new feature Automatically generated release notes safe to test Approve running integration tests on a pull request labels Feb 18, 2026
@github-actions github-actions bot removed the safe to test Approve running integration tests on a pull request label Feb 18, 2026
@orto17 orto17 requested a review from eranturgeman February 18, 2026 10:54
@orto17 orto17 added the safe to test Approve running integration tests on a pull request label Feb 18, 2026
@github-actions github-actions bot removed the safe to test Approve running integration tests on a pull request label Feb 18, 2026
@github-actions
Copy link
Contributor

👍 Frogbot scanned this pull request and did not find any new security issues.


Copy link
Contributor

@attiasas attiasas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!, take a look at my comments.

  1. consider fixing linter issues
  2. consider adding more test cases to expand coverage

)

func TestMavenUpdateRegularDependency(t *testing.T) {
testProjectPath := filepath.Join("..", "testdata", "packagehandlers")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
testProjectPath := filepath.Join("..", "testdata", "packagehandlers")
testProjectPath := filepath.Join("..", "testdata", "packageupdaters")

test fails, you should pass the following.

I also suggest adding sub folder like maven and optionally even more under maven like regular to be prepared for more test cases in future

Comment on lines +154 to +159
if project.Parent.GroupId == groupId && project.Parent.ArtifactId == artifactId {
pattern := regexp.MustCompile(`(?s)(<parent>\s*<groupId>` + regexp.QuoteMeta(groupId) + `</groupId>\s*<artifactId>` + regexp.QuoteMeta(artifactId) + `</artifactId>\s*<version>)[^<]+(</version>)`)
newContent := pattern.ReplaceAll(content, []byte("${1}"+fixedVersion+"${2}"))
if !bytes.Equal(content, newContent) {
log.Debug("Updated parent", toDependencyName(groupId, artifactId), "to", fixedVersion)
return true, newContent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sure this will not fail on pom with vars. $ in a property may cause interaption to the replacer.

try and add test case for a pom similar to:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo-app</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <!-- ===================== -->
    <!-- Version Variables     -->
    <!-- ===================== -->
    <properties>
        <java.version>17</java.version>
        <spring.boot.version>3.2.5</spring.boot.version>
        <junit.version>5.10.2</junit.version>
        <lombok.version>1.18.32</lombok.version>
    </properties>

    <!-- ===================== -->
    <!-- Dependency Management -->
    <!-- ===================== -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!-- ===================== -->
    <!-- Dependencies          -->
    <!-- ===================== -->
    <dependencies>

        <!-- Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- JUnit 5 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <!-- ===================== -->
    <!-- Build Plugins         -->
    <!-- ===================== -->
    <build>
        <plugins>

            <!-- Compiler Plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>

        </plugins>
    </build>

</project>

var pomPaths []string
for _, component := range vulnDetails.Components {
if component.PreferredLocation != nil && component.PreferredLocation.File != "" {
pomPaths = append(pomPaths, component.PreferredLocation.File)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this may cause duplication, maybe use pomPaths := datastructures.MakeSet[string]()?

Comment on lines +65 to 68
pomPaths := m.getVulnerabilityOccurrences(vulnDetails)
if len(pomPaths) == 0 {
return fmt.Errorf("no pom.xml locations found for %s - Components array is empty or missing Location data", vulnDetails.ImpactedDependencyName)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pomPaths := m.getVulnerabilityOccurrences(vulnDetails)
if len(pomPaths) == 0 {
return fmt.Errorf("no pom.xml locations found for %s - Components array is empty or missing Location data", vulnDetails.ImpactedDependencyName)
}
pomPaths := m.getVulnerabilityOccurrences(vulnDetails)
if len(pomPaths) == 0 {
return fmt.Errorf("no pom.xml locations found for %s - Components array is empty or missing Location data", vulnDetails.ImpactedDependencyName)
}
llog.Verbose(fmt.Sprintf("Found vulnerability %s occurrences for component %s in %s", vulnDetails.IssueId, vulnDetails.ImpactedDependencyVersion, strings.Join(pomPaths, ", ")))

Consider adding logs

@eranturgeman eranturgeman added improvement Automatically generated release notes and removed improvement Automatically generated release notes labels Feb 22, 2026
"github.com/jfrog/frogbot/v2/utils"
)

func TestMavenUpdateRegularDependency(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please unify TestMavenUpdateRegularDependency, TestMavenUpdateDependencyManagement, TestMavenUpdatePropertyVersion and TestMavenUpdateParentPOM if possible, the code seems pretty similar to all of them.
Make usre to give a clear name for each test case in the t.Run

assert.NotContains(t, string(modifiedPom), "<version>2.5.0</version>")
}

func TestMavenDependencyNotFound(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please unify with TestMavenIndirectDependencyNotSupported for one test that checks the failing fixes

_, clearMavenDepTreeRun, err = mph.CreateTempDirWithSettingsXmlIfNeeded()
if err != nil {
return
func (m *MavenPackageUpdater) getVulnerabilityOccurrences(vulnDetails *utils.VulnerabilityDetails) []string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this function and dont use preferredLocation in general (this is intended for the CLI not for Frogbot).
please use GetVulnerabilityLocations to get all occurences of descriptors to fix, and then iterate them all and fix each of them

var errors []string
for _, pomPath := range pomPaths {
if err := m.updatePomFile(pomPath, groupId, artifactId, vulnDetails.SuggestedFixedVersion); err != nil {
errors = append(errors, fmt.Sprintf("%s: %v", pomPath, err))
Copy link
Collaborator

@eranturgeman eranturgeman Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
errors = append(errors, fmt.Sprintf("%s: %v", pomPath, err))
failedFixErrorMsg := fmt.Errorf("failed to fix '%s' in descriptor '%s': %w", vulnDetails.ImpactedDependencyName, descriptorPath, fixErr)
err = errors.Join(err, failedFixErrorMsg)

Copy link
Collaborator

@eranturgeman eranturgeman Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also we can add log.Warn(failedFixErrorMsg) to match what we did in Golang and NPM

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create an array called 'failingDescriptors' and accumulate the names of the descriptors that failed so we can indicate them all in the final error - see my error suggestion

IsMavenDepTreeInstalled: true,
var errors []string
for _, pomPath := range pomPaths {
if err := m.updatePomFile(pomPath, groupId, artifactId, vulnDetails.SuggestedFixedVersion); err != nil {
Copy link
Collaborator

@eranturgeman eranturgeman Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if err := m.updatePomFile(pomPath, groupId, artifactId, vulnDetails.SuggestedFixedVersion); err != nil {
if fixErr := m.updatePomFile(pomPath, groupId, artifactId, vulnDetails.SuggestedFixedVersion); fixErr != nil {

depTreeParams := &java.DepTreeParams{
Server: scanDetails.ServerDetails,
IsMavenDepTreeInstalled: true,
var errors []string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove

pomPaths []pomPath
// mavenDepTreeManager handles the installation and execution of the maven-dep-tree to obtain all the project poms and running mvn commands
*java.MavenDepTreeManager
if len(errors) > 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if len(errors) > 0 {
if err != nil

// mavenDepTreeManager handles the installation and execution of the maven-dep-tree to obtain all the project poms and running mvn commands
*java.MavenDepTreeManager
if len(errors) > 0 {
return fmt.Errorf("failed to update pom.xml files:\n%s", strings.Join(errors, "\n"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("failed to update pom.xml files:\n%s", strings.Join(errors, "\n"))
return fmt.Errorf("encountered errors while fixing '%s' vulnerability in descriptors [%s]: %w", vulnDetails.ImpactedDependencyName, strings.Join(failingDescriptors, ", "), err)

content, err := os.ReadFile(pomPath)
if err != nil {
return err
return fmt.Errorf("failed to update Pom file: failed to read %s: %w", pomPath, err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("failed to update Pom file: failed to read %s: %w", pomPath, err)
return fmt.Errorf("failed to read %s: %w", pomPath, err)

we dont need to state here 'failed to update pom' this will be a duplication with the wrapping error

mph.pomDependencies = make(map[string]pomDependencyDetails)
var project mavenProject
if err = xml.Unmarshal(content, &project); err != nil {
return fmt.Errorf("failed to update Pom file: failed to parse %s: %w", pomPath, err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("failed to update Pom file: failed to parse %s: %w", pomPath, err)
return fmt.Errorf("failed to parse %s: %w", pomPath, err)

return err

var updated bool
newContent := content
Copy link
Collaborator

@eranturgeman eranturgeman Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this used for? this is not a copy of the content but rather a copy of the pointer, so we change the actual 'content' anyway. Also - there is no revert to this backup in case of a failure from what I see.

var updated bool
newContent := content

if updated, newContent = m.updateInParent(&project, groupId, artifactId, fixedVersion, newContent); updated {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if removing this 'newContent := content' line make sure to pass 'content' here in the parameters

}

err = mph.getProjectPoms()
func (m *MavenPackageUpdater) updatePomFile(pomPath, groupId, artifactId, fixedVersion string) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is something wrong with the fixing process in general. for each vulnerability we might need to fix it in 3 different places (parent, manegement and regular).
In order to keep the pom.xml valid we have to make sure to update in ALL necessary locations or in none of them.
I suggest the following:
Attempt to make all 3 fixes on the content WITHOUT writing to the file after each time. if all fixes that are needed succeeded we write to the file (and we also save 2 writes to the file). if any of the needed failed - we return an error and we dont even have to restore a backup since we only write to the file at the end

pattern := regexp.MustCompile(`(?s)(<parent>\s*<groupId>` + regexp.QuoteMeta(groupId) + `</groupId>\s*<artifactId>` + regexp.QuoteMeta(artifactId) + `</artifactId>\s*<version>)[^<]+(</version>)`)
newContent := pattern.ReplaceAll(content, []byte("${1}"+fixedVersion+"${2}"))
if !bytes.Equal(content, newContent) {
log.Debug("Updated parent", toDependencyName(groupId, artifactId), "to", fixedVersion)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant log for my opinoin. lets log the final result if we managed to update the dep in all places not in every section. for big projects this will create a massive log

pattern := regexp.MustCompile(`(?s)(<groupId>` + regexp.QuoteMeta(groupId) + `</groupId>\s*<artifactId>` + regexp.QuoteMeta(artifactId) + `</artifactId>\s*<version>)[^<]+(</version>)`)
newContent := pattern.ReplaceAll(content, []byte("${1}"+fixedVersion+"${2}"))
if !bytes.Equal(content, newContent) {
log.Debug("Updated dependency", toDependencyName(groupId, artifactId), "to", fixedVersion)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant log for my opinoin. lets log the final result if we managed to update the dep in all places not in every section. for big projects this will create a massive log

pattern := regexp.MustCompile(`(<` + regexp.QuoteMeta(propertyName) + `>)[^<]+(</` + regexp.QuoteMeta(propertyName) + `>)`)
newContent := pattern.ReplaceAll(content, []byte("${1}"+newValue+"${2}"))
if !bytes.Equal(content, newContent) {
log.Debug("Updated property", propertyName, "to", newValue)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant log for my opinoin. lets log the final result if we managed to update the dep in all places not in every section. for big projects this will create a massive log

Copy link
Collaborator

@eranturgeman eranturgeman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new feature Automatically generated release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants