🧹 CPE Normalization Examples
CPE strings from different feeds rarely agree on casing, separators, or vendor spelling. Normalize before you store or match, or you'll miss real vulnerabilities. cpe-skills offers two layers: field-level normalization in the validation module, and vendor/product alias resolution in the vendor-normalization module.
NormalizeCPE: Field-Level Cleaning
NormalizeCPE lowercases each field, replaces spaces with underscores, and regenerates Cpe23. The original object is not mutated — a new *CPE is returned.
package main
import (
"fmt"
"github.com/scagogogo/cpe-skills"
)
func main() {
raw := &cpeskills.CPE{
Part: *cpeskills.PartApplication,
Vendor: "Microsoft",
ProductName: "Windows 10",
Version: "10.0.19041",
}
n := cpeskills.NormalizeCPE(raw)
fmt.Println(n.Vendor) // microsoft
fmt.Println(n.ProductName) // windows_10
fmt.Println(n.Cpe23) // cpe:2.3:a:microsoft:windows_10:10.0.19041:*:*:*:*:*:*:*
}NormalizeComponent is the underlying helper if you only need to clean one string.
Package-Level Vendor Normalizers
NormalizeVendorName and NormalizeProductName are conveniences over a package-global VendorNormalizer (GlobalVendorNormalizer), preloaded with aliases for major vendors:
fmt.Println(cpeskills.NormalizeVendorName("Microsoft Corporation")) // microsoft
fmt.Println(cpeskills.NormalizeVendorName("msft")) // microsoft
fmt.Println(cpeskills.NormalizeProductName("microsoft", "Windows 10")) // windows_10NormalizeCPEVendorProduct runs both on a whole CPE and rebuilds its Cpe23.
VendorNormalizer: Register Custom Aliases
NewVendorNormalizer() returns an independent normalizer with the built-in alias table. RegisterVendorAlias maps any number of aliases onto one canonical form; RegisterProductAlias does the same for products.
n := cpeskills.NewVendorNormalizer()
n.RegisterVendorAlias("mycompany", "my_company", "my company", "my-company", "MyCompany Inc.")
n.RegisterProductAlias("mycompany", "myapp", "my_app", "my-app")
fmt.Println(n.NormalizeVendor("MyCompany Inc.")) // mycompany
fmt.Println(n.NormalizeProduct("mycompany", "my-app")) // myapp
fmt.Println(n.HasVendor("my-company")) // true
fmt.Println(n.VendorCount()) // includes built-ins + your aliasAll comparisons are case-insensitive and ignore separators, so Microsoft Corp and microsoft_corp resolve to one key.
AreSameVendor: Compare Without Normalizing
AreSameVendor and AreSameProduct answer "do these refer to the same entity?" without forcing you to normalize both sides first:
n := cpeskills.NewVendorNormalizer()
fmt.Println(n.AreSameVendor("microsoft_corporation", "msft")) // true
fmt.Println(n.AreSameProduct("microsoft", "windows_10", "windows")) // true
fmt.Println(n.AreSameVendor("oracle", "google")) // falseNormalizeCPE on a VendorNormalizer
(n *VendorNormalizer).NormalizeCPE clones the CPE, canonicalizes vendor and product, and rebuilds Cpe23 — the right step before matching or storing:
raw := cpeskills.MustParse("cpe:2.3:a:Microsoft_Corporation:Windows_10:10:*:*:*:*:*:*:*")
n := cpeskills.NewVendorNormalizer()
clean := n.NormalizeCPE(raw)
fmt.Println(clean.Vendor) // microsoft
fmt.Println(clean.ProductName) // windows_10
fmt.Println(clean.Cpe23) // cpe:2.3:a:microsoft:windows_10:10:*:*:*:*:*:*:*Normalize Before Matching
The matching module compares attributes literally; if one side says apache and the other apache_software_foundation, CPESubset returns Disjoint and you silently drop a vulnerable match. Normalizing both sides first collapses them to the same canonical key:
a := cpeskills.MustParse("cpe:2.3:a:apache:log4j:2.14.0:*:*:*:*:*:*:*")
b := cpeskills.MustParse("cpe:2.3:a:apache_software_foundation:log4j:2.14.0:*:*:*:*:*:*:*")
n := cpeskills.NewVendorNormalizer()
na, nb := n.NormalizeCPE(a), n.NormalizeCPE(b)
fmt.Println(cpeskills.CPEEqual(na, nb)) // trueSummary
NormalizeCPE(validation) — field-level: lowercase, underscore, rebuildCpe23. Non-mutating.NormalizeVendorName/NormalizeProductName/NormalizeCPEVendorProduct— package-level conveniences overGlobalVendorNormalizer.NewVendorNormalizer()— independent normalizer; extend withRegisterVendorAlias/RegisterProductAlias.AreSameVendor/AreSameProduct— compare without explicit normalization.- Always normalize before matching or storing so spelling drift doesn't break relations.
See the validation and vendor-normalization module pages for the full API reference.