Skip to content

Commit 833cff6

Browse files
committed
feat(npm): lockfile v3
1 parent 4444a8b commit 833cff6

File tree

2 files changed

+141
-8
lines changed

2 files changed

+141
-8
lines changed

module/npm/lockfile_v3.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package npm
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/murphysecurity/murphysec/model"
7+
"path"
8+
)
9+
10+
type v3Lockfile struct {
11+
Name string `json:"name"`
12+
Version string `json:"version"`
13+
LockfileVersion int `json:"lockfileVersion"`
14+
Packages map[string]v3Package `json:"packages"`
15+
}
16+
17+
type v3Package struct {
18+
Name string `json:"name"`
19+
Version string `json:"version"`
20+
Dependencies map[string]string `json:"dependencies"`
21+
DevDependencies map[string]string `json:"devDependencies"`
22+
Dev bool `json:"dev"`
23+
}
24+
25+
type v3ParsedLockfile struct {
26+
Name string
27+
Version string
28+
Deps []model.DependencyItem
29+
}
30+
31+
func parseLockfileV3(data []byte) (r *v3ParsedLockfile, e error) {
32+
var lockfile v3Lockfile
33+
if e := json.Unmarshal(data, &lockfile); e != nil {
34+
return nil, fmt.Errorf("parse lockfile failed: %w", e)
35+
}
36+
if lockfile.LockfileVersion != 3 {
37+
return nil, fmt.Errorf("unsupported lockfile version: %d", lockfile.LockfileVersion)
38+
}
39+
if lockfile.Packages == nil {
40+
lockfile.Packages = make(map[string]v3Package)
41+
}
42+
parsedLockfile := v3ParsedLockfile{
43+
Name: lockfile.Name,
44+
Version: lockfile.Version,
45+
Deps: make([]model.DependencyItem, 0),
46+
}
47+
root := lockfile._v3Conv("", "", make(map[string]struct{}))
48+
if root != nil {
49+
parsedLockfile.Deps = root.Dependencies
50+
}
51+
return &parsedLockfile, nil
52+
}
53+
54+
func (v *v3Lockfile) _v3Conv(rp string, name string, visited map[string]struct{}) *model.DependencyItem {
55+
if _, ok := visited[name]; ok {
56+
return nil
57+
}
58+
visited[name] = struct{}{}
59+
defer func() {
60+
delete(visited, name)
61+
}()
62+
key := path.Join(rp, "node_modules", name)
63+
pkg, ok := v.Packages[key]
64+
if !ok {
65+
key = path.Join(rp, name)
66+
pkg, ok = v.Packages[key]
67+
}
68+
if !ok {
69+
key = path.Join("node_modules", name)
70+
pkg, ok = v.Packages[key]
71+
}
72+
if !ok {
73+
key = name
74+
pkg, ok = v.Packages[name]
75+
}
76+
if !ok {
77+
return nil
78+
}
79+
if pkg.Dev {
80+
return nil
81+
}
82+
var item = &model.DependencyItem{
83+
Component: model.Component{
84+
CompName: pkg.Name,
85+
CompVersion: pkg.Version,
86+
EcoRepo: EcoRepo,
87+
},
88+
}
89+
if item.CompName == "" {
90+
item.CompName = name
91+
}
92+
if pkg.Dependencies != nil {
93+
for s := range pkg.Dependencies {
94+
if s == "" {
95+
continue
96+
}
97+
r := v._v3Conv(key, s, visited)
98+
if r == nil {
99+
continue
100+
}
101+
item.Dependencies = append(item.Dependencies, *r)
102+
}
103+
}
104+
return item
105+
}

module/npm/npm.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,42 @@ func (i *Inspector) InspectProject(ctx context.Context) error {
4141

4242
func ScanNpmProject(ctx context.Context) ([]model.Module, error) {
4343
dir := model.UseInspectionTask(ctx).Dir()
44-
logger := logctx.Use(ctx)
4544
pkgFile := filepath.Join(dir, "package-lock.json")
45+
module := model.Module{
46+
PackageManager: "npm",
47+
ModuleName: "",
48+
ModuleVersion: "",
49+
ModulePath: pkgFile,
50+
}
51+
logger := logctx.Use(ctx)
4652
logger.Debug("Read package-lock.json", zap.String("path", pkgFile))
4753
data, e := os.ReadFile(pkgFile)
4854
if e != nil {
4955
return nil, errors.WithMessage(e, "Errors when reading package-lock.json")
5056
}
57+
lockfileVer, e := parseLockfileVersion(data)
58+
if e != nil {
59+
return nil, e
60+
}
61+
if lockfileVer == 3 {
62+
parsed, e := parseLockfileV3(data)
63+
if e != nil {
64+
return nil, fmt.Errorf("v3lockfile: %w", e)
65+
}
66+
module.ModuleName = parsed.Name
67+
module.ModuleVersion = parsed.Version
68+
module.Dependencies = parsed.Deps
69+
return []model.Module{module}, nil
70+
}
5171
var lockfile NpmPkgLock
5272
if e := json.Unmarshal(data, &lockfile); e != nil {
5373
return nil, e
5474
}
5575
if lockfile.LockfileVersion > 2 || lockfile.LockfileVersion < 1 {
5676
return nil, errors.New(fmt.Sprintf("unsupported lockfileVersion: %d", lockfile.LockfileVersion))
5777
}
78+
module.ModuleName = lockfile.Name
79+
module.ModuleVersion = lockfile.Version
5880
for s := range lockfile.Dependencies {
5981
if strings.HasPrefix(s, "node_modules/") {
6082
delete(lockfile.Dependencies, s)
@@ -82,12 +104,7 @@ func ScanNpmProject(ctx context.Context) ([]model.Module, error) {
82104
if len(rootComp) == 0 {
83105
logger.Warn("Not found root component")
84106
}
85-
module := model.Module{
86-
PackageManager: "npm",
87-
ModuleName: lockfile.Name,
88-
ModuleVersion: lockfile.Version,
89-
ModulePath: filepath.Join(dir, "package-lock.json"),
90-
}
107+
91108
m := map[string]int{}
92109
for _, it := range rootComp {
93110
if d := _convDep(it, lockfile, m, 0); d != nil {
@@ -132,7 +149,7 @@ func _convDep(root string, m NpmPkgLock, visited map[string]int, deep int) *mode
132149
type NpmPkgLock struct {
133150
Name string `json:"name"`
134151
Version string `json:"version"`
135-
LockfileVersion int `json:"LockfileVersion"`
152+
LockfileVersion int `json:"lockfileVersion"`
136153
Dependencies map[string]struct {
137154
Version string `json:"version"`
138155
Requires map[string]interface{} `json:"requires"`
@@ -143,3 +160,14 @@ var EcoRepo = model.EcoRepo{
143160
Ecosystem: "npm",
144161
Repository: "",
145162
}
163+
164+
func parseLockfileVersion(data []byte) (int, error) {
165+
type unknownVersionLockfile struct {
166+
LockfileVersion int `json:"lockfileVersion"`
167+
}
168+
var u unknownVersionLockfile
169+
if e := json.Unmarshal(data, &u); e != nil {
170+
return 0, fmt.Errorf("parse lockfile version failed: %w", e)
171+
}
172+
return u.LockfileVersion, nil
173+
}

0 commit comments

Comments
 (0)