Skip to content

Commit 8446cff

Browse files
SONARKT-600 Rule S6474: Using remote artifacts without authenticity and integrity checks is security-sensitive
1 parent 28aa6c7 commit 8446cff

File tree

10 files changed

+345
-15
lines changed

10 files changed

+345
-15
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"kotlin-kotlin-language-server-project": [
3+
0,
4+
0
5+
]
6+
}

sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/KotlinGradleCheckList.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import org.sonarsource.kotlin.gradle.checks.RootProjectNamePresentCheck
2222
import org.sonarsource.kotlin.gradle.checks.DependencyGroupedCheck
2323
import org.sonarsource.kotlin.gradle.checks.DependencyVersionHardcodedCheck
2424
import org.sonarsource.kotlin.gradle.checks.MissingSettingsCheck
25+
import org.sonarsource.kotlin.gradle.checks.MissingVerificationMetadataCheck
2526
import org.sonarsource.kotlin.gradle.checks.TaskDefinitionsCheck
2627
import org.sonarsource.kotlin.gradle.checks.TaskRegisterVsCreateCheck
2728

@@ -32,6 +33,7 @@ val KOTLIN_GRADLE_CHECKS: List<Class<out KotlinCheck>> = listOf(
3233
DependencyGroupedCheck::class.java,
3334
DependencyVersionHardcodedCheck::class.java,
3435
MissingSettingsCheck::class.java,
36+
MissingVerificationMetadataCheck::class.java,
3537
TaskDefinitionsCheck::class.java,
3638
TaskRegisterVsCreateCheck::class.java,
3739
)

sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/KotlinGradleSensor.kt

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import java.io.File
3333

3434
const val GRADLE_PROJECT_ROOT_PROPERTY = "sonar.kotlin.gradleProjectRoot"
3535
const val MISSING_SETTINGS_RULE_KEY = "S6631"
36+
const val MISSING_VERIFICATION_METADATA_RULE_KEY = "S6474"
3637

3738
private val LOG = LoggerFactory.getLogger(KotlinGradleSensor::class.java)
3839

@@ -72,6 +73,7 @@ class KotlinGradleSensor(
7273

7374
sensorContext.config()[GRADLE_PROJECT_ROOT_PROPERTY].ifPresent {
7475
checkForMissingGradleSettings(File(it), sensorContext)
76+
checkForMissingVerificationMetadata(File(it), sensorContext)
7577
}
7678

7779
return fileSystem.inputFiles(mainFilePredicate)
@@ -82,19 +84,37 @@ class KotlinGradleSensor(
8284
if (sensorContext.activeRules().find(missingSettingsRuleKey) == null) return
8385

8486
if (!rootDirFile.resolve("settings.gradle").exists() && !rootDirFile.resolve("settings.gradle.kts").exists()) {
85-
val project = sensorContext.project()
87+
raiseProjectLevelIssue(
88+
sensorContext,
89+
missingSettingsRuleKey,
90+
"""Add a missing "settings.gradle" or "settings.gradle.kts" file.""",
91+
)
92+
}
93+
}
94+
95+
private fun checkForMissingVerificationMetadata(rootDirFile: File, sensorContext: SensorContext) {
96+
val missingVerificationMetadataRuleKey = RuleKey.of(KOTLIN_REPOSITORY_KEY, MISSING_VERIFICATION_METADATA_RULE_KEY)
97+
if (sensorContext.activeRules().find(missingVerificationMetadataRuleKey) == null) return
8698

87-
with(sensorContext) {
88-
newIssue()
89-
.forRule(missingSettingsRuleKey)
90-
.at(
91-
newIssue()
92-
.newLocation()
93-
.on(project)
94-
.message("""Add a missing "settings.gradle" or "settings.gradle.kts" file.""")
95-
)
96-
.save()
97-
}
99+
if (!rootDirFile.resolve("gradle/verification-metadata.xml").exists()) {
100+
raiseProjectLevelIssue(
101+
sensorContext,
102+
missingVerificationMetadataRuleKey,
103+
"""Dependencies are not verified because the "verification-metadata.xml" file is missing. Make sure it is safe here.""",
104+
)
98105
}
99106
}
107+
108+
private fun raiseProjectLevelIssue(sensorContext: SensorContext, ruleKey: RuleKey, message: String) =
109+
with(sensorContext) {
110+
newIssue()
111+
.forRule(ruleKey)
112+
.at(
113+
newIssue()
114+
.newLocation()
115+
.on(project())
116+
.message(message)
117+
)
118+
.save()
119+
}
100120
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* SonarSource Kotlin
3+
* Copyright (C) 2018-2025 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonarsource.kotlin.gradle.checks
18+
19+
import org.jetbrains.kotlin.psi.KtCallExpression
20+
import org.sonar.check.Rule
21+
import org.sonarsource.kotlin.api.checks.AbstractCheck
22+
import org.sonarsource.kotlin.api.frontend.KotlinFileContext
23+
24+
private const val disableVerificationMetadata = "disableDependencyVerification"
25+
private const val message = """This call disables dependencies verification. Make sure it is safe here."""
26+
27+
// The part of the rule looking for missing verification-metadata.xml is implemented in the KotlinGradleSensor
28+
@Rule(key = "S6474")
29+
class MissingVerificationMetadataCheck : AbstractCheck() {
30+
override fun visitCallExpression(expression: KtCallExpression, kotlinFileContext: KotlinFileContext) {
31+
if (getFunctionName(expression) == disableVerificationMetadata) {
32+
kotlinFileContext.reportIssue(expression, message)
33+
}
34+
}
35+
}
36+

sonar-kotlin-gradle/src/test/java/org/sonarsource/kotlin/gradle/KotlinGradleSensorTest.kt

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import org.sonar.check.Rule
3030
import org.sonarsource.kotlin.api.checks.AbstractCheck
3131
import org.sonarsource.kotlin.api.frontend.KotlinFileContext
3232
import org.sonarsource.kotlin.gradle.checks.MissingSettingsCheck
33+
import org.sonarsource.kotlin.gradle.checks.MissingVerificationMetadataCheck
3334
import org.sonarsource.kotlin.testapi.AbstractSensorTest
3435
import kotlin.io.path.createFile
3536

@@ -80,7 +81,7 @@ internal class KotlinGraldeSensorTest : AbstractSensorTest() {
8081

8182
addBuildFile()
8283

83-
val checkFactory = checkFactory("S6631")
84+
val checkFactory = checkFactory(MISSING_SETTINGS_RULE_KEY)
8485
sensor(checkFactory).execute(context)
8586
val issues = context.allIssues()
8687

@@ -103,7 +104,7 @@ internal class KotlinGraldeSensorTest : AbstractSensorTest() {
103104
addSettingsFile()
104105
addBuildFile()
105106

106-
val checkFactory = checkFactory("S6631")
107+
val checkFactory = checkFactory(MISSING_SETTINGS_RULE_KEY)
107108
sensor(checkFactory).execute(context)
108109
val issues = context.allIssues()
109110

@@ -122,7 +123,7 @@ internal class KotlinGraldeSensorTest : AbstractSensorTest() {
122123
addSettingsKtsFile()
123124
addBuildFile()
124125

125-
val checkFactory = checkFactory("S6631")
126+
val checkFactory = checkFactory(MISSING_SETTINGS_RULE_KEY)
126127
sensor(checkFactory).execute(context)
127128
val issues = context.allIssues()
128129

@@ -147,6 +148,75 @@ internal class KotlinGraldeSensorTest : AbstractSensorTest() {
147148
assertThat(issues).isEmpty()
148149
}
149150

151+
@Test
152+
fun test_missing_verification_metadata_rule_is_triggered_when_verification_metadata_is_not_present() {
153+
mockkStatic("org.sonarsource.kotlin.gradle.KotlinGradleCheckListKt")
154+
every { KOTLIN_GRADLE_CHECKS } returns listOf(MissingVerificationMetadataCheck::class.java)
155+
156+
val settings = MapSettings()
157+
settings.setProperty(GRADLE_PROJECT_ROOT_PROPERTY, baseDir.toRealPath().toString())
158+
context.setSettings(settings)
159+
160+
val checkFactory = checkFactory(MISSING_VERIFICATION_METADATA_RULE_KEY)
161+
sensor(checkFactory).execute(context)
162+
val issues = context.allIssues()
163+
164+
assertThat(issues).hasSize(1)
165+
val issue = issues.iterator().next()
166+
assertThat(issue.primaryLocation().inputComponent().key()).isEqualTo("projectKey")
167+
val expectedMessage = """Dependencies are not verified because the "verification-metadata.xml" file is missing. Make sure it is safe here."""
168+
assertThat(issue.primaryLocation().message()).isEqualTo(expectedMessage)
169+
}
170+
171+
@Test
172+
fun test_missing_verification_metadata_rule_is_not_triggered_when_verification_metadata_is_present() {
173+
mockkStatic("org.sonarsource.kotlin.gradle.KotlinGradleCheckListKt")
174+
every { KOTLIN_GRADLE_CHECKS } returns listOf(MissingVerificationMetadataCheck::class.java)
175+
176+
val settings = MapSettings()
177+
settings.setProperty(GRADLE_PROJECT_ROOT_PROPERTY, baseDir.toRealPath().toString())
178+
context.setSettings(settings)
179+
180+
addVerificationMetadataFile()
181+
182+
val checkFactory = checkFactory(MISSING_VERIFICATION_METADATA_RULE_KEY)
183+
sensor(checkFactory).execute(context)
184+
185+
assertThat(context.allIssues()).isEmpty()
186+
}
187+
188+
@Test
189+
fun test_missing_verification_metadata_rule_is_not_triggered_when_rule_is_not_active() {
190+
mockkStatic("org.sonarsource.kotlin.gradle.KotlinGradleCheckListKt")
191+
every { KOTLIN_GRADLE_CHECKS } returns listOf(MissingVerificationMetadataCheck::class.java)
192+
193+
val settings = MapSettings()
194+
settings.setProperty(GRADLE_PROJECT_ROOT_PROPERTY, baseDir.toRealPath().toString())
195+
context.setSettings(settings)
196+
197+
addSettingsKtsFile()
198+
addBuildFile()
199+
200+
val checkFactory = checkFactory() // No rule key
201+
sensor(checkFactory).execute(context)
202+
203+
assertThat(context.allIssues()).isEmpty()
204+
}
205+
206+
@Test
207+
fun test_missing_verification_metadata_rule_is_not_triggereD_when_gradle_project_root_property_is_not_set() {
208+
mockkStatic("org.sonarsource.kotlin.gradle.KotlinGradleCheckListKt")
209+
every { KOTLIN_GRADLE_CHECKS } returns listOf(MissingVerificationMetadataCheck::class.java)
210+
211+
val settings = MapSettings()
212+
context.setSettings(settings)
213+
214+
val checkFactory = checkFactory(MISSING_VERIFICATION_METADATA_RULE_KEY)
215+
sensor(checkFactory).execute(context)
216+
217+
assertThat(context.allIssues()).isEmpty()
218+
}
219+
150220
private fun addBuildFile() {
151221
val buildFile = createInputFile(
152222
"build.gradle.kts", """
@@ -195,6 +265,29 @@ internal class KotlinGraldeSensorTest : AbstractSensorTest() {
195265
context.fileSystem().add(settingsFile)
196266
}
197267

268+
private fun addVerificationMetadataFile() {
269+
val verificationMetadataFile = createInputFile(
270+
"verification-metadata.xml", """
271+
<?xml version="1.0" encoding="UTF-8"?>
272+
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.3.xsd">
273+
<configuration>
274+
<verify-metadata>false</verify-metadata>
275+
<verify-signatures>false</verify-signatures>
276+
</configuration>
277+
<components>
278+
<component group="ch.qos.logback" name="logback-classic" version="1.2.9">
279+
<artifact name="logback-classic-1.2.9.jar">
280+
<sha256 value="ad745cc243805800d1ebbf5b7deba03b37c95885e6bce71335a73f7d6d0f14ee" origin="Verified"/>
281+
</artifact>
282+
</component>
283+
</components>
284+
</verification-metadata>
285+
""".trimIndent())
286+
baseDir.resolve("gradle").toFile().mkdir()
287+
baseDir.resolve("gradle/verification-metadata.xml").createFile()
288+
context.fileSystem().add(verificationMetadataFile)
289+
}
290+
198291
private fun sensor(checkFactory: CheckFactory): KotlinGradleSensor {
199292
return KotlinGradleSensor(checkFactory, language())
200293
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* SonarSource Kotlin
3+
* Copyright (C) 2018-2025 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonarsource.kotlin.gradle.checks
18+
19+
internal class MissingVerificationMetadataCheckTest : CheckTest(MissingVerificationMetadataCheck())
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
plugins {
2+
id("com.android.application")
3+
}
4+
5+
configurations {
6+
named("jetbrainsRuntimeLocalInstance") {
7+
resolutionStrategy.disableDependencyVerification() // Noncompliant {{This call disables dependencies verification. Make sure it is safe here.}}
8+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9+
}
10+
named("jetbrainsRuntimeDependency") {
11+
resolutionStrategy {
12+
disableDependencyVerification() // Noncompliant
13+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
}
15+
}
16+
named("jetbrainsRuntime") {
17+
resolutionStrategy {
18+
this.disableDependencyVerification() // Noncompliant
19+
}
20+
}
21+
}
22+
23+
detachedConfig.resolutionStrategy.disableDependencyVerification() // Noncompliant
24+
25+
if (name.contains("detached")) {
26+
disableDependencyVerification() // Noncompliant
27+
}
28+
29+
someGroup {
30+
tasks.register("checkDetachedDependencies") {
31+
val detachedConf: FileCollection = configurations.detachedConfiguration(dependencies.create("org.apache.commons:commons-lang3:3.3.1")).apply {
32+
resolutionStrategy.disableDependencyVerification() // Noncompliant
33+
}
34+
doLast {
35+
println(detachedConf.files)
36+
}
37+
}
38+
39+
disableDependencyVerification { // Noncompliant
40+
}
41+
}

0 commit comments

Comments
 (0)