diff --git a/python-commons/src/test/java/org/sonar/plugins/python/nosonar/NoSonarLineInfoCollectorTest.java b/python-commons/src/test/java/org/sonar/plugins/python/nosonar/NoSonarLineInfoCollectorTest.java index 7fb2f6e07..82d7748d0 100644 --- a/python-commons/src/test/java/org/sonar/plugins/python/nosonar/NoSonarLineInfoCollectorTest.java +++ b/python-commons/src/test/java/org/sonar/plugins/python/nosonar/NoSonarLineInfoCollectorTest.java @@ -185,6 +185,24 @@ private static Stream provideCollectorParameters() { Set.of(1, 2, 3, 4, 5), "", "" + ), + Arguments.of(""" + import hashlib + hashlib.md5(b"x").hexdigest() # nosec + """, + Map.of(2, new NoSonarLineInfo(Set.of(), "")), + Set.of(2), + "", + "" + ), + Arguments.of(""" + import hashlib + hashlib.md5(b"x").hexdigest() # nosec B303 legacy hash + """, + Map.of(2, new NoSonarLineInfo(Set.of(), "B303 legacy hash")), + Set.of(2), + "", + "" ) ); } diff --git a/python-frontend/src/main/java/org/sonar/plugins/python/api/nosonar/NoSonarInfoParser.java b/python-frontend/src/main/java/org/sonar/plugins/python/api/nosonar/NoSonarInfoParser.java index 4f80be0f4..791cd730c 100644 --- a/python-frontend/src/main/java/org/sonar/plugins/python/api/nosonar/NoSonarInfoParser.java +++ b/python-frontend/src/main/java/org/sonar/plugins/python/api/nosonar/NoSonarInfoParser.java @@ -33,14 +33,19 @@ public class NoSonarInfoParser { private static final String NOQA_PATTERN_REGEX = "^#\\s*noqa(?::\\s*(.+))?(?:[\\s;:].*)?"; private static final String NOSONAR_PREFIX_REGEX = "^#\\s*NOSONAR(\\W.*)?"; private static final String NOSONAR_PATTERN_REGEX = "^#\\s*NOSONAR(?:\\s*\\(([^)]*)\\))?($|\\s.*)"; + // Bandit `# nosec`. Anything after `nosec` is treated as a free-form description; rule IDs + // are not parsed, so every `# nosec` suppresses all issues on the line. + private static final String NOSEC_PATTERN_REGEX = "(?i)^#\\s*nosec\\b[:\\s]*(.*)"; private static final String RULE_KEY_PATTERN_REGEX = "^[a-zA-Z0-9]+$"; private final Pattern noSonarPattern; private final Pattern noQaPattern; + private final Pattern noSecPattern; public NoSonarInfoParser() { noSonarPattern = Pattern.compile(NOSONAR_PATTERN_REGEX); noQaPattern = Pattern.compile(NOQA_PATTERN_REGEX); + noSecPattern = Pattern.compile(NOSEC_PATTERN_REGEX); } public boolean isInvalidIssueSuppressionComment(String commentsLine) { @@ -94,6 +99,10 @@ public static boolean isValidNoQa(String noSonarCommentLine) { return noSonarCommentLine.matches(NOQA_PATTERN_REGEX); } + public static boolean isValidNoSec(String commentLine) { + return commentLine.matches(NOSEC_PATTERN_REGEX); + } + public Optional parse(String commentLine) { var rules = new HashSet(); StringBuilder concatenatedCommentBuilder = new StringBuilder(); @@ -132,6 +141,9 @@ private NoSonarLineInfo parseComment(String commentLine) { .filter(Predicate.not(String::isEmpty)) .forEach(rules::add); comment = parseNoQaComment(commentLine); + } else if (isValidNoSec(commentLine)) { + // `# nosec` always suppresses all rules on the line; we do not parse Bandit IDs. + comment = parseNoSecComment(commentLine); } else { return null; } @@ -174,6 +186,12 @@ private String parseNoQaComment(String noSonarCommentLine) { return getTruncatedCommentString(noQaPattern, noSonarCommentLine).strip(); } + private String parseNoSecComment(String noSecCommentLine) { + var raw = getPatternGroup(1, noSecPattern, noSecCommentLine); + var truncated = raw.length() > MAX_COMMENT_LENGTH ? raw.substring(0, MAX_COMMENT_LENGTH) : raw; + return truncated.strip(); + } + private static String getParamsString(Pattern pattern, String noSonarCommentLine) { return getPatternGroup(1, pattern, noSonarCommentLine); } diff --git a/python-frontend/src/test/java/org/sonar/plugins/python/api/nosonar/NoSonarInfoParserTest.java b/python-frontend/src/test/java/org/sonar/plugins/python/api/nosonar/NoSonarInfoParserTest.java index afde41140..53de781a4 100644 --- a/python-frontend/src/test/java/org/sonar/plugins/python/api/nosonar/NoSonarInfoParserTest.java +++ b/python-frontend/src/test/java/org/sonar/plugins/python/api/nosonar/NoSonarInfoParserTest.java @@ -130,6 +130,34 @@ private static Stream provideParserParameters() { Arguments.of( "# noqa; noqa; noqa; noqa; noqa", new NoSonarLineInfo(Set.of()) + ), + Arguments.of( + "# nosec", + new NoSonarLineInfo(Set.of()) + ), + Arguments.of( + "# nosec B101", + new NoSonarLineInfo(Set.of(), "B101") + ), + Arguments.of( + "# nosec B101, B102 reason text", + new NoSonarLineInfo(Set.of(), "B101, B102 reason text") + ), + Arguments.of( + "# nosec: it's fine", + new NoSonarLineInfo(Set.of(), "it's fine") + ), + Arguments.of( + "# NOSEC", + new NoSonarLineInfo(Set.of()) + ), + Arguments.of( + "# nosec a very long comment that I don't want to keep that long because there is more than 50 characters", + new NoSonarLineInfo(Set.of(), "a very long comment that I don't want to keep that") + ), + Arguments.of( + "# some text # nosec", + new NoSonarLineInfo(Set.of()) ) ); }