Repository: securego/gosec (Go)

Issue:
Support official Go directive comments for rule suppression
Currently, `gosec` supports the `#nosec` annotation comment, but in Go, comments in the form of `//toolname:directive` are officially recognized as directive comments.

> A directive comment is a line matching the regular expression //(line |extern |export |[a-z0-9]+:[a-z0-9]). Tools that define their own directives should use the form //toolname:directive.

https://tip.golang.org/doc/comment#syntax

Tools like `gofmt` and `pkgsite` (`godoc`) recognize directive comments in this format.
By supporting officially formatted directive comments, `gosec` can also benefit from this standardized approach.

For example:

```go
//gosec:disable G402
```


Discussion: Thanks for bringing this up. This is useful feature for compatibility starting with Go 1.24. 
I've created a pull request.
#1314 



Fix (patch):
diff --git a/README.md b/README.md
index 5e68182cb7..019a53e970 100644
--- a/README.md
+++ b/README.md
@@ -304,7 +304,13 @@ You could put the description or justification text for the annotation. The
 justification should be after the rule(s) to suppress and start with two or
 more dashes, e.g: `//#nosec G101 G102 -- This is a false positive`
 
-In some cases you may also want to revisit places where `#nosec` annotations
+Alternatively, gosec also supports the `//gosec:disable` directive, which functions similar to `#nosec`:
+
+```go
+//gosec:disable G101 -- This is a false positive
+```
+
+In some cases you may also want to revisit places where `#nosec` or `//gosec:disable` annotations
 have been used. To run the scanner and ignore any `#nosec` annotations you
 can do the following:
 
diff --git a/analyzer.go b/analyzer.go
index 186cc3c258..34ac82b5a4 100644
--- a/analyzer.go
+++ b/analyzer.go
@@ -57,6 +57,8 @@ const externalSuppressionJustification = "Globally suppressed."
 
 const aliasOfAllRules = "*"
 
+var directiveRegexp = regexp.MustCompile("^//gosec:disable(?: (.+))?$")
+
 type ignore struct {
 	start        int
 	end          int
@@ -582,53 +584,77 @@ func (gosec *Analyzer) ignore(n ast.Node) map[string]issue.SuppressionInfo {
 	}
 
 	for _, group := range groups {
-		comment := strings.TrimSpace(group.Text())
-		foundDefaultTag := strings.HasPrefix(comment, noSecDefaultTag) || regexp.MustCompile("\n *"+noSecDefaultTag).MatchString(comment)
-		foundAlternativeTag := strings.HasPrefix(comment, noSecAlternativeTag) || regexp.MustCompile("\n *"+noSecAlternativeTag).MatchString(comment)
-
-		if foundDefaultTag || foundAlternativeTag {
-			gosec.stats.NumNosec++
-
-			// Discard what's in front of the nosec tag.
-			if foundDefaultTag {
-				comment = strings.SplitN(comment, noSecDefaultTag, 2)[1]
-			} else {
-				comment = strings.SplitN(comment, noSecAlternativeTag, 2)[1]
-			}
+		found, args := findNoSecDirective(group, noSecDefaultTag, noSecAlternativeTag)
+		if !found {
+			continue
+		}
 
-			// Extract the directive and the justification.
-			justification := ""
-			commentParts := regexp.MustCompile(`-{2,}`).Split(comment, 2)
-			directive := commentParts[0]
-			if len(commentParts) > 1 {
-				justification = strings.TrimSpace(strings.TrimRight(commentParts[1], "\n"))
-			}
+		gosec.stats.NumNosec++
 
-			// Pull out the specific rules that are listed to be ignored.
-			re := regexp.MustCompile(`(G\d{3})`)
-			matches := re.FindAllStringSubmatch(directive, -1)
+		// Extract the directive and the justification.
+		justification := ""
+		commentParts := regexp.MustCompile(`-{2,}`).Split(args, 2)
+		directive := commentParts[0]
+		if len(commentParts) > 1 {
+			justification = strings.TrimSpace(strings.TrimRight(commentParts[1], "\n"))
+		}
 
-			suppression := issue.SuppressionInfo{
-				Kind:          "inSource",
-				Justification: justification,
-			}
+		// Pull out the specific rules that are listed to be ignored.
+		re := regexp.MustCompile(`(G\d{3})`)
+		matches := re.FindAllStringSubmatch(directive, -1)
 
-			// Find the rule IDs to ignore.
-			ignores := make(map[string]issue.SuppressionInfo)
-			for _, v := range matches {
-				ignores[v[1]] = suppression
-			}
+		suppression := issue.SuppressionInfo{
+			Kind:          "inSource",
+			Justification: justification,
+		}
 
-			// If no specific rules were given, ignore everything.
-			if len(matches) == 0 {
-				ignores[aliasOfAllRules] = suppression
-			}
-			return ignores
+		// Find the rule IDs to ignore.
+		ignores := make(map[string]issue.SuppressionInfo)
+		for _, v := range matches {
+			ignores[v[1]] = suppression
 		}
+
+		// If no specific rules were given, ignore everything.
+		if len(matches) == 0 {
+			ignores[aliasOfAllRules] = suppression
+		}
+		return ignores
 	}
 	return nil
 }
 
+// findNoSecDirective checks if the comment group contains `#nosec` or `//gosec:disable` directive.
+// If found, it returns true and the directive's arguments.
+func findNoSecDirective(group *ast.CommentGroup, noSecDefaultTag, noSecAlternativeTag string) (bool, string) {
+	// Check if the comment grounp has a nosec comment.
+	for _, tag := range []string{noSecDefaultTag, noSecAlternativeTag} {
+		if found, args := findNoSecTag(group, tag); found {
+			return true, args
+		}
+	}
+
+	// Check if the comment group has a directive comment.
+	for _, c := range group.List {
+		match := directiveRegexp.FindStringSubmatch(c.Text)
+		if len(match) > 0 {
+			return true, match[0]
+		}
+	}
+
+	return false, ""
+}
+
+func findNoSecTag(group *ast.CommentGroup, tag string) (bool, string) {
+	comment := strings.TrimSpace(group.Text())
+
+	if strings.HasPrefix(comment, tag) || regexp.MustCompile("\n *"+tag).MatchString(comment) {
+		// Discard what's in front of the nosec tag.
+		return true, strings.SplitN(comment, tag, 2)[1]
+	}
+
+	return false, ""
+}
+
 // Visit runs the gosec visitor logic over an AST created by parsing go code.
 // Rule methods added with AddRule will be invoked as necessary.
 func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {