github.com/khulnasoft-lab/tunnel-db@v0.0.0-20231117205118-74e1113bd007/pkg/vulnsrc/node/node.go (about) 1 package node 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strconv" 9 "strings" 10 11 bolt "go.etcd.io/bbolt" 12 "golang.org/x/xerrors" 13 14 "github.com/khulnasoft-lab/tunnel-db/pkg/db" 15 "github.com/khulnasoft-lab/tunnel-db/pkg/types" 16 "github.com/khulnasoft-lab/tunnel-db/pkg/vulnsrc/bucket" 17 "github.com/khulnasoft-lab/tunnel-db/pkg/vulnsrc/vulnerability" 18 ) 19 20 const ( 21 nodeDir = "nodejs-security-wg" 22 ) 23 24 var ( 25 source = types.DataSource{ 26 ID: vulnerability.NodejsSecurityWg, 27 Name: "Node.js Ecosystem Security Working Group", 28 URL: "https://github.com/nodejs/security-wg", 29 } 30 31 bucketName = bucket.Name(vulnerability.Npm, source.Name) 32 ) 33 34 type Number struct { 35 Value float64 36 } 37 38 // This is for Go 1.14+ compat, to support mixed strings of CVSSScores 39 // In Node core CVSSScore is like: "4.8 (Medium)", Type string 40 // In NPM package CVSSScore is like: 4.8, Type float64 41 // Details: https://github.com/golang/go/issues/37308 42 func (n *Number) UnmarshalJSON(b []byte) error { 43 var data interface{} 44 if err := json.Unmarshal(b, &data); err != nil { 45 return err 46 } 47 48 switch v := data.(type) { 49 case float64: 50 n.Value = v 51 case string: 52 f, err := strconv.ParseFloat(strings.Split(v, " ")[0], 64) 53 if err != nil { 54 return err 55 } 56 n.Value = f 57 default: // it can be null: https://github.com/nodejs/security-wg/blob/master/vuln/npm/334.json 58 n.Value = -1 59 } 60 return nil 61 } 62 63 type RawAdvisory struct { 64 ID int 65 Title string 66 ModuleName string `json:"module_name"` 67 Cves []string 68 VulnerableVersions string `json:"vulnerable_versions"` 69 PatchedVersions string `json:"patched_versions"` 70 Overview string 71 Recommendation string 72 References []string 73 CvssScoreNumber Number `json:"cvss_score"` 74 CvssScore float64 75 } 76 77 type VulnSrc struct { 78 dbc db.Operation 79 } 80 81 func NewVulnSrc() VulnSrc { 82 return VulnSrc{ 83 dbc: db.Config{}, 84 } 85 } 86 87 func (vs VulnSrc) Name() types.SourceID { 88 return source.ID 89 } 90 91 func (vs VulnSrc) Update(dir string) error { 92 repoPath := filepath.Join(dir, nodeDir) 93 if err := vs.update(repoPath); err != nil { 94 return xerrors.Errorf("failed to update node vulnerabilities: %w", err) 95 } 96 return nil 97 } 98 99 func (vs VulnSrc) update(repoPath string) error { 100 root := filepath.Join(repoPath, "vuln") 101 102 err := vs.dbc.BatchUpdate(func(tx *bolt.Tx) error { 103 if err := vs.dbc.PutDataSource(tx, bucketName, source); err != nil { 104 return xerrors.Errorf("failed to put data source: %w", err) 105 } 106 if err := vs.walk(tx, root); err != nil { 107 return xerrors.Errorf("failed to walk node advisories: %w", err) 108 } 109 return nil 110 }) 111 if err != nil { 112 return xerrors.Errorf("batch update failed: %w", err) 113 } 114 return nil 115 } 116 117 func (vs VulnSrc) walk(tx *bolt.Tx, root string) error { 118 return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 119 if err != nil { 120 return err 121 } 122 if info.IsDir() || !strings.HasSuffix(info.Name(), ".json") { 123 return nil 124 } 125 126 f, err := os.Open(path) 127 if err != nil { 128 return err 129 } 130 defer f.Close() 131 132 return vs.commit(tx, f) 133 }) 134 } 135 136 func (vs VulnSrc) commit(tx *bolt.Tx, f *os.File) error { 137 advisory := RawAdvisory{} 138 var err error 139 if err = json.NewDecoder(f).Decode(&advisory); err != nil { 140 return err 141 } 142 143 // Node.js itself 144 if advisory.ModuleName == "" { 145 return nil 146 } 147 advisory.ModuleName = strings.ToLower(advisory.ModuleName) 148 149 vulnerabilityIDs := advisory.Cves 150 if len(vulnerabilityIDs) == 0 { 151 vulnerabilityIDs = []string{fmt.Sprintf("NSWG-ECO-%d", advisory.ID)} 152 } 153 154 adv := convertToGenericAdvisory(advisory) 155 for _, vulnID := range vulnerabilityIDs { 156 // for detecting vulnerabilities 157 err = vs.dbc.PutAdvisoryDetail(tx, vulnID, advisory.ModuleName, []string{bucketName}, adv) 158 if err != nil { 159 return xerrors.Errorf("failed to save node advisory: %w", err) 160 } 161 162 // If an advisory is 0 override with -1 163 // https://github.com/nodejs/security-wg/pull/91/files 164 if advisory.CvssScoreNumber.Value <= 0 { 165 advisory.CvssScoreNumber.Value = -1 166 } 167 168 // for displaying vulnerability detail 169 vuln := types.VulnerabilityDetail{ 170 ID: vulnID, 171 CvssScore: advisory.CvssScoreNumber.Value, 172 References: advisory.References, 173 Title: advisory.Title, 174 Description: advisory.Overview, 175 } 176 if err = vs.dbc.PutVulnerabilityDetail(tx, vulnID, source.ID, vuln); err != nil { 177 return xerrors.Errorf("failed to save node vulnerability detail: %w", err) 178 } 179 180 // for optimization 181 if err = vs.dbc.PutVulnerabilityID(tx, vulnID); err != nil { 182 return xerrors.Errorf("failed to save the vulnerability ID: %w", err) 183 } 184 } 185 186 return nil 187 } 188 189 func convertToGenericAdvisory(advisory RawAdvisory) types.Advisory { 190 var vulnerable, patched []string 191 if advisory.VulnerableVersions != "" { 192 for _, ver := range strings.Split(advisory.VulnerableVersions, "||") { 193 vulnerable = append(vulnerable, strings.TrimSpace(ver)) 194 } 195 } 196 if advisory.PatchedVersions != "" { 197 for _, ver := range strings.Split(advisory.PatchedVersions, "||") { 198 patched = append(patched, strings.TrimSpace(ver)) 199 } 200 } 201 202 return types.Advisory{ 203 VulnerableVersions: vulnerable, 204 PatchedVersions: patched, 205 } 206 }