@@ -144,6 +144,7 @@ func (i ignores) get(file string, line string) map[string][]issue.SuppressionInf
144144type Context struct {
145145 FileSet * token.FileSet
146146 Comments ast.CommentMap
147+ AllComments []* ast.CommentGroup // All comments in the file for line-based nosec lookup (fix for #1240)
147148 Info * types.Info
148149 Pkg * types.Package
149150 PkgFiles []* ast.File
@@ -475,6 +476,7 @@ func (gosec *Analyzer) checkRules(pkg *packages.Package) ([]*issue.Issue, *Metri
475476 FileSet : pkg .Fset ,
476477 Config : gosec .config ,
477478 Comments : ast .NewCommentMap (pkg .Fset , file , file .Comments ),
479+ AllComments : file .Comments , // Store all comments for line-based lookup (fix for #1240)
478480 Root : file ,
479481 Info : pkg .TypesInfo ,
480482 Pkg : pkg .Types ,
@@ -800,15 +802,39 @@ func (v *astVisitor) updateIgnoredRulesForNode(n ast.Node) {
800802 }
801803}
802804
805+ // parseNoSecDirective extracts rules and justification from nosec directive arguments
806+ func parseNoSecDirective (args string ) map [string ]issue.SuppressionInfo {
807+ justification := ""
808+ commentParts := regexp .MustCompile (`-{2,}` ).Split (args , 2 )
809+ directive := commentParts [0 ]
810+ if len (commentParts ) > 1 {
811+ justification = strings .TrimSpace (strings .TrimRight (commentParts [1 ], "\n " ))
812+ }
813+
814+ re := regexp .MustCompile (`(G\d{3})` )
815+ matches := re .FindAllStringSubmatch (directive , - 1 )
816+
817+ suppression := issue.SuppressionInfo {
818+ Kind : "inSource" ,
819+ Justification : justification ,
820+ }
821+
822+ ignores := make (map [string ]issue.SuppressionInfo )
823+ if len (matches ) == 0 {
824+ ignores [aliasOfAllRules ] = suppression
825+ } else {
826+ for _ , v := range matches {
827+ ignores [v [1 ]] = suppression
828+ }
829+ }
830+ return ignores
831+ }
832+
803833// ignore checks if a node is tagged with a nosec comment and returns the suppressed rules.
804834func (v * astVisitor ) ignore (n ast.Node ) map [string ]issue.SuppressionInfo {
805835 if v .ignoreNosec {
806836 return nil
807837 }
808- groups , ok := v .context .Comments [n ]
809- if ! ok {
810- return nil
811- }
812838
813839 noSecDefaultTag , err := v .gosec .config .GetGlobal (Nosec )
814840 if err != nil {
@@ -823,38 +849,56 @@ func (v *astVisitor) ignore(n ast.Node) map[string]issue.SuppressionInfo {
823849 noSecAlternativeTag = NoSecTag (noSecAlternativeTag )
824850 }
825851
826- for _ , group := range groups {
827- found , args := findNoSecDirective (group , noSecDefaultTag , noSecAlternativeTag )
828- if ! found {
829- continue
830- }
831- v .stats .NumNosec ++
832-
833- justification := ""
834- commentParts := regexp .MustCompile (`-{2,}` ).Split (args , 2 )
835- directive := commentParts [0 ]
836- if len (commentParts ) > 1 {
837- justification = strings .TrimSpace (strings .TrimRight (commentParts [1 ], "\n " ))
838- }
839-
840- re := regexp .MustCompile (`(G\d{3})` )
841- matches := re .FindAllStringSubmatch (directive , - 1 )
842-
843- suppression := issue.SuppressionInfo {
844- Kind : "inSource" ,
845- Justification : justification ,
846- }
852+ // First, try the existing AST CommentMap lookup
853+ groups , ok := v .context .Comments [n ]
854+ if ok {
855+ for _ , group := range groups {
856+ found , args := findNoSecDirective (group , noSecDefaultTag , noSecAlternativeTag )
857+ if ! found {
858+ continue
859+ }
860+ v .stats .NumNosec ++
861+ return parseNoSecDirective (args )
862+ }
863+ }
864+
865+ // FIX FOR ISSUE #1240:
866+ // If not found in CommentMap, check all comments on the same line.
867+ // This handles the case where the comment is associated with a parent node
868+ // (like IfStmt or BlockStmt) instead of the specific expression that
869+ // triggers the rule (like a type conversion CallExpr).
870+ //
871+ // Go's ast.CommentMap associates comments with the "largest" node on the
872+ // same line. For example:
873+ // if len(x) <= int(y) { // #nosec G115
874+ // The comment gets associated with the IfStmt, not the int(y) CallExpr.
875+ // This fallback ensures the nosec still applies to the conversion.
876+ if v .context .FileSet != nil && n != nil && v .context .AllComments != nil {
877+ nodePos := v .context .FileSet .Position (n .Pos ())
878+ nodeEndPos := v .context .FileSet .Position (n .End ())
879+
880+ for _ , group := range v .context .AllComments {
881+ if group == nil {
882+ continue
883+ }
847884
848- ignores := make (map [string ]issue.SuppressionInfo )
849- for _ , v := range matches {
850- ignores [v [1 ]] = suppression
851- }
885+ for _ , comment := range group .List {
886+ commentPos := v .context .FileSet .Position (comment .Pos ())
852887
853- if len (matches ) == 0 {
854- ignores [aliasOfAllRules ] = suppression
888+ // Check if the comment is on the same line as the node
889+ // (either where it starts or ends, to handle multi-line nodes)
890+ if commentPos .Line == nodePos .Line || commentPos .Line == nodeEndPos .Line {
891+ found , args := findNoSecDirective (group , noSecDefaultTag , noSecAlternativeTag )
892+ if ! found {
893+ continue
894+ }
895+ v .stats .NumNosec ++
896+ return parseNoSecDirective (args )
897+ }
898+ }
855899 }
856- return ignores
857900 }
901+
858902 return nil
859903}
860904
0 commit comments