@underpostnet/cyberia 3.3.77 → 3.4.0
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/.env.example +1 -0
- package/.github/workflows/coverall.cyberia.ci.yml +12 -3
- package/.github/workflows/cyberia-client.cd.yml +2 -1
- package/.github/workflows/cyberia-server.cd.yml +2 -1
- package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
- package/.github/workflows/engine-cyberia.ci.yml +6 -0
- package/.github/workflows/ghpkg.ci.yml +19 -2
- package/.github/workflows/gitlab.ci.yml +8 -6
- package/.github/workflows/hardhat.ci.yml +4 -1
- package/.github/workflows/npmpkg.ci.yml +9 -5
- package/.github/workflows/publish.ci.yml +21 -12
- package/.github/workflows/publish.cyberia.ci.yml +25 -14
- package/.github/workflows/pwa-microservices-template-page.cd.yml +4 -2
- package/CHANGELOG.md +75 -140
- package/CLI-HELP.md +28 -1
- package/Dockerfile +1 -1
- package/Dockerfile.dev +1 -1
- package/Dockerfile.test +1 -1
- package/README.md +1 -1
- package/bin/build.js +2 -4
- package/bin/cyberia.js +4 -4
- package/bin/index.js +4 -4
- package/compose.env +1 -0
- package/conf.js +1 -1042
- package/deploy/cyberia-client/deploy.sh +13 -11
- package/deploy/cyberia-server/deploy.sh +13 -11
- package/deploy/dd-cyberia/sync-deploy.sh +7 -28
- package/deploy/lib/config.sh +1 -1
- package/deploy/lib/github-actions-logging.sh +3 -2
- package/deploy/pwa-microservices-template/deploy.sh +7 -1
- package/deployment.yaml +1 -1
- package/docker-compose.yml +7 -7
- package/docs/coverage/cyberia/api/cyberia-instance/cyberia-fallback-capture.js.html +1 -1
- package/docs/coverage/cyberia/api/cyberia-instance/index.html +1 -1
- package/docs/coverage/cyberia/api/cyberia-server-defaults/cyberia-server-defaults.js.html +1 -1
- package/docs/coverage/cyberia/api/cyberia-server-defaults/index.html +1 -1
- package/docs/coverage/cyberia/api/object-layer/index.html +1 -1
- package/docs/coverage/cyberia/api/object-layer/object-layer.model.js.html +1 -1
- package/docs/coverage/cyberia/index.html +1 -1
- package/docs/coverage/cyberia/projects/cyberia/atlas-sprite-sheet-generator.js.html +1 -1
- package/docs/coverage/cyberia/projects/cyberia/index.html +1 -1
- package/docs/coverage/cyberia/projects/cyberia/instance-backup.js.html +1 -1
- package/docs/coverage/cyberia/projects/cyberia/instance-data.js.html +1 -1
- package/docs/coverage/cyberia/projects/cyberia/shape-generator.js.html +1 -1
- package/docs/coverage/cyberia/projects/cyberia/stat-balance.js.html +1 -1
- package/hardhat/package-lock.json +6 -6
- package/hardhat/package.json +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/package.json +19 -17
- package/scripts/test-monitor.sh +1 -1
- package/src/api/file/file.router.js +7 -1
- package/src/cli/baremetal.js +1 -1
- package/src/cli/docker-compose.js +1 -1
- package/src/cli/index.js +36 -0
- package/src/cli/kubectl.js +4 -3
- package/src/cli/release.js +2 -0
- package/src/cli/repository.js +7 -2
- package/src/cli/run.js +1 -1
- package/src/cli/secrets.js +393 -200
- package/src/cli/socketsecurity.js +260 -0
- package/src/client/components/core/CssCore.js +3 -0
- package/src/client/components/core/Modal.js +160 -36
- package/src/client/components/core/SearchBox.js +0 -1
- package/src/client/components/underpost/AppShellUnderpost.js +13 -1
- package/src/client/components/underpost/CssUnderpost.js +15 -2
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +8 -0
- package/src/client/public/cyberia-docs/CYBERIA.md +1 -1
- package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +54 -56
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +30 -29
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +54 -25
- package/src/client/ssr/head/Css.js +12 -10
- package/src/client/ssr/views/Cyberia404.js +6 -10
- package/src/client/ssr/views/NotFound.js +75 -0
- package/src/client-builder/client-bundle.js +2 -2
- package/src/index.js +13 -1
- package/src/runtime/engine-cyberia/Dockerfile +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.test +1 -1
- package/src/runtime/engine-cyberia/compose.env +1 -0
- package/src/runtime/engine-cyberia/docker-compose.yml +7 -7
- package/src/server/build/catalog.js +1 -0
- package/src/server/build/execution.js +19 -5
- package/src/server/build/package.js +166 -3
- package/src/server/build/testing.js +2 -1
- package/src/server/runtime/conf.js +21 -0
- package/src/server/runtime/config-scope.js +2 -0
- package/src/server/runtime/process.js +4 -3
- package/src/server/security/socketsecurity.js +1735 -0
- package/test/integration/infra/1-security/config-scope.test.js +3 -0
- package/test/integration/infra/1-security/socketsecurity.test.js +1041 -0
- package/test/integration/infra/1-security/sops-secret-store.test.js +201 -71
- package/test/unit/build-template.test.js +24 -0
- package/test/unit/deploy-log-table.test.js +38 -0
- package/test/unit/execution-profiles.test.js +1 -0
- package/test/unit/package.test.js +118 -0
|
@@ -0,0 +1,1041 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import nodePath from 'node:path';
|
|
7
|
+
import SocketSecurityService, {
|
|
8
|
+
SEVERITIES,
|
|
9
|
+
alertCategoryFactory,
|
|
10
|
+
alertFindingsFactory,
|
|
11
|
+
publishedPackagesFactory,
|
|
12
|
+
packageAlerts,
|
|
13
|
+
artifactNameFactory,
|
|
14
|
+
categoryCountsFactory,
|
|
15
|
+
childEnvFactory,
|
|
16
|
+
deferredFactory,
|
|
17
|
+
findingsFactory,
|
|
18
|
+
ghsaIdFactory,
|
|
19
|
+
locationLabelFactory,
|
|
20
|
+
npmAuditSummaryFactory,
|
|
21
|
+
orgArgsFactory,
|
|
22
|
+
ownPackageNamesFactory,
|
|
23
|
+
parseJsonOutput,
|
|
24
|
+
patchApply,
|
|
25
|
+
patchManifestFactory,
|
|
26
|
+
patchScan,
|
|
27
|
+
patchScanSummaryFactory,
|
|
28
|
+
patchSelectionFactory,
|
|
29
|
+
patchSetup,
|
|
30
|
+
purlPackageFactory,
|
|
31
|
+
reachabilityFactory,
|
|
32
|
+
reportViolates,
|
|
33
|
+
runSocket,
|
|
34
|
+
scan,
|
|
35
|
+
scanReportSummaryFactory,
|
|
36
|
+
securityReportFactory,
|
|
37
|
+
securityReportMarkdownFactory,
|
|
38
|
+
severityCountsFactory,
|
|
39
|
+
socketCommandFactory,
|
|
40
|
+
socketEnvFactory,
|
|
41
|
+
socketResultFactory,
|
|
42
|
+
sourceLocationFactory,
|
|
43
|
+
sourceTasksFactory,
|
|
44
|
+
writeSecurityReports,
|
|
45
|
+
} from '../../../../src/server/security/socketsecurity.js';
|
|
46
|
+
|
|
47
|
+
const tempRoot = () => fs.mkdtempSync(nodePath.join(os.tmpdir(), 'socketsecurity-'));
|
|
48
|
+
|
|
49
|
+
// An executor that answers every command with one result and records what it was asked.
|
|
50
|
+
const executorFactory = (result = { code: 0, stdout: '', stderr: '' }) => {
|
|
51
|
+
const calls = [];
|
|
52
|
+
const execute = (command, options = {}) => {
|
|
53
|
+
calls.push({ command, options });
|
|
54
|
+
return options.stdout ? result.stdout : result;
|
|
55
|
+
};
|
|
56
|
+
return { calls, execute };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const npmAuditFixture = {
|
|
60
|
+
metadata: {
|
|
61
|
+
vulnerabilities: { info: 0, low: 0, moderate: 1, high: 1, critical: 0, total: 2 },
|
|
62
|
+
dependencies: { prod: 10, dev: 3, total: 13 },
|
|
63
|
+
},
|
|
64
|
+
vulnerabilities: {
|
|
65
|
+
minimist: {
|
|
66
|
+
name: 'minimist',
|
|
67
|
+
severity: 'moderate',
|
|
68
|
+
isDirect: false,
|
|
69
|
+
via: [
|
|
70
|
+
{
|
|
71
|
+
title: 'Prototype Pollution in minimist',
|
|
72
|
+
url: 'https://github.com/advisories/GHSA-xvch-5gv4-984h',
|
|
73
|
+
severity: 'moderate',
|
|
74
|
+
range: '<1.2.6',
|
|
75
|
+
cwe: ['CWE-1321'],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
range: '<1.2.6',
|
|
79
|
+
fixAvailable: { name: 'tool', version: '9.0.0', isSemVerMajor: true },
|
|
80
|
+
},
|
|
81
|
+
axios: {
|
|
82
|
+
name: 'axios',
|
|
83
|
+
severity: 'high',
|
|
84
|
+
isDirect: true,
|
|
85
|
+
via: [
|
|
86
|
+
{
|
|
87
|
+
title: 'Server-Side Request Forgery in axios',
|
|
88
|
+
url: 'https://github.com/advisories/GHSA-8hc4-vh64-cxmj',
|
|
89
|
+
severity: 'high',
|
|
90
|
+
range: '<=1.7.3',
|
|
91
|
+
},
|
|
92
|
+
'follow-redirects',
|
|
93
|
+
],
|
|
94
|
+
range: '<=1.7.3',
|
|
95
|
+
fixAvailable: true,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const patchScanFixture = {
|
|
101
|
+
canAccessPaidPatches: false,
|
|
102
|
+
freePatches: 2,
|
|
103
|
+
paidPatches: 0,
|
|
104
|
+
scannedPackages: 40,
|
|
105
|
+
packagesWithPatches: 2,
|
|
106
|
+
status: 'success',
|
|
107
|
+
totalPatches: 2,
|
|
108
|
+
packages: [
|
|
109
|
+
{
|
|
110
|
+
purl: 'pkg:npm/semver@7.3.5',
|
|
111
|
+
patches: [
|
|
112
|
+
{
|
|
113
|
+
cveIds: ['CVE-2022-25883'],
|
|
114
|
+
ghsaIds: ['GHSA-c2qf-rxjj-qqgw'],
|
|
115
|
+
purl: 'pkg:npm/semver@7.3.5',
|
|
116
|
+
severity: 'MODERATE',
|
|
117
|
+
tier: 'free',
|
|
118
|
+
title: 'semver vulnerable to Regular Expression Denial of Service',
|
|
119
|
+
uuid: 'bef9d52f-74ea-4eb0-9992-86e981170794',
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
purl: 'pkg:npm/axios@1.7.3',
|
|
125
|
+
patches: [
|
|
126
|
+
{
|
|
127
|
+
cveIds: ['CVE-2024-39338'],
|
|
128
|
+
ghsaIds: ['GHSA-8hc4-vh64-cxmj'],
|
|
129
|
+
purl: 'pkg:npm/axios@1.7.3',
|
|
130
|
+
severity: 'HIGH',
|
|
131
|
+
tier: 'free',
|
|
132
|
+
title: 'Server-Side Request Forgery in axios',
|
|
133
|
+
uuid: '211d430d-caac-46a9-9303-eb257430429c',
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const scanReportFixture = {
|
|
141
|
+
healthy: false,
|
|
142
|
+
orgSlug: 'acme',
|
|
143
|
+
scanId: '000aaaa1-0000-0a0a-00a0-00a0000000a0',
|
|
144
|
+
options: { fold: 'version', reportLevel: 'warn' },
|
|
145
|
+
alerts: {
|
|
146
|
+
npm: {
|
|
147
|
+
'left-pad': {
|
|
148
|
+
'1.3.0': {
|
|
149
|
+
type: 'installScripts',
|
|
150
|
+
policy: 'error',
|
|
151
|
+
url: 'https://socket.dev/npm/package/left-pad',
|
|
152
|
+
manifest: ['package.json'],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
axios: {
|
|
156
|
+
'1.7.3': {
|
|
157
|
+
type: 'cve',
|
|
158
|
+
policy: 'warn',
|
|
159
|
+
url: 'https://socket.dev/npm/package/axios',
|
|
160
|
+
manifest: ['package-lock.json'],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const factsFixture = {
|
|
168
|
+
tier1ReachabilityScanId: 'reach-1',
|
|
169
|
+
components: [
|
|
170
|
+
{
|
|
171
|
+
name: 'axios',
|
|
172
|
+
version: '1.7.3',
|
|
173
|
+
reachability: [{ ghsa_id: 'GHSA-8hc4-vh64-cxmj', reachability: [{ type: 'reachable', workspacePath: '.' }] }],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: 'minimist',
|
|
177
|
+
version: '1.2.5',
|
|
178
|
+
reachability: [
|
|
179
|
+
{ ghsa_id: 'GHSA-xvch-5gv4-984h', reachability: [{ type: 'unreachable', workspacePath: '.' }] },
|
|
180
|
+
{ ghsa_id: 'GHSA-vh95-rmgr-6w4m', reachability: [{ type: 'error', workspacePath: '.' }] },
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const manifestFixture = {
|
|
187
|
+
patches: {
|
|
188
|
+
'pkg:npm/axios@1.7.3': {
|
|
189
|
+
uuid: '211d430d-caac-46a9-9303-eb257430429c',
|
|
190
|
+
files: { 'package/lib/adapters/http.js': { beforeHash: 'a', afterHash: 'b' } },
|
|
191
|
+
vulnerabilities: {
|
|
192
|
+
'GHSA-8hc4-vh64-cxmj': { cves: ['CVE-2024-39338'], summary: 'SSRF in axios', severity: 'HIGH' },
|
|
193
|
+
},
|
|
194
|
+
license: 'MIT',
|
|
195
|
+
tier: 'free',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const PROBE_SOURCE = 'const a = 1;\nconst b = 2;\nconst key = process.env.KEY;\n';
|
|
201
|
+
|
|
202
|
+
// A checkout whose own package is published as `probe` and `@acme/probe`.
|
|
203
|
+
const probeRoot = () => {
|
|
204
|
+
const root = tempRoot();
|
|
205
|
+
fs.outputFileSync(nodePath.join(root, 'src/cli/probe.js'), PROBE_SOURCE);
|
|
206
|
+
return root;
|
|
207
|
+
};
|
|
208
|
+
const OWN_PACKAGES = ['probe', '@acme/probe'];
|
|
209
|
+
|
|
210
|
+
const artifactsFixture = [
|
|
211
|
+
{
|
|
212
|
+
type: 'npm',
|
|
213
|
+
name: 'left-pad',
|
|
214
|
+
version: '1.3.0',
|
|
215
|
+
alerts: [{ type: 'installScripts', severity: 'high', file: 'package/package.json' }],
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
type: 'npm',
|
|
219
|
+
name: 'axios',
|
|
220
|
+
version: '1.7.3',
|
|
221
|
+
alerts: [
|
|
222
|
+
{ type: 'cve', severity: 'high', file: 'package/lib/adapters/http.js' },
|
|
223
|
+
{ type: 'networkAccess', severity: 'low', file: 'package/lib/a.js' },
|
|
224
|
+
{ type: 'networkAccess', severity: 'middle', file: 'package/lib/b.js' },
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
type: 'npm',
|
|
229
|
+
namespace: '@acme',
|
|
230
|
+
name: 'probe',
|
|
231
|
+
version: '1.0.0',
|
|
232
|
+
alerts: [
|
|
233
|
+
{ type: 'envVars', severity: 'low', file: 'package/src/cli/probe.js', start: PROBE_SOURCE.indexOf('process') },
|
|
234
|
+
{ type: 'urlStrings', severity: 'middle', file: 'package/src/client/public/app.js', start: 5 },
|
|
235
|
+
{ type: 'gptSecurity', severity: 'high', file: 'src/cli/probe.js', start: 0 },
|
|
236
|
+
{ type: 'filesystemAccess', severity: 'low', file: 'package/../../etc/passwd', start: 0 },
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
type: 'npm',
|
|
241
|
+
name: 'probe',
|
|
242
|
+
version: '0.9.0',
|
|
243
|
+
alerts: [{ type: 'shellAccess', severity: 'critical', file: 'package/src/cli/probe.js', start: 12 }],
|
|
244
|
+
},
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
describe('Socket security configuration', () => {
|
|
248
|
+
it('provides one canonical class API with named aliases', () => {
|
|
249
|
+
expect(SocketSecurityService.socketEnvFactory).to.equal(socketEnvFactory);
|
|
250
|
+
expect(SocketSecurityService.runSocket).to.equal(runSocket);
|
|
251
|
+
expect(SocketSecurityService.SEVERITIES).to.equal(SEVERITIES);
|
|
252
|
+
expect(SocketSecurityService.PREPARE_SCRIPT).to.equal('node bin socketsecurity --patch-apply');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('resolves the three Socket variables through the environment reader', () => {
|
|
256
|
+
const values = { SOCKET_CLI_API_TOKEN: 'sktsec_x', SOCKET_CLI_ORG_SLUG: 'acme', SOCKET_CLI_ACCEPT_RISKS: '1' };
|
|
257
|
+
expect(socketEnvFactory((key) => values[key] ?? '')).to.deep.equal({
|
|
258
|
+
token: 'sktsec_x',
|
|
259
|
+
orgSlug: 'acme',
|
|
260
|
+
acceptRisks: '1',
|
|
261
|
+
});
|
|
262
|
+
expect(socketEnvFactory(() => '')).to.deep.equal({ token: '', orgSlug: '', acceptRisks: '' });
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('forwards only the values that are set into the child environment', () => {
|
|
266
|
+
const env = childEnvFactory({ token: 'sktsec_x', orgSlug: '', acceptRisks: '' }, { PATH: '/usr/bin' });
|
|
267
|
+
expect(env).to.deep.equal({ PATH: '/usr/bin', SOCKET_CLI_API_TOKEN: 'sktsec_x' });
|
|
268
|
+
expect(orgArgsFactory({ orgSlug: 'acme' })).to.deep.equal(['--org', 'acme']);
|
|
269
|
+
expect(orgArgsFactory({})).to.deep.equal([]);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe('Socket command execution', () => {
|
|
274
|
+
it('quotes every argument of a command line', () => {
|
|
275
|
+
expect(socketCommandFactory(['patch', 'get', "it's"], { binary: '/x/.bin/socket' })).to.equal(
|
|
276
|
+
"'/x/.bin/socket' 'patch' 'get' 'it'\\''s'",
|
|
277
|
+
);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('parses the JSON document after the lines printed before it', () => {
|
|
281
|
+
expect(parseJsonOutput('No SOCKET_API_TOKEN set.\n{\n "status": "success"\n}\n')).to.deep.equal({
|
|
282
|
+
status: 'success',
|
|
283
|
+
});
|
|
284
|
+
expect(parseJsonOutput('[1, 2]')).to.deep.equal([1, 2]);
|
|
285
|
+
expect(parseJsonOutput('nothing here')).to.equal(null);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('normalizes the CLI envelope and the patch status objects', () => {
|
|
289
|
+
expect(socketResultFactory({ code: 0, stdout: '{"ok":true,"data":{"id":"s1"}}' })).to.deep.equal({
|
|
290
|
+
ok: true,
|
|
291
|
+
code: 0,
|
|
292
|
+
data: { id: 's1' },
|
|
293
|
+
message: '',
|
|
294
|
+
});
|
|
295
|
+
expect(
|
|
296
|
+
socketResultFactory({ code: 1, stdout: '{"ok":false,"message":"Auth Error","cause":"no token"}' }),
|
|
297
|
+
).to.include({
|
|
298
|
+
ok: false,
|
|
299
|
+
message: 'Auth Error: no token',
|
|
300
|
+
});
|
|
301
|
+
expect(socketResultFactory({ code: 0, stdout: '{"status":"no_manifest"}' })).to.include({ ok: true });
|
|
302
|
+
expect(socketResultFactory({ code: 0, stdout: '{"status":"error","error":"Manifest not found"}' })).to.include({
|
|
303
|
+
ok: false,
|
|
304
|
+
message: 'Manifest not found',
|
|
305
|
+
});
|
|
306
|
+
expect(socketResultFactory({ code: 2, stdout: '', stderr: 'boom\nlast line' })).to.include({
|
|
307
|
+
ok: false,
|
|
308
|
+
message: 'last line',
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('reports an absent CLI without executing anything', () => {
|
|
313
|
+
const { calls, execute } = executorFactory();
|
|
314
|
+
const result = runSocket(['patch', 'list'], { root: '/nowhere', execute, binary: '' });
|
|
315
|
+
expect(result).to.include({ ok: false, unavailable: true, code: 127 });
|
|
316
|
+
expect(calls).to.be.empty;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('runs in the checkout with the token in the environment, never on the command line', () => {
|
|
320
|
+
const { calls, execute } = executorFactory({ code: 0, stdout: '{"ok":true,"data":{}}' });
|
|
321
|
+
const env = { token: 'sktsec_secret', orgSlug: 'acme', acceptRisks: '' };
|
|
322
|
+
const result = runSocket(['scan', 'create', '--json'], { root: '/repo', execute, env, binary: '/bin/socket' });
|
|
323
|
+
expect(result.ok).to.equal(true);
|
|
324
|
+
expect(calls).to.have.length(1);
|
|
325
|
+
expect(calls[0].command).to.equal("'/bin/socket' 'scan' 'create' '--json'");
|
|
326
|
+
expect(calls[0].command).to.not.include('sktsec_secret');
|
|
327
|
+
expect(calls[0].options).to.include({ cwd: '/repo', silentOnError: true, silent: true });
|
|
328
|
+
expect(calls[0].options.env).to.include({ SOCKET_CLI_API_TOKEN: 'sktsec_secret', SOCKET_CLI_ORG_SLUG: 'acme' });
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('npm audit summary', () => {
|
|
333
|
+
it('orders findings by severity and labels the fix each one has', () => {
|
|
334
|
+
const summary = npmAuditSummaryFactory(npmAuditFixture);
|
|
335
|
+
expect(summary.counts).to.include({ high: 1, moderate: 1, total: 2 });
|
|
336
|
+
expect(summary.dependencies).to.deep.equal({ prod: 10, dev: 3, total: 13 });
|
|
337
|
+
expect(summary.findings.map(({ name }) => name)).to.deep.equal(['axios', 'minimist']);
|
|
338
|
+
expect(summary.findings[0]).to.include({ direct: true, fix: 'update' });
|
|
339
|
+
expect(summary.findings[0].via).to.deep.equal(['follow-redirects']);
|
|
340
|
+
expect(summary.findings[1].fix).to.equal('tool@9.0.0 (major)');
|
|
341
|
+
expect(summary.findings[1].advisories[0].cwe).to.deep.equal(['CWE-1321']);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('summarizes an empty document as a clean audit', () => {
|
|
345
|
+
expect(npmAuditSummaryFactory({})).to.deep.equal({
|
|
346
|
+
counts: { critical: 0, high: 0, moderate: 0, low: 0, info: 0, total: 0 },
|
|
347
|
+
dependencies: {},
|
|
348
|
+
findings: [],
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe('Socket scan and reachability', () => {
|
|
354
|
+
it('flattens the folded policy report into one row per alert', () => {
|
|
355
|
+
const summary = scanReportSummaryFactory(scanReportFixture);
|
|
356
|
+
expect(summary).to.include({
|
|
357
|
+
healthy: false,
|
|
358
|
+
scanId: scanReportFixture.scanId,
|
|
359
|
+
url: `https://socket.dev/dashboard/org/acme/sbom/${scanReportFixture.scanId}`,
|
|
360
|
+
});
|
|
361
|
+
expect(summary.alerts).to.have.length(2);
|
|
362
|
+
expect(summary.alerts[0]).to.include({ ecosystem: 'npm', package: 'left-pad', version: '1.3.0', policy: 'error' });
|
|
363
|
+
expect(scanReportSummaryFactory(null)).to.include({ healthy: null, url: '' });
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('reads the reachability facts into reachable, unreachable and undetermined results', () => {
|
|
367
|
+
const reachability = reachabilityFactory(factsFixture);
|
|
368
|
+
expect(reachability).to.include({ available: true, analyzed: 2 });
|
|
369
|
+
expect(reachability.counts).to.deep.equal({ reachable: 1, unreachable: 1, undetermined: 1 });
|
|
370
|
+
expect(reachability.results.map(({ reachable }) => reachable)).to.deep.equal([true, false, null]);
|
|
371
|
+
expect(reachabilityFactory(null)).to.include({ available: false, analyzed: 0 });
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('creates the scan, reads its artifacts, and consumes the facts file the reachability scan leaves behind', () => {
|
|
375
|
+
const root = tempRoot();
|
|
376
|
+
const factsPath = nodePath.join(root, SocketSecurityService.FACTS_PATH);
|
|
377
|
+
const results = [
|
|
378
|
+
{ code: 1, stdout: JSON.stringify({ ok: true, data: scanReportFixture }) },
|
|
379
|
+
{ code: 0, stdout: JSON.stringify({ ok: true, data: artifactsFixture }) },
|
|
380
|
+
];
|
|
381
|
+
const calls = [];
|
|
382
|
+
// A leftover facts file would be taken as pre-generated input, so it must be gone by the time the CLI runs.
|
|
383
|
+
fs.writeJsonSync(factsPath, { components: [] });
|
|
384
|
+
const execute = (command, options) => {
|
|
385
|
+
calls.push({ command, options, staleFacts: fs.existsSync(factsPath) });
|
|
386
|
+
if (calls.length === 1) fs.writeJsonSync(factsPath, factsFixture);
|
|
387
|
+
return results[calls.length - 1];
|
|
388
|
+
};
|
|
389
|
+
const result = scan({
|
|
390
|
+
root,
|
|
391
|
+
execute,
|
|
392
|
+
env: { token: 'sktsec_x', orgSlug: 'acme' },
|
|
393
|
+
reach: true,
|
|
394
|
+
binary: '/bin/socket',
|
|
395
|
+
});
|
|
396
|
+
expect(calls[0].staleFacts).to.equal(false);
|
|
397
|
+
expect(calls[0].command).to.include("'--report-level' 'defer'");
|
|
398
|
+
expect(calls[0].command).to.include("'--reach' '--reach-retain-facts-file'");
|
|
399
|
+
expect(calls[0].command).to.include("'--org' 'acme' '.'");
|
|
400
|
+
expect(calls[0].options.silent).to.equal(false);
|
|
401
|
+
expect(calls[1].command).to.equal(
|
|
402
|
+
`'/bin/socket' 'scan' 'view' '${scanReportFixture.scanId}' '--json' '--no-banner' '--org' 'acme'`,
|
|
403
|
+
);
|
|
404
|
+
expect(result).to.include({ ok: true });
|
|
405
|
+
expect(result.summary.healthy).to.equal(false);
|
|
406
|
+
expect(result.artifacts).to.deep.equal(artifactsFixture);
|
|
407
|
+
expect(result.reachability.counts.reachable).to.equal(1);
|
|
408
|
+
expect(result.facts).to.deep.equal(factsFixture);
|
|
409
|
+
expect(fs.existsSync(factsPath)).to.equal(false);
|
|
410
|
+
fs.removeSync(root);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('reports a scan whose artifacts cannot be read as failed', () => {
|
|
414
|
+
const results = [
|
|
415
|
+
{ code: 0, stdout: JSON.stringify({ ok: true, data: scanReportFixture }) },
|
|
416
|
+
{ code: 1, stdout: JSON.stringify({ ok: false, message: 'Scan results not ready', cause: 'retry' }) },
|
|
417
|
+
];
|
|
418
|
+
let call = 0;
|
|
419
|
+
const result = scan({ root: tempRoot(), execute: () => results[call++], env: {}, binary: '/bin/socket' });
|
|
420
|
+
expect(result).to.include({ ok: false, message: 'Scan results not ready: retry', artifacts: null });
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('resolves own packages to the checked-out version when it is published, the latest otherwise', () => {
|
|
424
|
+
const published = {
|
|
425
|
+
"'probe@1.0.0'": '',
|
|
426
|
+
"'probe'": '0.9.0',
|
|
427
|
+
"'@acme/probe@1.0.0'": '1.0.0',
|
|
428
|
+
"'@acme/probe'": '1.0.0',
|
|
429
|
+
};
|
|
430
|
+
const calls = [];
|
|
431
|
+
const execute = (command, options) => {
|
|
432
|
+
calls.push({ command, options });
|
|
433
|
+
return published[command.replace(/^npm view (.*) version$/, '$1')] ?? '';
|
|
434
|
+
};
|
|
435
|
+
const packages = publishedPackagesFactory({
|
|
436
|
+
packageJson: { version: '1.0.0' },
|
|
437
|
+
ownPackages: [...OWN_PACKAGES, 'unpublished'],
|
|
438
|
+
root: '/repo',
|
|
439
|
+
execute,
|
|
440
|
+
});
|
|
441
|
+
expect(packages).to.deep.equal([
|
|
442
|
+
{ name: 'probe', version: '0.9.0', current: false, purl: 'pkg:npm/probe@0.9.0' },
|
|
443
|
+
{ name: '@acme/probe', version: '1.0.0', current: true, purl: 'pkg:npm/@acme/probe@1.0.0' },
|
|
444
|
+
]);
|
|
445
|
+
expect(calls.every(({ options }) => options.cwd === '/repo' && options.stdout === true)).to.equal(true);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('reads the registry alerts on the published packages in the scan artifact shape', () => {
|
|
449
|
+
const own = artifactsFixture.filter((artifact) => OWN_PACKAGES.includes(artifactNameFactory(artifact)));
|
|
450
|
+
const { calls, execute } = executorFactory({ code: 0, stdout: JSON.stringify({ ok: true, data: own }) });
|
|
451
|
+
const packages = [{ name: 'probe', version: '1.0.0', current: true, purl: 'pkg:npm/probe@1.0.0' }];
|
|
452
|
+
const result = packageAlerts({
|
|
453
|
+
root: '/repo',
|
|
454
|
+
execute,
|
|
455
|
+
env: { token: 'sktsec_x' },
|
|
456
|
+
binary: '/bin/socket',
|
|
457
|
+
packages,
|
|
458
|
+
});
|
|
459
|
+
expect(calls[0].command).to.equal("'/bin/socket' 'package' 'shallow' 'pkg:npm/probe@1.0.0' '--json' '--no-banner'");
|
|
460
|
+
expect(result).to.deep.include({ ok: true, message: '', packages, artifacts: own });
|
|
461
|
+
|
|
462
|
+
const failed = executorFactory({ code: 1, stdout: JSON.stringify({ ok: false, message: 'Not found' }) });
|
|
463
|
+
expect(packageAlerts({ execute: failed.execute, env: {}, binary: '/bin/socket', packages })).to.include({
|
|
464
|
+
ok: false,
|
|
465
|
+
message: 'Not found',
|
|
466
|
+
artifacts: null,
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const idle = executorFactory();
|
|
470
|
+
expect(packageAlerts({ execute: idle.execute, env: {}, binary: '/bin/socket', packages: [] })).to.deep.include({
|
|
471
|
+
ok: true,
|
|
472
|
+
artifacts: [],
|
|
473
|
+
});
|
|
474
|
+
expect(idle.calls).to.have.length(0);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
describe('Alert domains', () => {
|
|
479
|
+
it('names the own packages from the manifest, the bin map, the repository owner and the organization', () => {
|
|
480
|
+
expect(
|
|
481
|
+
ownPackageNamesFactory({
|
|
482
|
+
packageJson: {
|
|
483
|
+
name: 'underpost-engine',
|
|
484
|
+
bin: { underpost: 'bin/index.js' },
|
|
485
|
+
repository: { url: 'git+https://github.com/underpostnet/engine.git' },
|
|
486
|
+
},
|
|
487
|
+
organization: 'underpost',
|
|
488
|
+
}),
|
|
489
|
+
).to.deep.equal([
|
|
490
|
+
'underpost-engine',
|
|
491
|
+
'underpost',
|
|
492
|
+
'@underpostnet/underpost-engine',
|
|
493
|
+
'@underpostnet/underpost',
|
|
494
|
+
'@underpost/underpost-engine',
|
|
495
|
+
'@underpost/underpost',
|
|
496
|
+
]);
|
|
497
|
+
expect(ownPackageNamesFactory({ packageJson: { name: 'probe' }, organization: '' })).to.deep.equal(['probe']);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('classifies alert types into one category table, and names scoped artifacts', () => {
|
|
501
|
+
expect(alertCategoryFactory('envVars')).to.equal('envVars');
|
|
502
|
+
expect(alertCategoryFactory('urlStrings')).to.equal('network');
|
|
503
|
+
expect(alertCategoryFactory('usesEval')).to.equal('debugAccess');
|
|
504
|
+
expect(alertCategoryFactory('installScripts')).to.equal('installScripts');
|
|
505
|
+
expect(alertCategoryFactory('criticalCVE')).to.equal('advisories');
|
|
506
|
+
expect(alertCategoryFactory('somethingNew')).to.equal('other');
|
|
507
|
+
const types = Object.values(SocketSecurityService.ALERT_CATEGORIES).flatMap((category) => category.types);
|
|
508
|
+
expect(types).to.have.lengthOf(new Set(types).size);
|
|
509
|
+
expect(artifactNameFactory({ namespace: '@acme', name: 'probe' })).to.equal('@acme/probe');
|
|
510
|
+
expect(artifactNameFactory({ namespace: 'acme', name: 'probe' })).to.equal('@acme/probe');
|
|
511
|
+
expect(artifactNameFactory({ name: 'probe' })).to.equal('probe');
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('maps an alert file onto the checkout, refusing a path that leaves it', () => {
|
|
515
|
+
const root = probeRoot();
|
|
516
|
+
const start = PROBE_SOURCE.indexOf('process');
|
|
517
|
+
expect(sourceLocationFactory({ root, file: 'package/src/cli/probe.js', start })).to.deep.equal({
|
|
518
|
+
file: 'src/cli/probe.js',
|
|
519
|
+
line: 3,
|
|
520
|
+
local: true,
|
|
521
|
+
});
|
|
522
|
+
expect(sourceLocationFactory({ root, file: 'src/cli/probe.js', start, current: false })).to.deep.equal({
|
|
523
|
+
file: 'src/cli/probe.js',
|
|
524
|
+
line: null,
|
|
525
|
+
local: true,
|
|
526
|
+
});
|
|
527
|
+
expect(sourceLocationFactory({ root, file: 'src/cli/probe.js', start: 10_000 }).line).to.equal(null);
|
|
528
|
+
expect(sourceLocationFactory({ root, file: 'src/gone.js', start: 0 })).to.deep.equal({
|
|
529
|
+
file: 'src/gone.js',
|
|
530
|
+
line: null,
|
|
531
|
+
local: false,
|
|
532
|
+
});
|
|
533
|
+
expect(sourceLocationFactory({ root, file: 'package/../../etc/passwd', start: 0 })).to.deep.equal({
|
|
534
|
+
file: '',
|
|
535
|
+
line: null,
|
|
536
|
+
local: false,
|
|
537
|
+
});
|
|
538
|
+
expect(locationLabelFactory({ file: 'src/a.js', line: 4, local: true })).to.equal('src/a.js:4');
|
|
539
|
+
expect(locationLabelFactory({ file: 'src/a.js', line: null, local: false })).to.equal(
|
|
540
|
+
'src/a.js (not in this checkout)',
|
|
541
|
+
);
|
|
542
|
+
expect(locationLabelFactory({ file: '', package: '@acme/probe', installed: '1.0.0' })).to.equal(
|
|
543
|
+
'@acme/probe@1.0.0',
|
|
544
|
+
);
|
|
545
|
+
fs.removeSync(root);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('splits artifact alerts into dependency findings per package version and source findings per location', () => {
|
|
549
|
+
const root = probeRoot();
|
|
550
|
+
const { dependency, source } = alertFindingsFactory({
|
|
551
|
+
artifacts: artifactsFixture,
|
|
552
|
+
policies: scanReportSummaryFactory(scanReportFixture).alerts,
|
|
553
|
+
ownPackages: OWN_PACKAGES,
|
|
554
|
+
root,
|
|
555
|
+
version: '1.0.0',
|
|
556
|
+
});
|
|
557
|
+
expect(dependency).to.deep.equal([
|
|
558
|
+
{
|
|
559
|
+
category: 'advisories',
|
|
560
|
+
type: 'cve',
|
|
561
|
+
severity: 'high',
|
|
562
|
+
package: 'axios',
|
|
563
|
+
installed: '1.7.3',
|
|
564
|
+
policy: 'warn',
|
|
565
|
+
files: 1,
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
category: 'installScripts',
|
|
569
|
+
type: 'installScripts',
|
|
570
|
+
severity: 'high',
|
|
571
|
+
package: 'left-pad',
|
|
572
|
+
installed: '1.3.0',
|
|
573
|
+
policy: 'error',
|
|
574
|
+
files: 1,
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
category: 'network',
|
|
578
|
+
type: 'networkAccess',
|
|
579
|
+
severity: 'moderate',
|
|
580
|
+
package: 'axios',
|
|
581
|
+
installed: '1.7.3',
|
|
582
|
+
policy: '',
|
|
583
|
+
files: 2,
|
|
584
|
+
},
|
|
585
|
+
]);
|
|
586
|
+
expect(source.map((finding) => [finding.severity, finding.type, locationLabelFactory(finding)])).to.deep.equal([
|
|
587
|
+
['critical', 'shellAccess', 'src/cli/probe.js'],
|
|
588
|
+
['high', 'gptSecurity', 'src/cli/probe.js:1'],
|
|
589
|
+
['moderate', 'urlStrings', 'src/client/public/app.js (not in this checkout)'],
|
|
590
|
+
['low', 'envVars', 'src/cli/probe.js:3'],
|
|
591
|
+
['low', 'filesystemAccess', '@acme/probe@1.0.0'],
|
|
592
|
+
]);
|
|
593
|
+
expect(source[0]).to.include({ package: 'probe', installed: '0.9.0', category: 'shell' });
|
|
594
|
+
fs.removeSync(root);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('groups source findings into one refactoring task per category', () => {
|
|
598
|
+
const root = probeRoot();
|
|
599
|
+
const { source } = alertFindingsFactory({
|
|
600
|
+
artifacts: artifactsFixture,
|
|
601
|
+
ownPackages: OWN_PACKAGES,
|
|
602
|
+
root,
|
|
603
|
+
version: '1.0.0',
|
|
604
|
+
});
|
|
605
|
+
const tasks = sourceTasksFactory(source);
|
|
606
|
+
expect(tasks.map(({ category, severity, locations }) => [category, severity, locations])).to.deep.equal([
|
|
607
|
+
['shell', 'critical', ['src/cli/probe.js']],
|
|
608
|
+
['codeAnomaly', 'high', ['src/cli/probe.js:1']],
|
|
609
|
+
['network', 'moderate', ['src/client/public/app.js (not in this checkout)']],
|
|
610
|
+
['envVars', 'low', ['src/cli/probe.js:3']],
|
|
611
|
+
['filesystem', 'low', ['@acme/probe@1.0.0']],
|
|
612
|
+
]);
|
|
613
|
+
expect(tasks[3].task).to.include('environmentValueFactory');
|
|
614
|
+
expect(tasks[3].types).to.deep.equal(['envVars']);
|
|
615
|
+
expect(categoryCountsFactory(source)).to.deep.equal({
|
|
616
|
+
codeAnomaly: 1,
|
|
617
|
+
envVars: 1,
|
|
618
|
+
filesystem: 1,
|
|
619
|
+
network: 1,
|
|
620
|
+
shell: 1,
|
|
621
|
+
});
|
|
622
|
+
expect(severityCountsFactory(source)).to.deep.equal({
|
|
623
|
+
critical: 1,
|
|
624
|
+
high: 1,
|
|
625
|
+
moderate: 1,
|
|
626
|
+
low: 2,
|
|
627
|
+
info: 0,
|
|
628
|
+
total: 5,
|
|
629
|
+
});
|
|
630
|
+
fs.removeSync(root);
|
|
631
|
+
});
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
describe('Socket patches', () => {
|
|
635
|
+
it('lists the available patches most severe first', () => {
|
|
636
|
+
const summary = patchScanSummaryFactory(patchScanFixture);
|
|
637
|
+
expect(summary).to.include({ scannedPackages: 40, freePatches: 2, canAccessPaidPatches: false });
|
|
638
|
+
expect(summary.available.map(({ purl, severity }) => [purl, severity])).to.deep.equal([
|
|
639
|
+
['pkg:npm/axios@1.7.3', 'high'],
|
|
640
|
+
['pkg:npm/semver@7.3.5', 'moderate'],
|
|
641
|
+
]);
|
|
642
|
+
expect(patchScanSummaryFactory(null).available).to.deep.equal([]);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it('keeps one patch per package version, the most severe then the newest', () => {
|
|
646
|
+
const available = patchScanSummaryFactory({
|
|
647
|
+
packages: [
|
|
648
|
+
{
|
|
649
|
+
purl: 'pkg:npm/axios@1.7.3',
|
|
650
|
+
patches: [
|
|
651
|
+
{ purl: 'pkg:npm/axios@1.7.3', uuid: 'newest-moderate', severity: 'MODERATE', tier: 'free' },
|
|
652
|
+
{ purl: 'pkg:npm/axios@1.7.3', uuid: 'newest-high', severity: 'HIGH', tier: 'free' },
|
|
653
|
+
{ purl: 'pkg:npm/axios@1.7.3', uuid: 'older-high', severity: 'HIGH', tier: 'free' },
|
|
654
|
+
],
|
|
655
|
+
},
|
|
656
|
+
{ purl: 'pkg:npm/ws@8.16.0', patches: [{ purl: 'pkg:npm/ws@8.16.0', uuid: 'ws-1', severity: 'HIGH' }] },
|
|
657
|
+
],
|
|
658
|
+
}).available;
|
|
659
|
+
const { selected, skipped } = patchSelectionFactory(available);
|
|
660
|
+
expect(selected.map(({ uuid }) => uuid)).to.deep.equal(['newest-high', 'ws-1']);
|
|
661
|
+
expect(skipped.map(({ uuid, reason }) => `${uuid}: ${reason}`)).to.deep.equal([
|
|
662
|
+
'older-high: one patch per package version: newest-high selected',
|
|
663
|
+
'newest-moderate: one patch per package version: newest-high selected',
|
|
664
|
+
]);
|
|
665
|
+
expect(patchSelectionFactory([])).to.deep.equal({ selected: [], skipped: [] });
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it('scans and applies through the CLI with the organization when one is set', () => {
|
|
669
|
+
const { calls, execute } = executorFactory({ code: 0, stdout: JSON.stringify(patchScanFixture) });
|
|
670
|
+
const scanned = patchScan({ root: '/repo', execute, env: { orgSlug: 'acme' }, binary: '/bin/socket' });
|
|
671
|
+
expect(calls[0].command).to.equal("'/bin/socket' 'patch' 'scan' '--json' '--yes' '--org' 'acme'");
|
|
672
|
+
expect(scanned.summary.available).to.have.length(2);
|
|
673
|
+
|
|
674
|
+
const apply = executorFactory({ code: 0, stdout: '{"status":"no_manifest","patchesApplied":0,"results":[]}' });
|
|
675
|
+
const applied = patchApply({ root: '/repo', execute: apply.execute, env: {}, dryRun: true, binary: '/bin/socket' });
|
|
676
|
+
expect(apply.calls[0].command).to.equal("'/bin/socket' 'patch' 'apply' '--ecosystems' 'npm' '--json' '--dry-run'");
|
|
677
|
+
expect(applied).to.include({ ok: true, status: 'no_manifest', patchesApplied: 0, failed: 0 });
|
|
678
|
+
|
|
679
|
+
const partial = executorFactory({
|
|
680
|
+
code: 1,
|
|
681
|
+
stdout: JSON.stringify({
|
|
682
|
+
status: 'partial_failure',
|
|
683
|
+
failed: 1,
|
|
684
|
+
alreadyPatched: 1,
|
|
685
|
+
patchesApplied: 0,
|
|
686
|
+
results: [
|
|
687
|
+
{ purl: 'pkg:npm/semver@7.3.5', error: null },
|
|
688
|
+
{
|
|
689
|
+
purl: 'pkg:npm/ws@8.16.0',
|
|
690
|
+
error: 'Cannot apply patch: lib/websocket-server.js - File hash does not match',
|
|
691
|
+
},
|
|
692
|
+
],
|
|
693
|
+
}),
|
|
694
|
+
stderr: 'No SOCKET_API_TOKEN set. Using public patch API proxy (free patches only).',
|
|
695
|
+
});
|
|
696
|
+
expect(patchApply({ root: '/repo', execute: partial.execute, env: {}, binary: '/bin/socket' })).to.include({
|
|
697
|
+
ok: false,
|
|
698
|
+
status: 'partial_failure',
|
|
699
|
+
failed: 1,
|
|
700
|
+
message: 'Cannot apply patch: lib/websocket-server.js - File hash does not match',
|
|
701
|
+
});
|
|
702
|
+
expect(patchApply({ root: '/repo', execute, env: {}, binary: '' })).to.include({
|
|
703
|
+
unavailable: true,
|
|
704
|
+
status: 'unavailable',
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it('reads the manifest a checkout carries', () => {
|
|
709
|
+
const root = tempRoot();
|
|
710
|
+
expect(patchManifestFactory(root)).to.deep.equal({ path: '.socket/manifest.json', exists: false, patches: [] });
|
|
711
|
+
fs.outputJsonSync(nodePath.join(root, SocketSecurityService.MANIFEST_PATH), manifestFixture);
|
|
712
|
+
const manifest = patchManifestFactory(root);
|
|
713
|
+
expect(manifest.exists).to.equal(true);
|
|
714
|
+
expect(manifest.patches[0]).to.deep.include({
|
|
715
|
+
purl: 'pkg:npm/axios@1.7.3',
|
|
716
|
+
uuid: '211d430d-caac-46a9-9303-eb257430429c',
|
|
717
|
+
tier: 'free',
|
|
718
|
+
files: ['package/lib/adapters/http.js'],
|
|
719
|
+
});
|
|
720
|
+
expect(manifest.patches[0].vulnerabilities[0]).to.deep.equal({
|
|
721
|
+
id: 'GHSA-8hc4-vh64-cxmj',
|
|
722
|
+
severity: 'high',
|
|
723
|
+
summary: 'SSRF in axios',
|
|
724
|
+
cves: ['CVE-2024-39338'],
|
|
725
|
+
});
|
|
726
|
+
fs.removeSync(root);
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
it('installs the prepare hook once and keeps the manifest layout', () => {
|
|
730
|
+
const root = tempRoot();
|
|
731
|
+
const path = nodePath.join(root, 'package.json');
|
|
732
|
+
fs.writeFileSync(
|
|
733
|
+
path,
|
|
734
|
+
'{\n "name": "probe",\n "scripts": {\n "test": "echo ok"\n },\n "version": "1.0.0"\n}\n',
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
expect(patchSetup({ root, dryRun: true })).to.include({
|
|
738
|
+
changed: true,
|
|
739
|
+
prepare: 'node bin socketsecurity --patch-apply',
|
|
740
|
+
});
|
|
741
|
+
expect(JSON.parse(fs.readFileSync(path, 'utf8')).scripts.prepare).to.equal(undefined);
|
|
742
|
+
|
|
743
|
+
expect(patchSetup({ root }).changed).to.equal(true);
|
|
744
|
+
const written = fs.readFileSync(path, 'utf8');
|
|
745
|
+
expect(Object.keys(JSON.parse(written))).to.deep.equal(['name', 'scripts', 'version']);
|
|
746
|
+
expect(written).to.include(' "prepare": "node bin socketsecurity --patch-apply"');
|
|
747
|
+
expect(patchSetup({ root })).to.include({ changed: false });
|
|
748
|
+
|
|
749
|
+
fs.writeJsonSync(path, { name: 'probe', scripts: { prepare: 'node scripts/own.js' } });
|
|
750
|
+
expect(patchSetup({ root }).prepare).to.equal('node scripts/own.js && node bin socketsecurity --patch-apply');
|
|
751
|
+
fs.removeSync(root);
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
describe('Alert policy', () => {
|
|
756
|
+
it('reads the policy action an artifact alert carries when the policy report left it out', () => {
|
|
757
|
+
const { dependency } = alertFindingsFactory({
|
|
758
|
+
artifacts: [
|
|
759
|
+
{ name: 'left-pad', version: '1.3.0', alerts: [{ type: 'gptAnomaly', severity: 'middle', action: 'ignore' }] },
|
|
760
|
+
],
|
|
761
|
+
});
|
|
762
|
+
expect(dependency[0]).to.include({ type: 'gptAnomaly', severity: 'moderate', policy: 'ignore' });
|
|
763
|
+
});
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
describe('Security report', () => {
|
|
767
|
+
const npmAudit = npmAuditSummaryFactory(npmAuditFixture);
|
|
768
|
+
const patches = patchScanSummaryFactory(patchScanFixture);
|
|
769
|
+
const scanSummary = scanReportSummaryFactory(scanReportFixture);
|
|
770
|
+
const reachability = reachabilityFactory(factsFixture);
|
|
771
|
+
const dependencyAlerts = () =>
|
|
772
|
+
alertFindingsFactory({ artifacts: artifactsFixture, policies: scanSummary.alerts, ownPackages: OWN_PACKAGES })
|
|
773
|
+
.dependency;
|
|
774
|
+
|
|
775
|
+
it('extracts advisory ids and PURL coordinates', () => {
|
|
776
|
+
expect(ghsaIdFactory('https://github.com/advisories/GHSA-8hc4-vh64-cxmj')).to.equal('GHSA-8hc4-vh64-cxmj');
|
|
777
|
+
expect(ghsaIdFactory('CVE-2024-1')).to.equal('');
|
|
778
|
+
expect(purlPackageFactory('pkg:npm/@grpc/grpc-js@1.14.4')).to.deep.equal({
|
|
779
|
+
name: '@grpc/grpc-js',
|
|
780
|
+
version: '1.14.4',
|
|
781
|
+
});
|
|
782
|
+
expect(purlPackageFactory('pkg:pypi/requests@2.0.0')).to.deep.equal({ name: '', version: '' });
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it('merges the dependency sources into one finding per advisory or alert and assigns reachability tiers', () => {
|
|
786
|
+
const root = tempRoot();
|
|
787
|
+
fs.outputJsonSync(nodePath.join(root, SocketSecurityService.MANIFEST_PATH), manifestFixture);
|
|
788
|
+
const manifest = patchManifestFactory(root);
|
|
789
|
+
fs.removeSync(root);
|
|
790
|
+
|
|
791
|
+
const findings = findingsFactory({
|
|
792
|
+
npmAudit,
|
|
793
|
+
patchScan: patches,
|
|
794
|
+
alerts: dependencyAlerts(),
|
|
795
|
+
reachability,
|
|
796
|
+
manifest,
|
|
797
|
+
});
|
|
798
|
+
const axios = findings.find(({ package: name, id }) => name === 'axios' && id === 'GHSA-8hc4-vh64-cxmj');
|
|
799
|
+
expect(axios).to.include({
|
|
800
|
+
category: 'advisories',
|
|
801
|
+
severity: 'high',
|
|
802
|
+
tier: 1,
|
|
803
|
+
reachable: true,
|
|
804
|
+
fix: 'update',
|
|
805
|
+
installed: '1.7.3',
|
|
806
|
+
patched: true,
|
|
807
|
+
});
|
|
808
|
+
expect(axios.sources).to.deep.equal(['npm audit', 'socket patch']);
|
|
809
|
+
expect(axios.patches).to.deep.equal(['211d430d-caac-46a9-9303-eb257430429c']);
|
|
810
|
+
|
|
811
|
+
const minimist = findings.find(({ package: name }) => name === 'minimist');
|
|
812
|
+
expect(minimist).to.include({ tier: 1, reachable: false, fix: 'tool@9.0.0 (major)', patched: false });
|
|
813
|
+
|
|
814
|
+
const semver = findings.find(({ package: name }) => name === 'semver');
|
|
815
|
+
expect(semver).to.include({ tier: 3, reachable: null, fix: 'none', patched: false });
|
|
816
|
+
|
|
817
|
+
const leftPad = findings.find(({ package: name }) => name === 'left-pad');
|
|
818
|
+
expect(leftPad).to.include({
|
|
819
|
+
tier: 2,
|
|
820
|
+
severity: 'high',
|
|
821
|
+
policy: 'error',
|
|
822
|
+
id: 'installScripts',
|
|
823
|
+
category: 'installScripts',
|
|
824
|
+
});
|
|
825
|
+
const network = findings.find(({ id }) => id === 'networkAccess');
|
|
826
|
+
expect(network).to.include({ category: 'network', severity: 'moderate', files: 2, tier: 2 });
|
|
827
|
+
// The own package never reaches the dependency domain.
|
|
828
|
+
expect(findings.map(({ package: name }) => name)).to.not.include.members(OWN_PACKAGES);
|
|
829
|
+
expect(findings[0].severity).to.equal('high');
|
|
830
|
+
expect(findings.at(-1).severity).to.equal('moderate');
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
it('defers what no non-breaking action resolves, with the reason', () => {
|
|
834
|
+
const findings = findingsFactory({ npmAudit, patchScan: patches, alerts: dependencyAlerts(), reachability });
|
|
835
|
+
const deferred = deferredFactory(findings);
|
|
836
|
+
// A scan alert carries its alert type, not an advisory id, so it cannot merge with an advisory.
|
|
837
|
+
expect(deferred.map(({ package: name, id }) => `${name}#${id}`)).to.deep.equal([
|
|
838
|
+
'axios#cve',
|
|
839
|
+
'left-pad#installScripts',
|
|
840
|
+
'axios#networkAccess',
|
|
841
|
+
'minimist#GHSA-xvch-5gv4-984h',
|
|
842
|
+
'semver#GHSA-c2qf-rxjj-qqgw',
|
|
843
|
+
]);
|
|
844
|
+
expect(deferred[1].reason).to.equal('no compatible upstream fix; no Socket patch');
|
|
845
|
+
expect(deferred[3].reason).to.equal('the fix is a major upgrade (tool@9.0.0 (major)); no Socket patch');
|
|
846
|
+
expect(deferred[4].reason).to.equal(
|
|
847
|
+
'no compatible upstream fix; a Socket patch exists (bef9d52f-74ea-4eb0-9992-86e981170794) but the manifest carries another patch for this package version',
|
|
848
|
+
);
|
|
849
|
+
expect(deferredFactory([])).to.deep.equal([]);
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
it('assembles, renders and writes the report with a section per domain', () => {
|
|
853
|
+
const root = probeRoot();
|
|
854
|
+
const own = (artifact) => OWN_PACKAGES.includes(artifactNameFactory(artifact));
|
|
855
|
+
const scanArtifacts = artifactsFixture.filter((artifact) => !own(artifact));
|
|
856
|
+
const report = securityReportFactory({
|
|
857
|
+
root,
|
|
858
|
+
packageJson: { name: 'probe', version: '1.0.0' },
|
|
859
|
+
ownPackages: OWN_PACKAGES,
|
|
860
|
+
dependencies: {
|
|
861
|
+
dependencies: 3,
|
|
862
|
+
devDependencies: 2,
|
|
863
|
+
runtime: ['axios', 'vitest'],
|
|
864
|
+
lazy: ['bumpp (devDependencies)'],
|
|
865
|
+
misplaced: [{ name: 'vitest', declaredIn: 'devDependencies', importers: ['src/cli/test.js'] }],
|
|
866
|
+
},
|
|
867
|
+
npmAudit,
|
|
868
|
+
scan: {
|
|
869
|
+
ok: true,
|
|
870
|
+
message: '',
|
|
871
|
+
unavailable: false,
|
|
872
|
+
summary: scanSummary,
|
|
873
|
+
artifacts: scanArtifacts,
|
|
874
|
+
report: scanReportFixture,
|
|
875
|
+
facts: factsFixture,
|
|
876
|
+
reachability,
|
|
877
|
+
},
|
|
878
|
+
packageAlerts: {
|
|
879
|
+
ok: true,
|
|
880
|
+
message: '',
|
|
881
|
+
unavailable: false,
|
|
882
|
+
packages: [{ name: 'probe', version: '1.0.0', current: true, purl: 'pkg:npm/probe@1.0.0' }],
|
|
883
|
+
artifacts: artifactsFixture.filter(own),
|
|
884
|
+
},
|
|
885
|
+
patchScan: { ok: true, message: '', unavailable: false, data: patchScanFixture, summary: patches },
|
|
886
|
+
manifest: patchManifestFactory(root),
|
|
887
|
+
remediation: {
|
|
888
|
+
'npm audit fix': { ok: true, code: 0 },
|
|
889
|
+
'socket patch get': [{ uuid: 'u1', purl: 'pkg:npm/axios@1.7.3', ok: true, message: '' }],
|
|
890
|
+
'socket fix': { skipped: 'SOCKET_CLI_API_TOKEN is not set' },
|
|
891
|
+
'socket patch apply': { ok: true, status: 'success', patchesApplied: 1, alreadyPatched: 0, failed: 0 },
|
|
892
|
+
},
|
|
893
|
+
env: { token: 'sktsec_x', orgSlug: 'acme', acceptRisks: '' },
|
|
894
|
+
cliVersion: '1.1.171',
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
expect(report.socket).to.deep.equal({ cli: '1.1.171', token: true, org: 'acme', acceptRisks: false });
|
|
898
|
+
expect(report.scan).to.include({ ok: true, healthy: false, orgSlug: 'acme', artifacts: scanArtifacts.length });
|
|
899
|
+
const { dependencySecurity: dependency, sourceCodeRisk: source } = report;
|
|
900
|
+
expect(dependency.counts).to.include({ high: 3, moderate: 3, total: 6 });
|
|
901
|
+
expect(dependency.categories).to.deep.equal({ advisories: 4, installScripts: 1, network: 1 });
|
|
902
|
+
expect(dependency.deferred).to.have.length(5);
|
|
903
|
+
expect(dependency.remediation['npm audit fix']).to.deep.equal({ ok: true, code: 0 });
|
|
904
|
+
expect(source).to.include({ status: 'analyzed' });
|
|
905
|
+
expect(source.analyzed).to.deep.equal([{ name: 'probe', version: '1.0.0', current: true }]);
|
|
906
|
+
expect(source.packages).to.deep.equal(OWN_PACKAGES);
|
|
907
|
+
expect(source.counts).to.include({ critical: 1, high: 1, moderate: 1, low: 2, total: 5 });
|
|
908
|
+
expect(source.tasks).to.have.length(5);
|
|
909
|
+
expect(report.counts).to.include({ critical: 1, high: 4, moderate: 4, low: 2, total: 11 });
|
|
910
|
+
|
|
911
|
+
expect(reportViolates(report)).to.equal(true);
|
|
912
|
+
expect(reportViolates({ scan: { ok: true, healthy: true }, dependencySecurity: { findings: [] } })).to.equal(false);
|
|
913
|
+
expect(reportViolates({ scan: { ok: false }, dependencySecurity: { findings: [] } })).to.equal(true);
|
|
914
|
+
// A source finding gates the run like a dependency finding.
|
|
915
|
+
expect(
|
|
916
|
+
reportViolates({ scan: { healthy: true }, sourceCodeRisk: { findings: [{ severity: 'critical' }] } }),
|
|
917
|
+
).to.equal(true);
|
|
918
|
+
expect(
|
|
919
|
+
reportViolates({ scan: { healthy: true }, dependencySecurity: { findings: [{ severity: 'low' }] } }, 'moderate'),
|
|
920
|
+
).to.equal(false);
|
|
921
|
+
|
|
922
|
+
const markdown = securityReportMarkdownFactory(report);
|
|
923
|
+
for (const expected of [
|
|
924
|
+
'# Security audit: probe 1.0.0',
|
|
925
|
+
'| Dependency security | 0 | 3 | 3 | 0 | 0 | 6 |',
|
|
926
|
+
'| Source code risk | 1 | 1 | 1 | 2 | 0 | 5 |',
|
|
927
|
+
'## Socket scan',
|
|
928
|
+
`sbom/${scanReportFixture.scanId}`,
|
|
929
|
+
'## Dependency Security',
|
|
930
|
+
'| Install scripts and native code | 1 |',
|
|
931
|
+
'| high | Install scripts and native code | left-pad@1.3.0 | installScripts |',
|
|
932
|
+
'### Segregation',
|
|
933
|
+
'| vitest | devDependencies | src/cli/test.js |',
|
|
934
|
+
'### npm audit',
|
|
935
|
+
'### Reachability',
|
|
936
|
+
'| axios | 1.7.3 | GHSA-8hc4-vh64-cxmj | reachable |',
|
|
937
|
+
'### Socket patches',
|
|
938
|
+
'### Remediation',
|
|
939
|
+
'| socket patch get | pkg:npm/axios@1.7.3 | ok |',
|
|
940
|
+
'| socket fix | | skipped: SOCKET_CLI_API_TOKEN is not set |',
|
|
941
|
+
'### Deferred risks',
|
|
942
|
+
'## Source Code Risk Analysis',
|
|
943
|
+
'| Own packages | probe, @acme/probe |',
|
|
944
|
+
'| Analyzed | probe@1.0.0 |',
|
|
945
|
+
'### Refactoring tasks',
|
|
946
|
+
'| critical | Shell access |',
|
|
947
|
+
'src/cli/probe.js:3',
|
|
948
|
+
'| moderate | Network access and embedded URLs | urlStrings | src/client/public/app.js (not in this checkout) | @acme/probe@1.0.0 | none |',
|
|
949
|
+
])
|
|
950
|
+
expect(markdown, expected).to.include(expected);
|
|
951
|
+
expect(markdown.indexOf('## Dependency Security')).to.be.lessThan(markdown.indexOf('## Source Code Risk Analysis'));
|
|
952
|
+
|
|
953
|
+
const directory = nodePath.join(root, 'out');
|
|
954
|
+
const files = writeSecurityReports({
|
|
955
|
+
report,
|
|
956
|
+
directory,
|
|
957
|
+
artifacts: {
|
|
958
|
+
npmAudit: npmAuditFixture,
|
|
959
|
+
scan: scanReportFixture,
|
|
960
|
+
scanArtifacts: scanArtifacts,
|
|
961
|
+
packageAlerts: artifactsFixture.filter(own),
|
|
962
|
+
reach: factsFixture,
|
|
963
|
+
patchScan: null,
|
|
964
|
+
},
|
|
965
|
+
});
|
|
966
|
+
expect(files.map((file) => nodePath.basename(file))).to.deep.equal([
|
|
967
|
+
'security-report.json',
|
|
968
|
+
'security-report.md',
|
|
969
|
+
'npm-audit.json',
|
|
970
|
+
'socket-scan.json',
|
|
971
|
+
'socket-scan-artifacts.json',
|
|
972
|
+
'socket-package-alerts.json',
|
|
973
|
+
'socket-reach.json',
|
|
974
|
+
]);
|
|
975
|
+
const written = fs.readJsonSync(nodePath.join(directory, 'security-report.json'));
|
|
976
|
+
expect(Object.keys(written)).to.include.members(['scan', 'dependencySecurity', 'sourceCodeRisk', 'counts']);
|
|
977
|
+
expect(written.sourceCodeRisk.findings).to.have.length(5);
|
|
978
|
+
fs.removeSync(root);
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
it('reports a skipped scan, and a skipped source analysis, when no token is configured', () => {
|
|
982
|
+
const report = securityReportFactory({
|
|
983
|
+
packageJson: { name: 'probe', version: '1.0.0' },
|
|
984
|
+
ownPackages: ['probe'],
|
|
985
|
+
dependencies: { dependencies: 0, devDependencies: 0, runtime: [], lazy: [], misplaced: [] },
|
|
986
|
+
npmAudit: npmAuditSummaryFactory({}),
|
|
987
|
+
patchScan: { ok: false, message: '', unavailable: true, data: null, summary: patchScanSummaryFactory(null) },
|
|
988
|
+
manifest: { path: '.socket/manifest.json', exists: false, patches: [] },
|
|
989
|
+
});
|
|
990
|
+
expect(report.scan).to.deep.equal({ skipped: 'SOCKET_CLI_API_TOKEN is not set' });
|
|
991
|
+
expect(report.dependencySecurity.reachability.available).to.equal(false);
|
|
992
|
+
expect(report.sourceCodeRisk).to.include({ status: 'skipped: SOCKET_CLI_API_TOKEN is not set' });
|
|
993
|
+
expect(report.sourceCodeRisk.findings).to.deep.equal([]);
|
|
994
|
+
const markdown = securityReportMarkdownFactory(report);
|
|
995
|
+
expect(markdown).to.include('Skipped: SOCKET_CLI_API_TOKEN is not set.');
|
|
996
|
+
expect(markdown).to.include('| Status | CLI not installed |');
|
|
997
|
+
expect(markdown).to.include('No remediation ran in this pass.');
|
|
998
|
+
expect(markdown).to.include('No source code alerts.');
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
it('reports the source analysis as unavailable when the package lookup fails or nothing is published', () => {
|
|
1002
|
+
const report = securityReportFactory({
|
|
1003
|
+
packageJson: { name: 'probe', version: '1.0.0' },
|
|
1004
|
+
ownPackages: ['probe'],
|
|
1005
|
+
dependencies: { dependencies: 0, devDependencies: 0, runtime: [], lazy: [], misplaced: [] },
|
|
1006
|
+
npmAudit: npmAuditSummaryFactory({}),
|
|
1007
|
+
scan: {
|
|
1008
|
+
ok: false,
|
|
1009
|
+
message: 'Scan results not ready',
|
|
1010
|
+
unavailable: false,
|
|
1011
|
+
summary: scanSummary,
|
|
1012
|
+
artifacts: null,
|
|
1013
|
+
reachability: reachabilityFactory(null),
|
|
1014
|
+
},
|
|
1015
|
+
packageAlerts: {
|
|
1016
|
+
ok: false,
|
|
1017
|
+
message: 'Package not found',
|
|
1018
|
+
unavailable: false,
|
|
1019
|
+
packages: [{ name: 'probe', version: '1.0.0', current: true, purl: 'pkg:npm/probe@1.0.0' }],
|
|
1020
|
+
artifacts: null,
|
|
1021
|
+
},
|
|
1022
|
+
patchScan: { ok: true, message: '', unavailable: false, data: null, summary: patchScanSummaryFactory(null) },
|
|
1023
|
+
manifest: { path: '.socket/manifest.json', exists: false, patches: [] },
|
|
1024
|
+
env: { token: 'sktsec_x' },
|
|
1025
|
+
});
|
|
1026
|
+
expect(report.sourceCodeRisk.status).to.equal('unavailable: Package not found');
|
|
1027
|
+
const unpublished = securityReportFactory({
|
|
1028
|
+
packageJson: { name: 'probe', version: '1.0.0' },
|
|
1029
|
+
ownPackages: ['probe'],
|
|
1030
|
+
dependencies: { dependencies: 0, devDependencies: 0, runtime: [], lazy: [], misplaced: [] },
|
|
1031
|
+
npmAudit: npmAuditSummaryFactory({}),
|
|
1032
|
+
packageAlerts: { ok: true, message: '', unavailable: false, packages: [], artifacts: [] },
|
|
1033
|
+
patchScan: { ok: true, message: '', unavailable: false, data: null, summary: patchScanSummaryFactory(null) },
|
|
1034
|
+
manifest: { path: '.socket/manifest.json', exists: false, patches: [] },
|
|
1035
|
+
env: { token: 'sktsec_x' },
|
|
1036
|
+
});
|
|
1037
|
+
expect(unpublished.sourceCodeRisk.status).to.equal('unavailable: no own package is published to the registry');
|
|
1038
|
+
expect(report.scan.artifacts).to.equal(0);
|
|
1039
|
+
expect(reportViolates(report, 'critical')).to.equal(true);
|
|
1040
|
+
});
|
|
1041
|
+
});
|