Skip to content

Commit 7b3453b

Browse files
committed
feat(nodejs): detect runtime version from header
1 parent f065203 commit 7b3453b

3 files changed

Lines changed: 97 additions & 5 deletions

File tree

pkg/fanal/analyzer/language/nodejs/pkg/pkg.go

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package pkg
22

33
import (
4+
"bufio"
45
"context"
6+
"fmt"
57
"os"
68
"path/filepath"
9+
"strconv"
710
"strings"
811

912
"github.com/samber/lo"
1013

14+
"github.com/aquasecurity/trivy/pkg/dependency"
1115
"github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson"
1216
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
1317
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/language"
@@ -20,8 +24,9 @@ func init() {
2024
}
2125

2226
const (
23-
version = 1
24-
requiredFile = "package.json"
27+
version = 2
28+
requiredFile = "package.json"
29+
requiredNodeVersionFile = "node_version.h"
2530
)
2631

2732
type parser struct{}
@@ -42,15 +47,64 @@ func (*parser) Parse(_ context.Context, r xio.ReadSeekerAt) ([]types.Package, []
4247
return []types.Package{pkg.Package}, nil, nil
4348
}
4449

50+
type nodeVersionParser struct{}
51+
52+
func (*nodeVersionParser) Parse(_ context.Context, r xio.ReadSeekerAt) ([]types.Package, []types.Dependency, error) {
53+
var versions [3]int
54+
var found [3]bool
55+
scanner := bufio.NewScanner(r)
56+
for scanner.Scan() {
57+
fields := strings.Fields(scanner.Text())
58+
if len(fields) != 3 || fields[0] != "#define" {
59+
continue
60+
}
61+
62+
var i int
63+
switch fields[1] {
64+
case "NODE_MAJOR_VERSION":
65+
i = 0
66+
case "NODE_MINOR_VERSION":
67+
i = 1
68+
case "NODE_PATCH_VERSION":
69+
i = 2
70+
default:
71+
continue
72+
}
73+
v, err := strconv.Atoi(fields[2])
74+
if err != nil {
75+
return nil, nil, err
76+
}
77+
versions[i], found[i] = v, true
78+
}
79+
if err := scanner.Err(); err != nil {
80+
return nil, nil, err
81+
}
82+
if !found[0] || !found[1] || !found[2] {
83+
return nil, nil, nil
84+
}
85+
86+
version := fmt.Sprintf("%d.%d.%d", versions[0], versions[1], versions[2])
87+
return []types.Package{{
88+
ID: dependency.ID(types.NodePkg, "node", version),
89+
Name: "node",
90+
Version: version,
91+
}}, nil, nil
92+
}
93+
4594
type nodePkgLibraryAnalyzer struct{}
4695

47-
// Analyze analyzes package.json for node packages
96+
// Analyze analyzes package.json files and Node.js version headers.
4897
func (a nodePkgLibraryAnalyzer) Analyze(ctx context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
49-
return language.AnalyzePackage(ctx, types.NodePkg, input.FilePath, input.Content, &parser{}, input.Options.FileChecksum)
98+
var p language.Parser = &parser{}
99+
if filepath.Base(input.FilePath) == requiredNodeVersionFile {
100+
p = &nodeVersionParser{}
101+
}
102+
return language.AnalyzePackage(ctx, types.NodePkg, input.FilePath, input.Content, p, input.Options.FileChecksum)
50103
}
51104

52105
func (a nodePkgLibraryAnalyzer) Required(filePath string, _ os.FileInfo) bool {
53-
return requiredFile == filepath.Base(filePath)
106+
return filepath.Base(filePath) == requiredFile ||
107+
strings.HasSuffix(filepath.ToSlash(filePath), "include/node/"+requiredNodeVersionFile)
54108
}
55109

56110
func (a nodePkgLibraryAnalyzer) Type() analyzer.Type {

pkg/fanal/analyzer/language/nodejs/pkg/pkg_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,26 @@ func Test_nodePkgLibraryAnalyzer_Analyze(t *testing.T) {
4040
},
4141
},
4242
},
43+
{
44+
name: "Node.js version header",
45+
inputFile: "testdata/node_version.h",
46+
want: &analyzer.AnalysisResult{
47+
Applications: []types.Application{
48+
{
49+
Type: types.NodePkg,
50+
FilePath: "testdata/node_version.h",
51+
Packages: types.Packages{
52+
{
53+
ID: "node@26.5.0",
54+
Name: "node",
55+
Version: "26.5.0",
56+
FilePath: "testdata/node_version.h",
57+
},
58+
},
59+
},
60+
},
61+
},
62+
},
4363
{
4464
name: "happy path with checksum",
4565
inputFile: "testdata/package.json",
@@ -109,6 +129,16 @@ func Test_nodePkgLibraryAnalyzer_Required(t *testing.T) {
109129
filePath: "nodejs/package.json",
110130
want: true,
111131
},
132+
{
133+
name: "Node.js version header",
134+
filePath: "usr/local/include/node/node_version.h",
135+
want: true,
136+
},
137+
{
138+
name: "ignore source header",
139+
filePath: "src/node_version.h",
140+
want: false,
141+
},
112142
{
113143
name: "sad path",
114144
filePath: "nodejs/package-lock.json",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#ifndef SRC_NODE_VERSION_H_
2+
#define SRC_NODE_VERSION_H_
3+
4+
#define NODE_MAJOR_VERSION 26
5+
#define NODE_MINOR_VERSION 5
6+
#define NODE_PATCH_VERSION 0
7+
8+
#endif // SRC_NODE_VERSION_H_

0 commit comments

Comments
 (0)