@@ -44,10 +44,12 @@ const debug = Debug("snyk");
4444export class GoBinary {
4545 public name : string ;
4646 public modules : GoModule [ ] ;
47+ public goVersion : string ;
4748 private hasPclnTab : boolean ;
4849
4950 constructor ( goElfBinary : Elf ) {
50- [ this . name , this . modules ] = extractModuleInformation ( goElfBinary ) ;
51+ [ this . name , this . modules , this . goVersion ] =
52+ extractModuleInformation ( goElfBinary ) ;
5153
5254 const pclnTab = goElfBinary . body . sections . find (
5355 ( section ) => section . name === ".gopclntab" ,
@@ -108,6 +110,19 @@ export class GoBinary {
108110 // else: pclnTab exists but module has no packages - don't report anything
109111 }
110112
113+ if ( this . goVersion ) {
114+ const stdlibNodeId = `stdlib@${ this . goVersion } ` ;
115+ goModulesDepGraph . addPkgNode (
116+ { name : "stdlib" , version : this . goVersion } ,
117+ stdlibNodeId ,
118+ ) ;
119+ goModulesDepGraph . connectDep ( goModulesDepGraph . rootNodeId , stdlibNodeId ) ;
120+ } else {
121+ debug (
122+ `Skipping stdlib node for ${ this . name } : could not parse Go version` ,
123+ ) ;
124+ }
125+
111126 return goModulesDepGraph . build ( ) ;
112127 }
113128
@@ -184,15 +199,36 @@ interface GoFileNameError extends Error {
184199 moduleName : string ;
185200}
186201
202+ /**
203+ * Strips the "go" prefix from a Go version string and validates the format.
204+ * Returns the cleaned version (e.g., "1.21.0") or empty string if invalid.
205+ * Rejects RC/beta/devel versions since we cannot accurately match vulnerabilities
206+ * against pre-release builds.
207+ */
208+ export function parseGoVersion ( rawVersion : string ) : string {
209+ // Only match release versions (e.g., "go1.21" or "go1.21.5").
210+ // Reject RC/beta (go1.21rc1, go1.22beta2) and devel builds.
211+ const match = rawVersion . match ( / ^ g o ( \d + \. \d + (?: \. \d + ) ? ) $ / ) ;
212+ if ( ! match ) {
213+ return "" ;
214+ }
215+ const ver = match [ 1 ] ;
216+ // Ensure three-segment semver (e.g., "1.19" → "1.19.0") because
217+ // @snyk /vuln uses node's semver library which requires three segments.
218+ return ver . includes ( "." , ver . indexOf ( "." ) + 1 ) ? ver : ver + ".0" ;
219+ }
220+
187221export function extractModuleInformation (
188222 binary : Elf ,
189- ) : [ name : string , deps : GoModule [ ] ] {
190- const mod = readRawBuildInfo ( binary ) ;
191- if ( ! mod ) {
223+ ) : [ name : string , deps : GoModule [ ] , goVersion : string ] {
224+ const { goVersion : rawGoVersion , modInfo } = readRawBuildInfo ( binary ) ;
225+ if ( ! modInfo ) {
192226 throw Error ( "binary contains empty module info" ) ;
193227 }
194228
195- const [ pathDirective , mainModuleLine , ...versionsLines ] = mod
229+ const goVersion = parseGoVersion ( rawGoVersion ) ;
230+
231+ const [ pathDirective , mainModuleLine , ...versionsLines ] = modInfo
196232 . replace ( "\r" , "" )
197233 . split ( "\n" ) ;
198234 const lineSplit = mainModuleLine . split ( "\t" ) ;
@@ -224,7 +260,7 @@ export function extractModuleInformation(
224260 }
225261 } ) ;
226262
227- return [ name , modules ] ;
263+ return [ name , modules , goVersion ] ;
228264}
229265
230266// Source
@@ -234,7 +270,12 @@ export function extractModuleInformation(
234270 * module version information in the executable binary
235271 * @param binary
236272 */
237- export function readRawBuildInfo ( binary : Elf ) : string {
273+ export interface RawBuildInfo {
274+ goVersion : string ;
275+ modInfo : string ;
276+ }
277+
278+ export function readRawBuildInfo ( binary : Elf ) : RawBuildInfo {
238279 const buildInfoMagic = "\xff Go buildinf:" ;
239280 // Read the first 64kB of dataAddr to find the build info blob.
240281 // On some platforms, the blob will be in its own section, and DataStart
@@ -272,9 +313,9 @@ export function readRawBuildInfo(binary: Elf): string {
272313 const ptrSize = data [ 14 ] ;
273314 if ( ( data [ 15 ] & 2 ) !== 0 ) {
274315 data = data . subarray ( 32 ) ;
275- [ , data ] = decodeString ( data ) ;
276- const [ mod ] = decodeString ( data ) ;
277- return mod ;
316+ const [ goVersion , rest ] = decodeString ( data ) ;
317+ const [ mod ] = decodeString ( rest ) ;
318+ return { goVersion , modInfo : mod } ;
278319 } else {
279320 const bigEndian = data [ 15 ] !== 0 ;
280321
@@ -326,7 +367,7 @@ export function readRawBuildInfo(binary: Elf): string {
326367 // First 16 bytes are unicodes as last 16
327368 // Mirrors go version source code
328369 if ( mod . length >= 33 && mod [ mod . length - 17 ] === "\n" ) {
329- return mod . slice ( 16 , mod . length - 16 ) ;
370+ return { goVersion : version , modInfo : mod . slice ( 16 , mod . length - 16 ) } ;
330371 } else {
331372 throw Error ( "binary is not built with go module support" ) ;
332373 }
0 commit comments