@vltpkg/query 1.0.0-rc.22 → 1.0.0-rc.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attribute.d.ts +14 -0
- package/dist/attribute.js +132 -0
- package/dist/combinator.d.ts +5 -0
- package/dist/combinator.js +111 -0
- package/dist/id.d.ts +5 -0
- package/dist/id.js +35 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +410 -0
- package/dist/parser.d.ts +14 -0
- package/dist/parser.js +92 -0
- package/dist/pseudo/abandoned.d.ts +6 -0
- package/dist/pseudo/abandoned.js +5 -0
- package/dist/pseudo/attr.d.ts +18 -0
- package/dist/pseudo/attr.js +57 -0
- package/dist/pseudo/built.d.ts +7 -0
- package/dist/pseudo/built.js +15 -0
- package/dist/pseudo/confused.d.ts +8 -0
- package/dist/pseudo/confused.js +18 -0
- package/dist/pseudo/cve.d.ts +12 -0
- package/dist/pseudo/cve.js +43 -0
- package/dist/pseudo/cwe.d.ts +12 -0
- package/dist/pseudo/cwe.js +42 -0
- package/dist/pseudo/debug.d.ts +6 -0
- package/dist/pseudo/debug.js +5 -0
- package/dist/pseudo/deprecated.d.ts +6 -0
- package/dist/pseudo/deprecated.js +5 -0
- package/dist/pseudo/dev.d.ts +5 -0
- package/dist/pseudo/dev.js +14 -0
- package/dist/pseudo/diff.d.ts +26 -0
- package/dist/pseudo/diff.js +75 -0
- package/dist/pseudo/dynamic.d.ts +6 -0
- package/dist/pseudo/dynamic.js +5 -0
- package/dist/pseudo/empty.d.ts +6 -0
- package/dist/pseudo/empty.js +13 -0
- package/dist/pseudo/entropic.d.ts +6 -0
- package/dist/pseudo/entropic.js +5 -0
- package/dist/pseudo/env.d.ts +6 -0
- package/dist/pseudo/env.js +5 -0
- package/dist/pseudo/eval.d.ts +6 -0
- package/dist/pseudo/eval.js +5 -0
- package/dist/pseudo/fs.d.ts +6 -0
- package/dist/pseudo/fs.js +5 -0
- package/dist/pseudo/helpers.d.ts +38 -0
- package/dist/pseudo/helpers.js +79 -0
- package/dist/pseudo/host.d.ts +19 -0
- package/dist/pseudo/host.js +79 -0
- package/dist/pseudo/hostname.d.ts +11 -0
- package/dist/pseudo/hostname.js +138 -0
- package/dist/pseudo/license.d.ts +12 -0
- package/dist/pseudo/license.js +74 -0
- package/dist/pseudo/link.d.ts +8 -0
- package/dist/pseudo/link.js +24 -0
- package/dist/pseudo/malware.d.ts +23 -0
- package/dist/pseudo/malware.js +186 -0
- package/dist/pseudo/minified.d.ts +6 -0
- package/dist/pseudo/minified.js +5 -0
- package/dist/pseudo/missing.d.ts +7 -0
- package/dist/pseudo/missing.js +14 -0
- package/dist/pseudo/native.d.ts +6 -0
- package/dist/pseudo/native.js +5 -0
- package/dist/pseudo/network.d.ts +6 -0
- package/dist/pseudo/network.js +5 -0
- package/dist/pseudo/obfuscated.d.ts +6 -0
- package/dist/pseudo/obfuscated.js +5 -0
- package/dist/pseudo/optional.d.ts +5 -0
- package/dist/pseudo/optional.js +14 -0
- package/dist/pseudo/outdated.d.ts +53 -0
- package/dist/pseudo/outdated.js +211 -0
- package/dist/pseudo/overridden.d.ts +7 -0
- package/dist/pseudo/overridden.js +16 -0
- package/dist/pseudo/path.d.ts +18 -0
- package/dist/pseudo/path.js +110 -0
- package/dist/pseudo/peer.d.ts +5 -0
- package/dist/pseudo/peer.js +14 -0
- package/dist/pseudo/prerelease.d.ts +17 -0
- package/dist/pseudo/prerelease.js +40 -0
- package/dist/pseudo/private.d.ts +6 -0
- package/dist/pseudo/private.js +15 -0
- package/dist/pseudo/prod.d.ts +5 -0
- package/dist/pseudo/prod.js +14 -0
- package/dist/pseudo/published.d.ts +39 -0
- package/dist/pseudo/published.js +179 -0
- package/dist/pseudo/registry.d.ts +10 -0
- package/dist/pseudo/registry.js +24 -0
- package/dist/pseudo/root.d.ts +6 -0
- package/dist/pseudo/root.js +17 -0
- package/dist/pseudo/scanned.d.ts +8 -0
- package/dist/pseudo/scanned.js +16 -0
- package/dist/pseudo/score.d.ts +15 -0
- package/dist/pseudo/score.js +132 -0
- package/dist/pseudo/scripts.d.ts +9 -0
- package/dist/pseudo/scripts.js +43 -0
- package/dist/pseudo/semver.d.ts +16 -0
- package/dist/pseudo/semver.js +166 -0
- package/dist/pseudo/severity.d.ts +14 -0
- package/dist/pseudo/severity.js +159 -0
- package/dist/pseudo/shell.d.ts +6 -0
- package/dist/pseudo/shell.js +5 -0
- package/dist/pseudo/shrinkwrap.d.ts +6 -0
- package/dist/pseudo/shrinkwrap.js +5 -0
- package/dist/pseudo/spec.d.ts +16 -0
- package/dist/pseudo/spec.js +101 -0
- package/dist/pseudo/squat.d.ts +14 -0
- package/dist/pseudo/squat.js +171 -0
- package/dist/pseudo/suspicious.d.ts +6 -0
- package/dist/pseudo/suspicious.js +5 -0
- package/dist/pseudo/tracker.d.ts +6 -0
- package/dist/pseudo/tracker.js +5 -0
- package/dist/pseudo/trivial.d.ts +6 -0
- package/dist/pseudo/trivial.js +5 -0
- package/dist/pseudo/type.d.ts +7 -0
- package/dist/pseudo/type.js +21 -0
- package/dist/pseudo/undesirable.d.ts +6 -0
- package/dist/pseudo/undesirable.js +5 -0
- package/dist/pseudo/unknown.d.ts +6 -0
- package/dist/pseudo/unknown.js +5 -0
- package/dist/pseudo/unmaintained.d.ts +6 -0
- package/dist/pseudo/unmaintained.js +5 -0
- package/dist/pseudo/unpopular.d.ts +6 -0
- package/dist/pseudo/unpopular.js +5 -0
- package/dist/pseudo/unstable.d.ts +6 -0
- package/dist/pseudo/unstable.js +5 -0
- package/dist/pseudo/workspace.d.ts +5 -0
- package/dist/pseudo/workspace.js +19 -0
- package/dist/pseudo.d.ts +5 -0
- package/dist/pseudo.js +366 -0
- package/dist/types.d.ts +124 -0
- package/dist/types.js +1 -0
- package/package.json +10 -10
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { splitDepID } from '@vltpkg/dep-id/browser';
|
|
2
|
+
import { getOptions, gitHostWebsites } from '@vltpkg/spec/browser';
|
|
3
|
+
import { asPostcssNodeWithChildren } from '@vltpkg/dss-parser';
|
|
4
|
+
import { error } from '@vltpkg/error-cause';
|
|
5
|
+
import { removeDanglingEdges, removeNode } from "./helpers.js";
|
|
6
|
+
import { parseInternals } from "./spec.js";
|
|
7
|
+
/**
|
|
8
|
+
* Given a named git host (e.g. "github"), resolve its hostname
|
|
9
|
+
* by looking up `gitHostWebsites` (e.g. "github.com"), or
|
|
10
|
+
* falling back to the `git-hosts` template URL.
|
|
11
|
+
*/
|
|
12
|
+
const resolveGitHostname = (namedHost, gitHosts) => {
|
|
13
|
+
// Check gitHostWebsites first (e.g. github -> github.com)
|
|
14
|
+
const website = gitHostWebsites[namedHost];
|
|
15
|
+
if (website) {
|
|
16
|
+
try {
|
|
17
|
+
return new URL(website).hostname;
|
|
18
|
+
/* c8 ignore next */
|
|
19
|
+
}
|
|
20
|
+
catch { }
|
|
21
|
+
}
|
|
22
|
+
// Fall back to parsing the git-hosts template URL
|
|
23
|
+
const template = gitHosts[namedHost];
|
|
24
|
+
if (template) {
|
|
25
|
+
try {
|
|
26
|
+
// Templates look like 'git+ssh://git@github.com:$1/$2.git'
|
|
27
|
+
// Replace colons after the host with / for URL parsing
|
|
28
|
+
const normalized = template
|
|
29
|
+
.replace(/^git\+/, '')
|
|
30
|
+
.replace(/:(\$)/, '/$1');
|
|
31
|
+
return new URL(normalized).hostname;
|
|
32
|
+
/* c8 ignore next */
|
|
33
|
+
}
|
|
34
|
+
catch { }
|
|
35
|
+
}
|
|
36
|
+
/* c8 ignore next */
|
|
37
|
+
return undefined;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the hostname for a git remote string.
|
|
41
|
+
* Named hosts: "github:user/repo" -> extract "github" and look up.
|
|
42
|
+
* Full URLs: "git+ssh://git@example.com/repo.git" -> parse URL.
|
|
43
|
+
*/
|
|
44
|
+
const getGitHostname = (gitRemote, gitHosts) => {
|
|
45
|
+
// Check if it's a named git host like "github:user/repo"
|
|
46
|
+
const colonIdx = gitRemote.indexOf(':');
|
|
47
|
+
if (colonIdx > 0) {
|
|
48
|
+
const possibleHost = gitRemote.slice(0, colonIdx);
|
|
49
|
+
// If this is a known named git host, resolve it
|
|
50
|
+
if (possibleHost in gitHosts) {
|
|
51
|
+
return resolveGitHostname(possibleHost, gitHosts);
|
|
52
|
+
}
|
|
53
|
+
// Could be a protocol URL like git+ssh://...
|
|
54
|
+
try {
|
|
55
|
+
const normalized = gitRemote
|
|
56
|
+
.replace(/^git\+/, '')
|
|
57
|
+
.replace(/^ssh:\/\/([^@]+@)?([^:/]+)[:/]/, 'ssh://$1$2/');
|
|
58
|
+
return new URL(normalized).hostname;
|
|
59
|
+
/* c8 ignore next */
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
62
|
+
}
|
|
63
|
+
/* c8 ignore start - git remotes always contain a colon */
|
|
64
|
+
// Try parsing as a plain URL
|
|
65
|
+
try {
|
|
66
|
+
return new URL(gitRemote).hostname;
|
|
67
|
+
}
|
|
68
|
+
catch { }
|
|
69
|
+
return undefined;
|
|
70
|
+
/* c8 ignore stop */
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* :hostname(str) Pseudo-Selector, matches only nodes whose
|
|
74
|
+
* upstream hostname matches the provided domain string.
|
|
75
|
+
*
|
|
76
|
+
* Examples:
|
|
77
|
+
* - :hostname("registry.npmjs.org") — default npm registry deps
|
|
78
|
+
* - :hostname("github.com") — github git deps
|
|
79
|
+
* - :hostname("example.com") — custom registry deps
|
|
80
|
+
*/
|
|
81
|
+
export const hostname = async (state) => {
|
|
82
|
+
let internals;
|
|
83
|
+
try {
|
|
84
|
+
internals = parseInternals(asPostcssNodeWithChildren(state.current).nodes);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
throw error('Failed to parse :hostname selector', {
|
|
88
|
+
cause: err,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const targetHostname = internals.specValue;
|
|
92
|
+
for (const node of state.partial.nodes) {
|
|
93
|
+
const tuple = splitDepID(node.id);
|
|
94
|
+
const type = tuple[0];
|
|
95
|
+
let nodeHostname;
|
|
96
|
+
switch (type) {
|
|
97
|
+
case 'registry': {
|
|
98
|
+
const registryName = tuple[1];
|
|
99
|
+
const options = getOptions(node.options);
|
|
100
|
+
// Look up the registry URL from registries map
|
|
101
|
+
const registryUrl = options.registries[registryName] ?? options.registry;
|
|
102
|
+
if (registryUrl) {
|
|
103
|
+
try {
|
|
104
|
+
nodeHostname = new URL(registryUrl).hostname;
|
|
105
|
+
/* c8 ignore next */
|
|
106
|
+
}
|
|
107
|
+
catch { }
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case 'git': {
|
|
112
|
+
const gitRemote = tuple[1];
|
|
113
|
+
const options = getOptions(node.options);
|
|
114
|
+
nodeHostname = getGitHostname(gitRemote, options['git-hosts']);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case 'remote': {
|
|
118
|
+
const url = tuple[1];
|
|
119
|
+
try {
|
|
120
|
+
nodeHostname = new URL(url).hostname;
|
|
121
|
+
/* c8 ignore next */
|
|
122
|
+
}
|
|
123
|
+
catch { }
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
// file and workspace deps are local — no hostname
|
|
127
|
+
default: {
|
|
128
|
+
nodeHostname = undefined;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (nodeHostname !== targetHostname) {
|
|
133
|
+
removeNode(state, node);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
removeDanglingEdges(state);
|
|
137
|
+
return state;
|
|
138
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ParserState } from '../types.ts';
|
|
2
|
+
import type { PostcssNode } from '@vltpkg/dss-parser';
|
|
3
|
+
export type LicenseKinds = 'unlicensed' | 'misc' | 'restricted' | 'ambiguous' | 'copyleft' | 'unknown' | 'none' | 'exception' | undefined;
|
|
4
|
+
export type LicenseAlertTypes = 'explicitlyUnlicensedItem' | 'miscLicenseIssues' | 'nonpermissiveLicense' | 'ambiguousClassifier' | 'copyleftLicense' | 'unidentifiedLicense' | 'noLicenseFound' | 'licenseException' | undefined;
|
|
5
|
+
export declare const isLicenseKind: (value?: string) => value is LicenseKinds;
|
|
6
|
+
export declare const asLicenseKind: (value?: string) => LicenseKinds;
|
|
7
|
+
export declare const parseInternals: (nodes: PostcssNode[]) => {
|
|
8
|
+
kind: LicenseKinds;
|
|
9
|
+
};
|
|
10
|
+
export declare const license: (state: ParserState) => Promise<ParserState & {
|
|
11
|
+
securityArchive: NonNullable<ParserState["securityArchive"]>;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { error } from '@vltpkg/error-cause';
|
|
2
|
+
import { asError } from '@vltpkg/types';
|
|
3
|
+
import { asPostcssNodeWithChildren, asStringNode, asTagNode, isStringNode, isTagNode, } from '@vltpkg/dss-parser';
|
|
4
|
+
import { assertSecurityArchive, removeDanglingEdges, removeNode, removeQuotes, } from "./helpers.js";
|
|
5
|
+
const kindsMap = new Map([
|
|
6
|
+
['unlicensed', 'explicitlyUnlicensedItem'],
|
|
7
|
+
['misc', 'miscLicenseIssues'],
|
|
8
|
+
['restricted', 'nonpermissiveLicense'],
|
|
9
|
+
['ambiguous', 'ambiguousClassifier'],
|
|
10
|
+
['copyleft', 'copyleftLicense'],
|
|
11
|
+
['unknown', 'unidentifiedLicense'],
|
|
12
|
+
['none', 'noLicenseFound'],
|
|
13
|
+
['exception', 'licenseException'],
|
|
14
|
+
[undefined, undefined],
|
|
15
|
+
]);
|
|
16
|
+
const kinds = new Set(kindsMap.keys());
|
|
17
|
+
export const isLicenseKind = (value) => kinds.has(value);
|
|
18
|
+
export const asLicenseKind = (value) => {
|
|
19
|
+
if (!isLicenseKind(value)) {
|
|
20
|
+
throw error('Expected a valid license kind', {
|
|
21
|
+
found: value,
|
|
22
|
+
validOptions: Array.from(kinds),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
};
|
|
27
|
+
export const parseInternals = (nodes) => {
|
|
28
|
+
let kind;
|
|
29
|
+
if (isStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])) {
|
|
30
|
+
kind = asLicenseKind(removeQuotes(asStringNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])
|
|
31
|
+
.value));
|
|
32
|
+
}
|
|
33
|
+
else if (isTagNode(asPostcssNodeWithChildren(nodes[0]).nodes[0])) {
|
|
34
|
+
kind = asLicenseKind(asTagNode(asPostcssNodeWithChildren(nodes[0]).nodes[0]).value);
|
|
35
|
+
}
|
|
36
|
+
return { kind };
|
|
37
|
+
};
|
|
38
|
+
export const license = async (state) => {
|
|
39
|
+
assertSecurityArchive(state, 'license');
|
|
40
|
+
let internals;
|
|
41
|
+
try {
|
|
42
|
+
internals = parseInternals(asPostcssNodeWithChildren(state.current).nodes);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (asError(err).message === 'Expected a query node') {
|
|
46
|
+
// No parameters provided - pseudo state form: match ANY license defined (not 'none')
|
|
47
|
+
for (const node of state.partial.nodes) {
|
|
48
|
+
const report = state.securityArchive.get(node.id);
|
|
49
|
+
// Exclude if no report or if it has 'noLicenseFound' alert
|
|
50
|
+
const exclude = !report?.alerts ||
|
|
51
|
+
report.alerts.some(alert => alert.type === 'noLicenseFound');
|
|
52
|
+
if (exclude) {
|
|
53
|
+
removeNode(state, node);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
removeDanglingEdges(state);
|
|
57
|
+
return state;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
throw error('Failed to parse :license selector', { cause: err });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const { kind } = internals;
|
|
64
|
+
const alertName = kindsMap.get(kind);
|
|
65
|
+
for (const node of state.partial.nodes) {
|
|
66
|
+
const report = state.securityArchive.get(node.id);
|
|
67
|
+
const exclude = !report?.alerts.some(alert => alert.type === alertName);
|
|
68
|
+
if (exclude) {
|
|
69
|
+
removeNode(state, node);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
removeDanglingEdges(state);
|
|
73
|
+
return state;
|
|
74
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ParserState } from '../types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* :link Pseudo-Selector, matches only nodes that are file links.
|
|
4
|
+
*
|
|
5
|
+
* It filters out any node that is not of type 'file' or nodes of 'file'
|
|
6
|
+
* type that ends with 'tar.gz' since these are local tarballs.
|
|
7
|
+
*/
|
|
8
|
+
export declare const link: (state: ParserState) => Promise<ParserState>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { splitDepID } from '@vltpkg/dep-id/browser';
|
|
2
|
+
import { removeNode } from "./helpers.js";
|
|
3
|
+
/**
|
|
4
|
+
* :link Pseudo-Selector, matches only nodes that are file links.
|
|
5
|
+
*
|
|
6
|
+
* It filters out any node that is not of type 'file' or nodes of 'file'
|
|
7
|
+
* type that ends with 'tar.gz' since these are local tarballs.
|
|
8
|
+
*/
|
|
9
|
+
export const link = async (state) => {
|
|
10
|
+
for (const node of state.partial.nodes) {
|
|
11
|
+
const [type, path] = splitDepID(node.id);
|
|
12
|
+
if (type !== 'file' || path.endsWith('tar.gz') || path === '.') {
|
|
13
|
+
removeNode(state, node);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
for (const edge of state.partial.edges) {
|
|
17
|
+
if (!edge.spec.file ||
|
|
18
|
+
edge.spec.file.endsWith('tar.gz') ||
|
|
19
|
+
edge.spec.file === '.') {
|
|
20
|
+
state.partial.edges.delete(edge);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return state;
|
|
24
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ParserState } from '../types.ts';
|
|
2
|
+
import type { PostcssNode } from '@vltpkg/dss-parser';
|
|
3
|
+
export type MalwareKinds = '0' | '1' | '2' | '3' | 'critical' | 'high' | 'medium' | 'low' | undefined;
|
|
4
|
+
export type MalwareAlertTypes = 'malware' | 'gptMalware' | 'gptSecurity' | 'gptAnomaly' | undefined;
|
|
5
|
+
export type MalwareComparator = '>' | '<' | '>=' | '<=' | undefined;
|
|
6
|
+
export declare const isMalwareKind: (value?: string) => value is MalwareKinds;
|
|
7
|
+
export declare const asMalwareKind: (value?: string) => MalwareKinds;
|
|
8
|
+
export declare const parseInternals: (nodes: PostcssNode[]) => {
|
|
9
|
+
kind: MalwareKinds;
|
|
10
|
+
comparator: MalwareComparator;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* :malware Pseudo-Selector, matches nodes with malware alerts.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* - :malware - matches malware with severity >= medium (critical, high, medium but not low)
|
|
17
|
+
* - :malware(critical) - matches specific malware kind
|
|
18
|
+
* - :malware(>1) - matches malware with severity greater than 1
|
|
19
|
+
* - :malware(">=medium") - matches malware with severity >= medium
|
|
20
|
+
*/
|
|
21
|
+
export declare const malware: (state: ParserState) => Promise<ParserState & {
|
|
22
|
+
securityArchive: NonNullable<ParserState["securityArchive"]>;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { error } from '@vltpkg/error-cause';
|
|
2
|
+
import { asPostcssNodeWithChildren, asStringNode, asTagNode, isStringNode, isTagNode, } from '@vltpkg/dss-parser';
|
|
3
|
+
import { assertSecurityArchive, removeDanglingEdges, removeNode, removeQuotes, } from "./helpers.js";
|
|
4
|
+
const kindsMap = new Map([
|
|
5
|
+
['critical', 'malware'],
|
|
6
|
+
['high', 'gptMalware'],
|
|
7
|
+
['medium', 'gptSecurity'],
|
|
8
|
+
['low', 'gptAnomaly'],
|
|
9
|
+
['0', 'malware'],
|
|
10
|
+
['1', 'gptMalware'],
|
|
11
|
+
['2', 'gptSecurity'],
|
|
12
|
+
['3', 'gptAnomaly'],
|
|
13
|
+
]);
|
|
14
|
+
// Map numerical values to their respective kinds for comparison operations
|
|
15
|
+
const kindLevelMap = new Map([
|
|
16
|
+
['critical', 0],
|
|
17
|
+
['high', 1],
|
|
18
|
+
['medium', 2],
|
|
19
|
+
['low', 3],
|
|
20
|
+
['0', 0],
|
|
21
|
+
['1', 1],
|
|
22
|
+
['2', 2],
|
|
23
|
+
['3', 3],
|
|
24
|
+
]);
|
|
25
|
+
const kinds = new Set(kindsMap.keys());
|
|
26
|
+
export const isMalwareKind = (value) => kinds.has(value);
|
|
27
|
+
export const asMalwareKind = (value) => {
|
|
28
|
+
if (!isMalwareKind(value)) {
|
|
29
|
+
throw error('Expected a valid malware kind', {
|
|
30
|
+
found: value,
|
|
31
|
+
validOptions: Array.from(kinds),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
};
|
|
36
|
+
export const parseInternals = (nodes) => {
|
|
37
|
+
// Handle case where no parameters are provided (parameterless :malware)
|
|
38
|
+
if (!nodes[0]) {
|
|
39
|
+
return { kind: undefined, comparator: undefined };
|
|
40
|
+
}
|
|
41
|
+
const selectorNode = asPostcssNodeWithChildren(nodes[0]);
|
|
42
|
+
if (!selectorNode.nodes[0]) {
|
|
43
|
+
return { kind: undefined, comparator: undefined };
|
|
44
|
+
}
|
|
45
|
+
let kindValue = '';
|
|
46
|
+
let comparator = undefined;
|
|
47
|
+
let kind;
|
|
48
|
+
// Parse the parameter (kind with optional comparator)
|
|
49
|
+
if (isStringNode(selectorNode.nodes[0])) {
|
|
50
|
+
kindValue = removeQuotes(asStringNode(selectorNode.nodes[0]).value);
|
|
51
|
+
}
|
|
52
|
+
else if (isTagNode(selectorNode.nodes[0])) {
|
|
53
|
+
kindValue = asTagNode(selectorNode.nodes[0]).value;
|
|
54
|
+
}
|
|
55
|
+
// Extract comparator if present
|
|
56
|
+
if (kindValue.startsWith('>=')) {
|
|
57
|
+
comparator = '>=';
|
|
58
|
+
kindValue = kindValue.substring(2);
|
|
59
|
+
}
|
|
60
|
+
else if (kindValue.startsWith('<=')) {
|
|
61
|
+
comparator = '<=';
|
|
62
|
+
kindValue = kindValue.substring(2);
|
|
63
|
+
}
|
|
64
|
+
else if (kindValue.startsWith('>')) {
|
|
65
|
+
comparator = '>';
|
|
66
|
+
kindValue = kindValue.substring(1);
|
|
67
|
+
}
|
|
68
|
+
else if (kindValue.startsWith('<')) {
|
|
69
|
+
comparator = '<';
|
|
70
|
+
kindValue = kindValue.substring(1);
|
|
71
|
+
}
|
|
72
|
+
// Validate the kind without comparator
|
|
73
|
+
if (!comparator) {
|
|
74
|
+
kind = asMalwareKind(kindValue);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// For comparisons, just make sure it's a valid numeric value or a valid kind
|
|
78
|
+
if (isMalwareKind(kindValue)) {
|
|
79
|
+
kind = kindValue;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
throw error('Expected a valid malware kind or number between 0-3', {
|
|
83
|
+
found: kindValue,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { kind, comparator };
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* :malware Pseudo-Selector, matches nodes with malware alerts.
|
|
91
|
+
*
|
|
92
|
+
* Usage:
|
|
93
|
+
* - :malware - matches malware with severity >= medium (critical, high, medium but not low)
|
|
94
|
+
* - :malware(critical) - matches specific malware kind
|
|
95
|
+
* - :malware(>1) - matches malware with severity greater than 1
|
|
96
|
+
* - :malware(">=medium") - matches malware with severity >= medium
|
|
97
|
+
*/
|
|
98
|
+
export const malware = async (state) => {
|
|
99
|
+
assertSecurityArchive(state, 'malware');
|
|
100
|
+
let internals;
|
|
101
|
+
try {
|
|
102
|
+
internals = parseInternals(asPostcssNodeWithChildren(state.current).nodes);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
throw error('Failed to parse :malware selector', { cause: err });
|
|
106
|
+
}
|
|
107
|
+
const { kind, comparator } = internals;
|
|
108
|
+
const alertName = comparator ? undefined : kindsMap.get(kind);
|
|
109
|
+
for (const node of state.partial.nodes) {
|
|
110
|
+
const report = state.securityArchive.get(node.id);
|
|
111
|
+
// Always exclude nodes that don't have security data or alerts
|
|
112
|
+
if (!report?.alerts || report.alerts.length === 0) {
|
|
113
|
+
removeNode(state, node);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const node of state.partial.nodes) {
|
|
117
|
+
const report = state.securityArchive.get(node.id);
|
|
118
|
+
let exclude = true;
|
|
119
|
+
if (report) {
|
|
120
|
+
if (kind === undefined && comparator === undefined) {
|
|
121
|
+
// Parameterless :malware - match malware alerts with severity >= medium (exclude low/gptAnomaly)
|
|
122
|
+
exclude = !report.alerts.some(alert => alert.type === 'malware' ||
|
|
123
|
+
alert.type === 'gptMalware' ||
|
|
124
|
+
alert.type === 'gptSecurity');
|
|
125
|
+
}
|
|
126
|
+
else if (comparator) {
|
|
127
|
+
// retrieve the value to compare against
|
|
128
|
+
const kindLevel = kindLevelMap.get(kind);
|
|
129
|
+
// the kindLevel value has already been validated at this point
|
|
130
|
+
// and thus can never return an undefined/falsy value but ts doesn't
|
|
131
|
+
// know about that, so we have the extra check here
|
|
132
|
+
/* c8 ignore next - impossible */
|
|
133
|
+
if (kindLevel == null)
|
|
134
|
+
break;
|
|
135
|
+
// Check each alert to find any that match our comparison criteria
|
|
136
|
+
for (const alert of report.alerts) {
|
|
137
|
+
// Get the numerical value of the alert type
|
|
138
|
+
const alertType = alert.type;
|
|
139
|
+
// retrieve a key to the current alert level to be compared against
|
|
140
|
+
const currentAlertLevelKey = [...kindsMap.entries()].find(([_, alertValue]) => alertValue === alertType)?.[0];
|
|
141
|
+
// perform the comparison based on the user-provided kindLevel
|
|
142
|
+
if (currentAlertLevelKey) {
|
|
143
|
+
const currentAlertLevel = kindLevelMap.get(currentAlertLevelKey);
|
|
144
|
+
/* c8 ignore next - impossible but ts doesn't know */
|
|
145
|
+
if (currentAlertLevel == null)
|
|
146
|
+
continue;
|
|
147
|
+
switch (comparator) {
|
|
148
|
+
case '>':
|
|
149
|
+
if (currentAlertLevel > kindLevel) {
|
|
150
|
+
exclude = false;
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
case '<':
|
|
154
|
+
if (currentAlertLevel < kindLevel) {
|
|
155
|
+
exclude = false;
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
case '>=':
|
|
159
|
+
if (currentAlertLevel >= kindLevel) {
|
|
160
|
+
exclude = false;
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
case '<=':
|
|
164
|
+
if (currentAlertLevel <= kindLevel) {
|
|
165
|
+
exclude = false;
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
// If we've found a match, no need to check other alerts
|
|
170
|
+
if (!exclude)
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
// Original exact match behavior
|
|
177
|
+
exclude = !report.alerts.some(alert => alert.type === alertName);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (exclude) {
|
|
181
|
+
removeNode(state, node);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
removeDanglingEdges(state);
|
|
185
|
+
return state;
|
|
186
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters out any node that does not have a **minifiedFile** report alert.
|
|
3
|
+
*/
|
|
4
|
+
export declare const minified: (state: import("../types.ts").ParserState) => Promise<import("../types.ts").ParserState & {
|
|
5
|
+
securityArchive: NonNullable<import("../types.ts").ParserState["securityArchive"]>;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ParserState } from '../types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* :missing Pseudo-Selector, matches only edges that are not linked to any node.
|
|
4
|
+
* It filters out any edges that have a 'to' property, keeping only dangling edges
|
|
5
|
+
* and clears all nodes from the result.
|
|
6
|
+
*/
|
|
7
|
+
export declare const missing: (state: ParserState) => Promise<ParserState>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* :missing Pseudo-Selector, matches only edges that are not linked to any node.
|
|
3
|
+
* It filters out any edges that have a 'to' property, keeping only dangling edges
|
|
4
|
+
* and clears all nodes from the result.
|
|
5
|
+
*/
|
|
6
|
+
export const missing = async (state) => {
|
|
7
|
+
for (const edge of state.partial.edges) {
|
|
8
|
+
if (edge.to) {
|
|
9
|
+
state.partial.edges.delete(edge);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
state.partial.nodes.clear();
|
|
13
|
+
return state;
|
|
14
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters out any node that does not have a **hasNativeCode** report alert.
|
|
3
|
+
*/
|
|
4
|
+
export declare const nativeParser: (state: import("../types.ts").ParserState) => Promise<import("../types.ts").ParserState & {
|
|
5
|
+
securityArchive: NonNullable<import("../types.ts").ParserState["securityArchive"]>;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters out any node that does not have a **networkAccess** report alert.
|
|
3
|
+
*/
|
|
4
|
+
export declare const network: (state: import("../types.ts").ParserState) => Promise<import("../types.ts").ParserState & {
|
|
5
|
+
securityArchive: NonNullable<import("../types.ts").ParserState["securityArchive"]>;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters out any node that does not have an **obfuscatedFile** report alert.
|
|
3
|
+
*/
|
|
4
|
+
export declare const obfuscated: (state: import("../types.ts").ParserState) => Promise<import("../types.ts").ParserState & {
|
|
5
|
+
securityArchive: NonNullable<import("../types.ts").ParserState["securityArchive"]>;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { removeEdge, removeUnlinkedNodes } from "./helpers.js";
|
|
2
|
+
/**
|
|
3
|
+
* :optional Pseudo-Selector will only match optional dependencies.
|
|
4
|
+
*/
|
|
5
|
+
export const optional = async (state) => {
|
|
6
|
+
// filter edges that aren't marked as optional
|
|
7
|
+
for (const edge of state.partial.edges) {
|
|
8
|
+
if (!edge.optional) {
|
|
9
|
+
removeEdge(state, edge);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
removeUnlinkedNodes(state);
|
|
13
|
+
return state;
|
|
14
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { NodeLike } from '@vltpkg/types';
|
|
2
|
+
import type { ParserState } from '../types.ts';
|
|
3
|
+
import type { PostcssNode } from '@vltpkg/dss-parser';
|
|
4
|
+
/**
|
|
5
|
+
* The possible values accepted by the :outdated() pseudo selector.
|
|
6
|
+
*/
|
|
7
|
+
export type OutdatedKinds = 'any' | 'major' | 'minor' | 'patch' | 'in-range' | 'out-of-range';
|
|
8
|
+
/**
|
|
9
|
+
* Result of the internal parsing of the :outdated() pseudo selector.
|
|
10
|
+
*/
|
|
11
|
+
export type OutdatedInternals = {
|
|
12
|
+
kind: OutdatedKinds;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Extracts a semver type from a version string.
|
|
16
|
+
*/
|
|
17
|
+
export type SemverTypeExtraction = (version: string) => number | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Checks if a string is a valid {@link OutdatedKinds}.
|
|
20
|
+
*/
|
|
21
|
+
export declare const isOutdatedKind: (value: string) => value is OutdatedKinds;
|
|
22
|
+
/**
|
|
23
|
+
* Asserts that a string is a valid {@link OutdatedKinds}.
|
|
24
|
+
*/
|
|
25
|
+
export declare const asOutdatedKind: (value: string) => OutdatedKinds;
|
|
26
|
+
/**
|
|
27
|
+
* Fetches the available versions of a package from the npm registry.
|
|
28
|
+
*/
|
|
29
|
+
export declare const retrieveRemoteVersions: (node: NodeLike, signal?: AbortSignal) => Promise<string[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Retrieves what kind of check the :outdated selector should perform.
|
|
32
|
+
*/
|
|
33
|
+
export declare const parseInternals: (nodes: PostcssNode[]) => OutdatedInternals;
|
|
34
|
+
/**
|
|
35
|
+
* Filter nodes by queueing up for removal those that are not outdated.
|
|
36
|
+
*/
|
|
37
|
+
export declare const queueNode: (state: ParserState, node: NodeLike, kind: OutdatedKinds) => Promise<NodeLike | undefined>;
|
|
38
|
+
/**
|
|
39
|
+
* Filters out nodes that are not outdated.
|
|
40
|
+
*
|
|
41
|
+
* The :outdated() pseudo selector supports one `type` argument,
|
|
42
|
+
* possible values are the following:
|
|
43
|
+
*
|
|
44
|
+
* - `any`: Selects all nodes that have a greater version available.
|
|
45
|
+
* - `major`: Selects all nodes that have a greater major version available.
|
|
46
|
+
* - `minor`: Selects all nodes that have a greater minor version available.
|
|
47
|
+
* - `patch`: Selects all nodes that have a greater patch version available.
|
|
48
|
+
* - `in-range`: Selects all nodes that have a parent node with a spec definition
|
|
49
|
+
* that satisfies one of the available remote versions.
|
|
50
|
+
* - `out-of-range`: Selects all nodes that have a parent node with a spec definition
|
|
51
|
+
* that does not satisfy any of the available remote versions.
|
|
52
|
+
*/
|
|
53
|
+
export declare const outdated: (state: ParserState) => Promise<ParserState>;
|