Skip to content

Commit

Permalink
feat(npm): lockfile v3
Browse files Browse the repository at this point in the history
  • Loading branch information
iseki0 committed May 15, 2023
1 parent 4444a8b commit 833cff6
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 8 deletions.
105 changes: 105 additions & 0 deletions module/npm/lockfile_v3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package npm

import (
"encoding/json"
"fmt"
"github.com/murphysecurity/murphysec/model"
"path"
)

type v3Lockfile struct {
Name string `json:"name"`
Version string `json:"version"`
LockfileVersion int `json:"lockfileVersion"`
Packages map[string]v3Package `json:"packages"`
}

type v3Package struct {
Name string `json:"name"`
Version string `json:"version"`
Dependencies map[string]string `json:"dependencies"`
DevDependencies map[string]string `json:"devDependencies"`
Dev bool `json:"dev"`
}

type v3ParsedLockfile struct {
Name string
Version string
Deps []model.DependencyItem
}

func parseLockfileV3(data []byte) (r *v3ParsedLockfile, e error) {
var lockfile v3Lockfile
if e := json.Unmarshal(data, &lockfile); e != nil {
return nil, fmt.Errorf("parse lockfile failed: %w", e)
}
if lockfile.LockfileVersion != 3 {
return nil, fmt.Errorf("unsupported lockfile version: %d", lockfile.LockfileVersion)
}
if lockfile.Packages == nil {
lockfile.Packages = make(map[string]v3Package)
}
parsedLockfile := v3ParsedLockfile{
Name: lockfile.Name,
Version: lockfile.Version,
Deps: make([]model.DependencyItem, 0),
}
root := lockfile._v3Conv("", "", make(map[string]struct{}))
if root != nil {
parsedLockfile.Deps = root.Dependencies
}
return &parsedLockfile, nil
}

func (v *v3Lockfile) _v3Conv(rp string, name string, visited map[string]struct{}) *model.DependencyItem {
if _, ok := visited[name]; ok {
return nil
}
visited[name] = struct{}{}
defer func() {
delete(visited, name)
}()
key := path.Join(rp, "node_modules", name)
pkg, ok := v.Packages[key]
if !ok {
key = path.Join(rp, name)
pkg, ok = v.Packages[key]
}
if !ok {
key = path.Join("node_modules", name)
pkg, ok = v.Packages[key]
}
if !ok {
key = name
pkg, ok = v.Packages[name]
}
if !ok {
return nil
}
if pkg.Dev {
return nil
}
var item = &model.DependencyItem{
Component: model.Component{
CompName: pkg.Name,
CompVersion: pkg.Version,
EcoRepo: EcoRepo,
},
}
if item.CompName == "" {
item.CompName = name
}
if pkg.Dependencies != nil {
for s := range pkg.Dependencies {
if s == "" {
continue
}
r := v._v3Conv(key, s, visited)
if r == nil {
continue
}
item.Dependencies = append(item.Dependencies, *r)
}
}
return item
}
44 changes: 36 additions & 8 deletions module/npm/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,42 @@ func (i *Inspector) InspectProject(ctx context.Context) error {

func ScanNpmProject(ctx context.Context) ([]model.Module, error) {
dir := model.UseInspectionTask(ctx).Dir()
logger := logctx.Use(ctx)
pkgFile := filepath.Join(dir, "package-lock.json")
module := model.Module{
PackageManager: "npm",
ModuleName: "",
ModuleVersion: "",
ModulePath: pkgFile,
}
logger := logctx.Use(ctx)
logger.Debug("Read package-lock.json", zap.String("path", pkgFile))
data, e := os.ReadFile(pkgFile)
if e != nil {
return nil, errors.WithMessage(e, "Errors when reading package-lock.json")
}
lockfileVer, e := parseLockfileVersion(data)
if e != nil {
return nil, e
}
if lockfileVer == 3 {
parsed, e := parseLockfileV3(data)
if e != nil {
return nil, fmt.Errorf("v3lockfile: %w", e)
}
module.ModuleName = parsed.Name
module.ModuleVersion = parsed.Version
module.Dependencies = parsed.Deps
return []model.Module{module}, nil
}
var lockfile NpmPkgLock
if e := json.Unmarshal(data, &lockfile); e != nil {
return nil, e
}
if lockfile.LockfileVersion > 2 || lockfile.LockfileVersion < 1 {
return nil, errors.New(fmt.Sprintf("unsupported lockfileVersion: %d", lockfile.LockfileVersion))
}
module.ModuleName = lockfile.Name
module.ModuleVersion = lockfile.Version
for s := range lockfile.Dependencies {
if strings.HasPrefix(s, "node_modules/") {
delete(lockfile.Dependencies, s)
Expand Down Expand Up @@ -82,12 +104,7 @@ func ScanNpmProject(ctx context.Context) ([]model.Module, error) {
if len(rootComp) == 0 {
logger.Warn("Not found root component")
}
module := model.Module{
PackageManager: "npm",
ModuleName: lockfile.Name,
ModuleVersion: lockfile.Version,
ModulePath: filepath.Join(dir, "package-lock.json"),
}

m := map[string]int{}
for _, it := range rootComp {
if d := _convDep(it, lockfile, m, 0); d != nil {
Expand Down Expand Up @@ -132,7 +149,7 @@ func _convDep(root string, m NpmPkgLock, visited map[string]int, deep int) *mode
type NpmPkgLock struct {
Name string `json:"name"`
Version string `json:"version"`
LockfileVersion int `json:"LockfileVersion"`
LockfileVersion int `json:"lockfileVersion"`
Dependencies map[string]struct {
Version string `json:"version"`
Requires map[string]interface{} `json:"requires"`
Expand All @@ -143,3 +160,14 @@ var EcoRepo = model.EcoRepo{
Ecosystem: "npm",
Repository: "",
}

func parseLockfileVersion(data []byte) (int, error) {
type unknownVersionLockfile struct {
LockfileVersion int `json:"lockfileVersion"`
}
var u unknownVersionLockfile
if e := json.Unmarshal(data, &u); e != nil {
return 0, fmt.Errorf("parse lockfile version failed: %w", e)
}
return u.LockfileVersion, nil
}

0 comments on commit 833cff6

Please sign in to comment.