import org.sonar.xoo.rule.OnePredefinedRuleExternalIssuePerLineSensor;
import org.sonar.xoo.rule.OneQuickFixPerLineSensor;
import org.sonar.xoo.rule.OneVulnerabilityIssuePerProjectSensor;
+import org.sonar.xoo.rule.OneVulnerabilityPerSecurityStandardSensor;
import org.sonar.xoo.rule.RandomAccessSensor;
import org.sonar.xoo.rule.SaveDataTwiceSensor;
import org.sonar.xoo.rule.Xoo2BasicProfile;
OneBugIssuePerLineSensor.class,
OneCodeSmellIssuePerLineSensor.class,
OneVulnerabilityIssuePerProjectSensor.class,
+ OneVulnerabilityPerSecurityStandardSensor.class,
DeprecatedGlobalSensor.class,
GlobalProjectSensor.class,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.xoo.rule;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.rule.ActiveRule;
+import org.sonar.api.batch.rule.ActiveRules;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.xoo.Xoo;
+
+/**
+ * Raise a vulnerability issue per standard that are defined on the line.
+ * Eg. if line contains: owaspAsvs-4.0:1.4.2 will raise an issue with the corresponding standard.
+ */
+public class OneVulnerabilityPerSecurityStandardSensor implements Sensor {
+
+ public static final String RULE_KEY_PREFIX = "OneVulnerabilityPerLine_";
+
+ private final FileSystem fs;
+ private final ActiveRules activeRules;
+
+ public OneVulnerabilityPerSecurityStandardSensor(FileSystem fs, ActiveRules activeRules) {
+ this.fs = fs;
+ this.activeRules = activeRules;
+ }
+
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor
+ .onlyOnLanguage(Xoo.KEY)
+ .createIssuesForRuleRepository(XooRulesDefinition.XOO_REPOSITORY);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ doAnalyse(context, Xoo.KEY);
+ }
+
+ private void doAnalyse(SensorContext context, String languageKey) {
+ String rulePrefix = languageKey + ":" + RULE_KEY_PREFIX;
+ Map<String, RuleKey> rulesBySecurityStandard = activeRules.findAll().stream()
+ .filter(r -> r.ruleKey().toString().startsWith(rulePrefix))
+ .collect(Collectors.toMap(c -> c.ruleKey().toString().substring(rulePrefix.length()), ActiveRule::ruleKey));
+
+ for (InputFile inputFile : fs.inputFiles(fs.predicates().hasLanguage(languageKey))) {
+ for (String securityStandard : rulesBySecurityStandard.keySet()) {
+ processFile(inputFile, context, rulesBySecurityStandard.get(securityStandard), securityStandard);
+ }
+ }
+ }
+
+
+ protected void processFile(InputFile inputFile, SensorContext context, RuleKey ruleKey, String securityStandard) {
+ try {
+ int[] lineCounter = {1};
+ try (InputStreamReader isr = new InputStreamReader(inputFile.inputStream(), inputFile.charset());
+ BufferedReader reader = new BufferedReader(isr)) {
+ reader.lines().forEachOrdered(lineStr -> {
+
+ if (lineStr.contains(securityStandard)) {
+ NewIssue newIssue = context.newIssue();
+ newIssue
+ .forRule(ruleKey)
+ .at(newIssue.newLocation()
+ .on(inputFile)
+ .at(inputFile.newRange(lineCounter[0], lineStr.indexOf(securityStandard), lineCounter[0], lineStr.indexOf(securityStandard) + securityStandard.length())))
+ .save();
+ }
+ lineCounter[0]++;
+ });
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to process " + inputFile, e);
+ }
+ }
+
+}
import javax.annotation.Nullable;
import org.sonar.api.SonarRuntime;
+import org.sonar.api.config.Configuration;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleScope;
import org.sonar.xoo.Xoo;
import org.sonar.xoo.Xoo2;
import org.sonar.xoo.checks.Check;
-import org.sonar.xoo.rule.variant.HotspotWithCodeVariantsSensor;
import org.sonar.xoo.rule.hotspot.HotspotWithContextsSensor;
import org.sonar.xoo.rule.hotspot.HotspotWithSingleContextSensor;
import org.sonar.xoo.rule.hotspot.HotspotWithoutContextSensor;
+import org.sonar.xoo.rule.variant.HotspotWithCodeVariantsSensor;
import org.sonar.xoo.rule.variant.IssueWithCodeVariantsSensor;
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY;
@Nullable
private final Version version;
+ @Nullable
+ private final Configuration configuration;
+
public XooRulesDefinition() {
- this(null);
+ this(null, null);
}
- public XooRulesDefinition(@Nullable SonarRuntime sonarRuntime) {
+ public XooRulesDefinition(@Nullable SonarRuntime sonarRuntime, @Nullable Configuration configuration) {
this.version = sonarRuntime != null ? sonarRuntime.getApiVersion() : null;
+ this.configuration = configuration;
}
@Override
.setType(RuleType.SECURITY_HOTSPOT);
addAllDescriptionSections(hotspotWithRangeAndMultipleLocations, "Hotspot with range and multiple locations");
-
NewRule issueOnEachFileWithExtUnknown = repo.createRule(OneIssuePerUnknownFileSensor.RULE_KEY).setName("Creates issues on each file with extension 'unknown'");
addAllDescriptionSections(issueOnEachFileWithExtUnknown, "This issue is generated on each file with extenstion 'unknown'");
.setType(RuleType.VULNERABILITY);
addAllDescriptionSections(oneVulnerabilityIssuePerProject, "Generate an issue on each project");
+ if (configuration != null && configuration.get("sonar.xoo.OneVulnerabilityPerLine.securityStandards").isPresent()) {
+ String[] standards = configuration.get("sonar.xoo.OneVulnerabilityPerLine.securityStandards").get().split(",");
+ for (String standard : standards) {
+ NewRule oneVulnerabilityIssuePerSecurityStandard = repo.createRule(OneVulnerabilityPerSecurityStandardSensor.RULE_KEY_PREFIX + standard)
+ .setName("One Vulnerability per line containing '%s'".formatted(standard))
+ .addDefaultImpact(SoftwareQuality.SECURITY, Severity.MEDIUM)
+ .setCleanCodeAttribute(CleanCodeAttribute.TRUSTWORTHY)
+ .setType(RuleType.VULNERABILITY);
+ addSecurityStandard(oneVulnerabilityIssuePerSecurityStandard, standard);
+ addAllDescriptionSections(oneVulnerabilityIssuePerSecurityStandard, "Generate an issue on each line containing '" + standard + "'.");
+ }
+ }
+
oneVulnerabilityIssuePerProject
.setDebtRemediationFunction(oneVulnerabilityIssuePerProject.debtRemediationFunctions().linearWithOffset("25min", "1h"))
.setGapDescription("A certified architect will need roughly half an hour to start working on removal of project, " +
.addOwaspAsvs(OwaspAsvsVersion.V4_0, "11.1.2", "14.5.1", "14.5.4");
}
+ if (version != null && version.isGreaterThanOrEqual(Version.create(10, 7))) {
+ hotspot
+ .addStig(StigVersion.ASD_V5R3, "V-222643", "V-222564", "V-222655");
+ oneVulnerabilityIssuePerProject
+ .addStig(StigVersion.ASD_V5R3, "V-222480", "V-222473", "V-222524");
+ }
+
NewRule hotspotWithContexts = repo.createRule(HotspotWithContextsSensor.RULE_KEY)
.setName("Find security hotspots with contexts")
.setType(RuleType.SECURITY_HOTSPOT)
repo.done();
}
+ private void addSecurityStandard(NewRule rule, String standard) {
+ String[] splitStandard = standard.split(":");
+ switch (splitStandard[0]) {
+ case "cwe":
+ rule.addCwe(Integer.parseInt(splitStandard[1]));
+ break;
+ case "owaspTop10":
+ rule.addOwaspTop10(Y2017, OwaspTop10.valueOf(splitStandard[1]));
+ break;
+ case "owaspTop10-2021":
+ rule.addOwaspTop10(Y2021, OwaspTop10.valueOf(splitStandard[1]));
+ break;
+ case "stig-ASD_V5R3":
+ if (version != null && version.isGreaterThanOrEqual(Version.create(10, 7))) {
+ rule.addStig(StigVersion.ASD_V5R3, splitStandard[1]);
+ }
+ break;
+ case "pciDss-3.2":
+ rule.addPciDss(PciDssVersion.V3_2, splitStandard[1]);
+ break;
+ case "pciDss-4.0":
+ rule.addPciDss(PciDssVersion.V4_0, splitStandard[1]);
+ default:
+ throw new IllegalArgumentException("Unknown standard: " + standard);
+ }
+ }
+
private static void defineRulesXooExternalWithCct(Context context) {
NewRepository repo = context.createExternalRepository(OneExternalIssueCctPerLineSensor.ENGINE_ID, Xoo.KEY).setName(OneExternalIssueCctPerLineSensor.ENGINE_ID);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.xoo.rule;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
+import org.sonar.api.batch.rule.ActiveRule;
+import org.sonar.api.batch.rule.ActiveRules;
+import org.sonar.api.batch.sensor.internal.SensorContextTester;
+import org.sonar.api.internal.apachecommons.io.IOUtils;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.xoo.Xoo;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class OneVulnerabilityPerSecurityStandardSensorTest {
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private final ActiveRules activeRules = mock(ActiveRules.class);
+
+ @Before
+ public void before() {
+ List<ActiveRule> rules = List.of(mockActiveRule("OneVulnerabilityPerLine_stig-ASD_V5R3:V12345"),
+ mockActiveRule("OneVulnerabilityPerLine_stig-ASD_V5R3:V12346"));
+ when(activeRules.findAll()).thenReturn(rules);
+ }
+
+ private ActiveRule mockActiveRule(String ruleKey) {
+ ActiveRule activeRuleMock = mock(ActiveRule.class);
+ when(activeRuleMock.ruleKey()).thenReturn(RuleKey.of(XooRulesDefinition.XOO_REPOSITORY, ruleKey));
+ return activeRuleMock;
+ }
+
+ @Test
+ public void execute_dataAndExecutionFlowsAreDetectedAndMessageIsFormatted() throws IOException {
+ DefaultInputFile inputFile = newTestFile(IOUtils.toString(getClass().getResource("vulnerabilities.xoo"), StandardCharsets.UTF_8));
+
+ DefaultFileSystem fs = new DefaultFileSystem(temp.newFolder());
+ fs.add(inputFile);
+
+ OneVulnerabilityPerSecurityStandardSensor sensor = new OneVulnerabilityPerSecurityStandardSensor(fs, activeRules);
+
+ SensorContextTester sensorContextTester = SensorContextTester.create(fs.baseDir());
+ sensorContextTester.setFileSystem(fs);
+ sensor.execute(sensorContextTester);
+
+ assertThat(sensorContextTester.allIssues()).hasSize(2);
+ }
+
+ private DefaultInputFile newTestFile(String content) {
+ return new TestInputFileBuilder("foo", "src/vulnerabilities.xoo")
+ .setLanguage(Xoo.KEY)
+ .setType(InputFile.Type.MAIN)
+ .setCharset(Charset.defaultCharset())
+ .setContents(content)
+ .build();
+ }
+}
import org.junit.Test;
import org.sonar.api.SonarEdition;
import org.sonar.api.SonarQubeSide;
+import org.sonar.api.config.Configuration;
import org.sonar.api.impl.server.RulesDefinitionContext;
import org.sonar.api.internal.SonarRuntimeImpl;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.xoo.rule.hotspot.HotspotWithoutContextSensor;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;
public class XooRulesDefinitionTest {
- private XooRulesDefinition def = new XooRulesDefinition(SonarRuntimeImpl.forSonarQube(Version.create(9, 3), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY));
+ private XooRulesDefinition def = new XooRulesDefinition(SonarRuntimeImpl.forSonarQube(Version.create(10, 7), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY), mock(Configuration.class));
private RulesDefinition.Context context = new RulesDefinitionContext();
assertThat(rule.securityStandards())
.isNotEmpty()
.containsExactlyInAnyOrder("cwe:1", "cwe:89", "cwe:123", "cwe:863", "owaspTop10:a1", "owaspTop10:a3",
- "owaspTop10-2021:a3", "owaspTop10-2021:a2");
+ "owaspTop10-2021:a3", "owaspTop10-2021:a2", "owaspAsvs-4.0:2.8.7", "owaspAsvs-4.0:3.1.1",
+ "owaspAsvs-4.0:4.2.2", "pciDss-3.2:4.2", "pciDss-3.2:4.2b", "pciDss-3.2:6.5.1",
+ "pciDss-3.2:6.5a.1b", "pciDss-4.0:4.1", "pciDss-4.0:4.2c", "pciDss-4.0:6.5.1", "pciDss-4.0:6.5a.1",
+ "stig-ASD_V5R3:V-222564", "stig-ASD_V5R3:V-222643", "stig-ASD_V5R3:V-222655");
}
@Test
assertThat(rule.securityStandards())
.isNotEmpty()
.containsExactlyInAnyOrder("cwe:250", "cwe:546", "cwe:564", "cwe:943", "owaspTop10-2021:a6", "owaspTop10-2021:a9",
- "owaspTop10:a10", "owaspTop10:a9");
+ "owaspTop10:a10", "owaspTop10:a9",
+ "owaspAsvs-4.0:11.1.2", "owaspAsvs-4.0:14.5.1", "owaspAsvs-4.0:14.5.4",
+ "pciDss-3.2:10.1a.2c", "pciDss-3.2:10.2", "pciDss-4.0:10.1", "pciDss-4.0:10.1a.2b",
+ "stig-ASD_V5R3:V-222473", "stig-ASD_V5R3:V-222480", "stig-ASD_V5R3:V-222524");
}
@Test
--- /dev/null
+line1
+line2
+stig-ASD_V5R3:V12345
+line4
+stig-ASD_V5R3:V12346