You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Dual format support — parse and encode both CPE 2.3 formatted strings and CPE 2.2 URIs
Full matching engine — all 17 attribute comparison cases from Table 6-2, wildcard support, four name comparison relations
Three validation tiers — quick string check, full WFN validation, strict dictionary acceptance criteria
Zero runtime dependencies — only typescript, tsup, and vitest as dev dependencies
Type-safe — full TypeScript types with ANY/NA as Symbols (not strings)
Dual output — ESM + CJS bundles with .d.ts declarations
Installation
npm install @interlynk-io/cpe-js
Quick Start
import{parse,encode,validate,isSuperset,ANY,NA}from'@interlynk-io/cpe-js';// Parse a CPE 2.3 formatted stringconstwfn=parse('cpe:2.3:a:microsoft:internet_explorer:8.0.6001:beta:*:*:*:*:*:*');console.log(wfn.part);// "a"console.log(wfn.vendor);// "microsoft"console.log(wfn.product);// "internet_explorer"console.log(wfn.version);// "8\\.0\\.6001"console.log(wfn.update);// "beta"console.log(wfn.edition);// Symbol(CPE_ANY)// Parse a CPE 2.2 URI (auto-detected)constwfn2=parse('cpe:/a:microsoft:internet_explorer:8.0.6001:beta');// Encode back to formatted stringconsole.log(encode(wfn));// "cpe:2.3:a:microsoft:internet_explorer:8.0.6001:beta:*:*:*:*:*:*"// Check if one CPE is a superset of anotherconstquery=parse('cpe:2.3:a:microsoft:internet_explorer:8.*:*:*:*:*:*:*:*');consttarget=parse('cpe:2.3:a:microsoft:internet_explorer:8.0.6001:-:-:en\\-us:*:*:*:*');console.log(isSuperset(query,target));// true
Use Cases
Parse CPEs from vulnerability feeds
import{parse,partName,displayName}from'@interlynk-io/cpe-js';constcpe=parse(nvdEntry.cpe23Uri);console.log(partName(cpe));// "Application"console.log(displayName(cpe));// "Microsoft Internet Explorer 8.0.6001 Beta"
Validate user input
import{parse,validate,validateString}from'@interlynk-io/cpe-js';// Quick string-level validation (no full parse)consterrors=validateString(userInput);if(errors.length>0){return{valid: false,errors: errors.map(e=>e.message)};}// Full WFN validation after parsingconstwfn=parse(userInput);constwfnErrors=validate(wfn);// Returns per-attribute errors: [{ attribute: "language", value: "xyz", message: "..." }]
Check dictionary acceptance criteria
import{parse,validateAsDict}from'@interlynk-io/cpe-js';// Stricter validation: part, vendor, product must not be ANY or NA; no wildcardsconstwfn=parse('cpe:2.3:a:microsoft:internet_explorer:8.0.6001:beta:-:en:*:*:*:*');consterrors=validateAsDict(wfn);// [] (valid dictionary entry)
Match CPEs against vulnerability advisories
import{parse,isSuperset,isSubset,isEqual,matchesAny,compare,Relation}from'@interlynk-io/cpe-js';// Does this advisory apply to this product?constadvisory=parse('cpe:2.3:a:apache:log4j:2.*:*:*:*:*:*:*:*');constinstalled=parse('cpe:2.3:a:apache:log4j:2.14.1:*:*:*:*:*:*:*');console.log(isSuperset(advisory,installed));// true — advisory covers this version// Full relationconsole.log(compare(advisory,installed));// Relation.SUPERSET// Check against a list of known vulnerable CPEsconstvulnerableCpes=[parse('cpe:2.3:a:apache:log4j:2.14.1:*:*:*:*:*:*:*'),parse('cpe:2.3:a:apache:log4j:2.15.0:*:*:*:*:*:*:*'),];console.log(matchesAny(advisory,vulnerableCpes));// true
Build CPEs programmatically
import{fromParts,setAttribute,encode,val,NA,createWFN}from'@interlynk-io/cpe-js';// Quick: from the three core fieldsconstwfn=fromParts('a','apache','log4j');console.log(encode(wfn));// "cpe:2.3:a:apache:log4j:*:*:*:*:*:*:*:*"// Set additional attributes (immutable — returns new WFN)constspecific=setAttribute(setAttribute(wfn,'version','2\\.14\\.1'),'update',NA);console.log(encode(specific));// "cpe:2.3:a:apache:log4j:2.14.1:-:*:*:*:*:*:*"// Using val() for convenient ANY/NA conversionconstwfn2=createWFN();wfn2.part='o';wfn2.vendor='microsoft';wfn2.product='windows_10';wfn2.version=val('1903');wfn2.update=val('-');// NA
Parse a CPE string (auto-detects 2.3 formatted string vs 2.2 URI). Throws on invalid input. Preserves case by default; pass { preserveCase: false } to lowercase all attribute values.
encode(wfn: WFN): string
Encode a WFN as a CPE 2.3 formatted string.
encodeURI(wfn: WFN): string
Encode a WFN as a CPE 2.2 URI.
fromParts(part, vendor, product): WFN
Create a WFN from the three core fields. All others default to ANY.
val(s: string): AttributeValue
Convert "*" to ANY, "-" to NA, or return the string.
mustParse(s: string): WFN
Alias for parse(). Use for known-good constants.
WFN Operations
Function
Description
createWFN(): WFN
Create a new WFN with all attributes set to ANY.
getAttribute(wfn, attr): AttributeValue
Get the value of a specific attribute.
setAttribute(wfn, attr, value): WFN
Return a new WFN with the attribute changed (immutable).
attributeEntries(wfn): Generator
Iterate over all 11 [attribute, value] pairs in canonical order.
Validation
Function
Description
validate(wfn: WFN): ValidationError[]
Validate a WFN is well-formed per NISTIR 7695. Returns [] if valid.
validateAsDict(wfn: WFN): ValidationError[]
Stricter: dictionary acceptance criteria per NISTIR 7697 (no ANY/NA for part/vendor/product, no wildcards).
validateString(s: string): ValidationError[]
Quick regex check of a CPE 2.3 string without full parsing.
Matching
Function
Description
compare(source, target): Relation
Return the overall name relation (SUPERSET, SUBSET, EQUAL, DISJOINT, or UNDEFINED).
Dictionary acceptance criteria (no ANY/NA for part/vendor/product)
No wildcard requirement for dictionary entries
Design Decisions
ANY/NA as Symbols — prevents confusion between the logical value ANY and the literal string "*". Symbol.for() ensures cross-realm equality.
WFN is the canonical form — all operations (validate, match, compare) work on WFNs. Bound forms are just serialization.
Immutable setAttribute() — returns a new WFN copy, aligning with React/functional patterns.
parse() auto-detects format — no need for callers to pre-classify CPE strings.
Structured validation errors — ValidationError[] with per-attribute details maps directly to UI form validation patterns.
Case is preserved on parse — parse() keeps the original case of vendor, product, version, and other free-form attributes so identifier data is not silently altered when round-tripping through your application or storage. The part attribute is always normalized to lowercase since the spec defines it as the enum 'a' | 'o' | 'h'. Matching remains case-insensitive (NISTIR 7696 §7.3) — compareAttribute lowercases values at compare time. Pass parse(s, { preserveCase: false }) for the legacy normalize-everything behavior.
Development
Setup
npm install
Tests
npm test# Run all 40 tests
npm run test:watch # Watch mode
npm run test:coverage # With coverage
Build
npm run build # Outputs ESM + CJS to ./dist
Type Check
npm run lint # tsc --noEmit
Playground
Interactive browser-based playground for exploring CPE parsing, encoding, validation, and matching. No build step required — Vite serves TypeScript directly.
npx vite --open playground.html --port 5555
Four tabs:
Parse — enter a CPE string (2.3 or 2.2), see parsed WFN attributes and canonical form. Quick-try buttons for common CPEs.
Build — select part type, fill in attributes, see the encoded CPE string live.
Validate — green/red validation with per-attribute error details. Toggle between basic and dictionary-strict modes.
Match — enter two CPE strings, see per-attribute comparison results and the overall name relation.