
Research
Malicious npm Package Brand-Squats TanStack to Exfiltrate Environment Variables
A brand-squatted TanStack npm package used postinstall scripts to steal .env files and exfiltrate developer secrets to an attacker-controlled endpoint.
A PURL (Package URL) parser and serializer, implementing the TC54 PURL specification.
npm install purl
purl [options] <input>
input - An npm package specifier or PURL string--check, -c - Validate the package exists on its registry and show latest version--json - Output only JSON to stdout (no colored PURL output)--help - Show help message--version - Show version number--json, outputs only JSON to stdout (useful for scripting)purl lodash@4.17.21 # outputs pkg:npm/lodash@4.17.21
purl @babel/core@7.0.0 # outputs pkg:npm/%40babel/core@7.0.0
purl express # outputs pkg:npm/express
purl 'pkg:pypi/requests@2.28' # outputs pkg:pypi/requests@2.28
purl -c lodash@4.17.21 # validates package exists, then outputs PURL
--check)npm, pypi, gem, cargo, nuget, hex, maven, composer, pub, hackage, cocoapods
import {
PURL,
parse,
stringify,
valid,
normalize,
eq,
compare,
type,
namespace,
name,
version,
qualifiers,
subpath,
fromNPM,
url,
validate,
validateTypes,
} from 'purl';
PURL classimport PURL from 'purl/purl';
const purl = new PURL('pkg:npm/lodash@4.17.21');
purl.type; // 'npm'
purl.namespace; // null
purl.name; // 'lodash'
purl.version; // '4.17.21'
purl.qualifiers; // null
purl.subpath; // null
String(purl); // 'pkg:npm/lodash@4.17.21'
// From components
const purl2 = new PURL({
type: 'npm',
namespace: '@babel',
name: 'core',
version: '7.0.0',
});
String(purl2); // 'pkg:npm/%40babel/core@7.0.0'
// Instance methods
purl.equals(other); // boolean
purl.compare(other); // -1 | 0 | 1
parse(purl)Parse a PURL string into a PURL object. Returns null if invalid.
import parse from 'purl/parse';
parse('pkg:npm/lodash@4.17.21'); // PURL instance
parse('invalid'); // null
stringify(components)Convert PURL components to a canonical PURL string.
import stringify from 'purl/stringify';
stringify({ type: 'npm', name: 'lodash', version: '4.17.21' });
// 'pkg:npm/lodash@4.17.21'
valid(purl)Returns the normalized PURL string if valid, null otherwise.
import valid from 'purl/valid';
valid('pkg:NPM/lodash@4.17.21'); // 'pkg:npm/lodash@4.17.21'
valid('invalid'); // null
normalize(purl)Normalize a PURL string. Throws if invalid.
import normalize from 'purl/normalize';
normalize('pkg:NPM/lodash@4.17.21'); // 'pkg:npm/lodash@4.17.21'
normalize('invalid'); // throws
eq(a, b)Compare two PURLs for equality.
import eq from 'purl/eq';
eq('pkg:npm/lodash@4.17.21', 'pkg:NPM/lodash@4.17.21'); // true
eq('pkg:npm/lodash@4.17.21', 'pkg:npm/lodash@4.17.20'); // false
compare(a, b)Compare two PURLs for sorting. Returns -1, 0, or 1.
import compare from 'purl/compare';
['pkg:npm/bbb', 'pkg:npm/aaa'].sort(compare);
// ['pkg:npm/aaa', 'pkg:npm/bbb']
Each accessor returns the component value or null if invalid/missing.
import type from 'purl/type';
import namespace from 'purl/namespace';
import name from 'purl/name';
import version from 'purl/version';
import qualifiers from 'purl/qualifiers';
import subpath from 'purl/subpath';
type('pkg:npm/lodash@4.17.21'); // 'npm'
namespace('pkg:npm/%40babel/core'); // '@babel'
name('pkg:npm/lodash@4.17.21'); // 'lodash'
version('pkg:npm/lodash@4.17.21'); // '4.17.21'
qualifiers('pkg:npm/foo?a=b'); // { a: 'b' }
subpath('pkg:npm/foo#lib/index.js'); // 'lib/index.js'
fromNPM(specifier)Convert an npm package specifier to a PURL object.
import fromNPM from 'purl/from-npm';
fromNPM('lodash@4.17.21'); // PURL for pkg:npm/lodash@4.17.21
fromNPM('@babel/core@7.0.0'); // PURL for pkg:npm/%40babel/core@7.0.0
fromNPM('lodash@^4.0.0'); // PURL for pkg:npm/lodash (no version for ranges)
url(purl)Get the registry URL for a PURL.
import url from 'purl/url';
url('pkg:npm/lodash@4.17.21'); // 'https://www.npmjs.com/package/lodash/v/4.17.21'
url('pkg:pypi/requests@2.28.0'); // 'https://pypi.org/project/requests/2.28.0/'
url('pkg:github/ljharb/qs@6.11.0'); // 'https://github.com/ljharb/qs/tree/6.11.0'
validate(purl)Validate a PURL against its package registry. Returns a promise.
import validate, { supportedTypes } from 'purl/validate';
const result = await validate('pkg:npm/lodash@4.17.21');
// { valid: true, latestVersion: '4.17.21' }
const result2 = await validate('pkg:npm/nonexistent-package-xyz');
// { valid: false, error: 'Package "nonexistent-package-xyz" not found on npm', latestVersion: null }
supportedTypes; // ['npm', 'pypi', 'gem', 'cargo', 'nuget', 'hex', 'maven', 'composer', 'pub', 'hackage', 'cocoapods']
Clone the repo, npm install, and run npm test.
Prior to v2, purl was a different package.
v0 was just a placeholder, but v1 can be found both on archive.org and on the original branch of this repo.
FAQs
a PURL ( https://tc54.org/purl/ ) parser and serializer
The npm package purl receives a total of 26,833 weekly downloads. As such, purl popularity was classified as popular.
We found that purl demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A brand-squatted TanStack npm package used postinstall scripts to steal .env files and exfiltrate developer secrets to an attacker-controlled endpoint.

Research
Compromised SAP CAP npm packages download and execute unverified binaries, creating urgent supply chain risk for affected developers and CI/CD environments.

Company News
Socket has acquired Secure Annex to expand extension security across browsers, IDEs, and AI tools.