sentinello 3.0.0 → 3.1.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/README.md +7 -3
- package/dist/cli.cjs +576 -392
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -6,6 +6,8 @@ var path = require('path');
|
|
|
6
6
|
var child_process = require('child_process');
|
|
7
7
|
var fs = require('fs');
|
|
8
8
|
var crypto = require('crypto');
|
|
9
|
+
var http = require('http');
|
|
10
|
+
var https = require('https');
|
|
9
11
|
var os = require('os');
|
|
10
12
|
var zlib = require('zlib');
|
|
11
13
|
var promises$2 = require('stream/promises');
|
|
@@ -25,7 +27,11 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
25
27
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
26
28
|
});
|
|
27
29
|
var __commonJS = (cb, mod) => function __require2() {
|
|
28
|
-
|
|
30
|
+
try {
|
|
31
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
throw mod = 0, e;
|
|
34
|
+
}
|
|
29
35
|
};
|
|
30
36
|
var __export = (target, all) => {
|
|
31
37
|
for (var name in all)
|
|
@@ -3725,9 +3731,9 @@ var require_stream_duplex = __commonJS({
|
|
|
3725
3731
|
module2.exports = Duplex;
|
|
3726
3732
|
var util = Object.create(require_util());
|
|
3727
3733
|
util.inherits = require_inherits();
|
|
3728
|
-
var
|
|
3734
|
+
var Readable = require_stream_readable();
|
|
3729
3735
|
var Writable = require_stream_writable();
|
|
3730
|
-
util.inherits(Duplex,
|
|
3736
|
+
util.inherits(Duplex, Readable);
|
|
3731
3737
|
{
|
|
3732
3738
|
keys = objectKeys(Writable.prototype);
|
|
3733
3739
|
for (v = 0; v < keys.length; v++) {
|
|
@@ -3740,7 +3746,7 @@ var require_stream_duplex = __commonJS({
|
|
|
3740
3746
|
var v;
|
|
3741
3747
|
function Duplex(options) {
|
|
3742
3748
|
if (!(this instanceof Duplex)) return new Duplex(options);
|
|
3743
|
-
|
|
3749
|
+
Readable.call(this, options);
|
|
3744
3750
|
Writable.call(this, options);
|
|
3745
3751
|
if (options && options.readable === false) this.readable = false;
|
|
3746
3752
|
if (options && options.writable === false) this.writable = false;
|
|
@@ -4028,10 +4034,10 @@ var require_string_decoder = __commonJS({
|
|
|
4028
4034
|
var require_stream_readable = __commonJS({
|
|
4029
4035
|
"../../node_modules/.pnpm/readable-stream@2.3.8/node_modules/readable-stream/lib/_stream_readable.js"(exports2, module2) {
|
|
4030
4036
|
var pna = require_process_nextick_args();
|
|
4031
|
-
module2.exports =
|
|
4037
|
+
module2.exports = Readable;
|
|
4032
4038
|
var isArray = require_isarray();
|
|
4033
4039
|
var Duplex;
|
|
4034
|
-
|
|
4040
|
+
Readable.ReadableState = ReadableState;
|
|
4035
4041
|
__require("events").EventEmitter;
|
|
4036
4042
|
var EElistenerCount = function(emitter, type2) {
|
|
4037
4043
|
return emitter.listeners(type2).length;
|
|
@@ -4059,7 +4065,7 @@ var require_stream_readable = __commonJS({
|
|
|
4059
4065
|
var BufferList = require_BufferList();
|
|
4060
4066
|
var destroyImpl = require_destroy();
|
|
4061
4067
|
var StringDecoder;
|
|
4062
|
-
util.inherits(
|
|
4068
|
+
util.inherits(Readable, Stream);
|
|
4063
4069
|
var kProxyEvents = ["error", "close", "destroy", "pause", "resume"];
|
|
4064
4070
|
function prependListener(emitter, event, fn) {
|
|
4065
4071
|
if (typeof emitter.prependListener === "function") return emitter.prependListener(event, fn);
|
|
@@ -4105,9 +4111,9 @@ var require_stream_readable = __commonJS({
|
|
|
4105
4111
|
this.encoding = options.encoding;
|
|
4106
4112
|
}
|
|
4107
4113
|
}
|
|
4108
|
-
function
|
|
4114
|
+
function Readable(options) {
|
|
4109
4115
|
Duplex = Duplex || require_stream_duplex();
|
|
4110
|
-
if (!(this instanceof
|
|
4116
|
+
if (!(this instanceof Readable)) return new Readable(options);
|
|
4111
4117
|
this._readableState = new ReadableState(options, this);
|
|
4112
4118
|
this.readable = true;
|
|
4113
4119
|
if (options) {
|
|
@@ -4116,7 +4122,7 @@ var require_stream_readable = __commonJS({
|
|
|
4116
4122
|
}
|
|
4117
4123
|
Stream.call(this);
|
|
4118
4124
|
}
|
|
4119
|
-
Object.defineProperty(
|
|
4125
|
+
Object.defineProperty(Readable.prototype, "destroyed", {
|
|
4120
4126
|
get: function() {
|
|
4121
4127
|
if (this._readableState === void 0) {
|
|
4122
4128
|
return false;
|
|
@@ -4130,13 +4136,13 @@ var require_stream_readable = __commonJS({
|
|
|
4130
4136
|
this._readableState.destroyed = value;
|
|
4131
4137
|
}
|
|
4132
4138
|
});
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4139
|
+
Readable.prototype.destroy = destroyImpl.destroy;
|
|
4140
|
+
Readable.prototype._undestroy = destroyImpl.undestroy;
|
|
4141
|
+
Readable.prototype._destroy = function(err, cb) {
|
|
4136
4142
|
this.push(null);
|
|
4137
4143
|
cb(err);
|
|
4138
4144
|
};
|
|
4139
|
-
|
|
4145
|
+
Readable.prototype.push = function(chunk, encoding) {
|
|
4140
4146
|
var state = this._readableState;
|
|
4141
4147
|
var skipChunkCheck;
|
|
4142
4148
|
if (!state.objectMode) {
|
|
@@ -4153,7 +4159,7 @@ var require_stream_readable = __commonJS({
|
|
|
4153
4159
|
}
|
|
4154
4160
|
return readableAddChunk(this, chunk, encoding, false, skipChunkCheck);
|
|
4155
4161
|
};
|
|
4156
|
-
|
|
4162
|
+
Readable.prototype.unshift = function(chunk) {
|
|
4157
4163
|
return readableAddChunk(this, chunk, null, true, false);
|
|
4158
4164
|
};
|
|
4159
4165
|
function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) {
|
|
@@ -4213,10 +4219,10 @@ var require_stream_readable = __commonJS({
|
|
|
4213
4219
|
function needMoreData(state) {
|
|
4214
4220
|
return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0);
|
|
4215
4221
|
}
|
|
4216
|
-
|
|
4222
|
+
Readable.prototype.isPaused = function() {
|
|
4217
4223
|
return this._readableState.flowing === false;
|
|
4218
4224
|
};
|
|
4219
|
-
|
|
4225
|
+
Readable.prototype.setEncoding = function(enc) {
|
|
4220
4226
|
if (!StringDecoder) StringDecoder = require_string_decoder().StringDecoder;
|
|
4221
4227
|
this._readableState.decoder = new StringDecoder(enc);
|
|
4222
4228
|
this._readableState.encoding = enc;
|
|
@@ -4252,7 +4258,7 @@ var require_stream_readable = __commonJS({
|
|
|
4252
4258
|
}
|
|
4253
4259
|
return state.length;
|
|
4254
4260
|
}
|
|
4255
|
-
|
|
4261
|
+
Readable.prototype.read = function(n) {
|
|
4256
4262
|
debug("read", n);
|
|
4257
4263
|
n = parseInt(n, 10);
|
|
4258
4264
|
var state = this._readableState;
|
|
@@ -4347,10 +4353,10 @@ var require_stream_readable = __commonJS({
|
|
|
4347
4353
|
}
|
|
4348
4354
|
state.readingMore = false;
|
|
4349
4355
|
}
|
|
4350
|
-
|
|
4356
|
+
Readable.prototype._read = function(n) {
|
|
4351
4357
|
this.emit("error", new Error("_read() is not implemented"));
|
|
4352
4358
|
};
|
|
4353
|
-
|
|
4359
|
+
Readable.prototype.pipe = function(dest, pipeOpts) {
|
|
4354
4360
|
var src = this;
|
|
4355
4361
|
var state = this._readableState;
|
|
4356
4362
|
switch (state.pipesCount) {
|
|
@@ -4455,7 +4461,7 @@ var require_stream_readable = __commonJS({
|
|
|
4455
4461
|
}
|
|
4456
4462
|
};
|
|
4457
4463
|
}
|
|
4458
|
-
|
|
4464
|
+
Readable.prototype.unpipe = function(dest) {
|
|
4459
4465
|
var state = this._readableState;
|
|
4460
4466
|
var unpipeInfo = { hasUnpiped: false };
|
|
4461
4467
|
if (state.pipesCount === 0) return this;
|
|
@@ -4487,7 +4493,7 @@ var require_stream_readable = __commonJS({
|
|
|
4487
4493
|
dest.emit("unpipe", this, unpipeInfo);
|
|
4488
4494
|
return this;
|
|
4489
4495
|
};
|
|
4490
|
-
|
|
4496
|
+
Readable.prototype.on = function(ev, fn) {
|
|
4491
4497
|
var res = Stream.prototype.on.call(this, ev, fn);
|
|
4492
4498
|
if (ev === "data") {
|
|
4493
4499
|
if (this._readableState.flowing !== false) this.resume();
|
|
@@ -4505,12 +4511,12 @@ var require_stream_readable = __commonJS({
|
|
|
4505
4511
|
}
|
|
4506
4512
|
return res;
|
|
4507
4513
|
};
|
|
4508
|
-
|
|
4514
|
+
Readable.prototype.addListener = Readable.prototype.on;
|
|
4509
4515
|
function nReadingNextTick(self2) {
|
|
4510
4516
|
debug("readable nexttick read 0");
|
|
4511
4517
|
self2.read(0);
|
|
4512
4518
|
}
|
|
4513
|
-
|
|
4519
|
+
Readable.prototype.resume = function() {
|
|
4514
4520
|
var state = this._readableState;
|
|
4515
4521
|
if (!state.flowing) {
|
|
4516
4522
|
debug("resume");
|
|
@@ -4536,7 +4542,7 @@ var require_stream_readable = __commonJS({
|
|
|
4536
4542
|
flow(stream);
|
|
4537
4543
|
if (state.flowing && !state.reading) stream.read(0);
|
|
4538
4544
|
}
|
|
4539
|
-
|
|
4545
|
+
Readable.prototype.pause = function() {
|
|
4540
4546
|
debug("call pause flowing=%j", this._readableState.flowing);
|
|
4541
4547
|
if (false !== this._readableState.flowing) {
|
|
4542
4548
|
debug("pause");
|
|
@@ -4551,7 +4557,7 @@ var require_stream_readable = __commonJS({
|
|
|
4551
4557
|
while (state.flowing && stream.read() !== null) {
|
|
4552
4558
|
}
|
|
4553
4559
|
}
|
|
4554
|
-
|
|
4560
|
+
Readable.prototype.wrap = function(stream) {
|
|
4555
4561
|
var _this = this;
|
|
4556
4562
|
var state = this._readableState;
|
|
4557
4563
|
var paused = false;
|
|
@@ -4595,7 +4601,7 @@ var require_stream_readable = __commonJS({
|
|
|
4595
4601
|
};
|
|
4596
4602
|
return this;
|
|
4597
4603
|
};
|
|
4598
|
-
Object.defineProperty(
|
|
4604
|
+
Object.defineProperty(Readable.prototype, "readableHighWaterMark", {
|
|
4599
4605
|
// making it explicit this property is not enumerable
|
|
4600
4606
|
// because otherwise some prototype manipulation in
|
|
4601
4607
|
// userland will fail
|
|
@@ -4604,7 +4610,7 @@ var require_stream_readable = __commonJS({
|
|
|
4604
4610
|
return this._readableState.highWaterMark;
|
|
4605
4611
|
}
|
|
4606
4612
|
});
|
|
4607
|
-
|
|
4613
|
+
Readable._fromList = fromList;
|
|
4608
4614
|
function fromList(n, state) {
|
|
4609
4615
|
if (state.length === 0) return null;
|
|
4610
4616
|
var ret2;
|
|
@@ -13117,6 +13123,15 @@ function compareSeverity(a, b) {
|
|
|
13117
13123
|
function meetsSeverityFloor(severity, floor) {
|
|
13118
13124
|
return severityWeight(severity) >= severityWeight(floor);
|
|
13119
13125
|
}
|
|
13126
|
+
var SOURCE_UNAVAILABLE_REASON_CODES = [
|
|
13127
|
+
"osv_db_not_seeded",
|
|
13128
|
+
"osv_db_unavailable",
|
|
13129
|
+
"gemnasium_db_not_seeded",
|
|
13130
|
+
"gemnasium_db_unavailable"
|
|
13131
|
+
];
|
|
13132
|
+
function isSourceUnavailableReason(code) {
|
|
13133
|
+
return SOURCE_UNAVAILABLE_REASON_CODES.includes(code);
|
|
13134
|
+
}
|
|
13120
13135
|
|
|
13121
13136
|
// ../../packages/core/src/advisory-rows.ts
|
|
13122
13137
|
var OSV_NORMALIZER_VERSION = 3;
|
|
@@ -28593,6 +28608,10 @@ async function detectYarnMajor(projectPath, lockfile) {
|
|
|
28593
28608
|
}
|
|
28594
28609
|
return null;
|
|
28595
28610
|
}
|
|
28611
|
+
function auditSummary(ctx, findingCount) {
|
|
28612
|
+
const packageCount = ctx.resolvedGraph?.packages?.length ?? null;
|
|
28613
|
+
return JSON.stringify({ source: SCANNER_NAME, packageCount, findingCount });
|
|
28614
|
+
}
|
|
28596
28615
|
function logCrossCheckDrops(result, packageManager) {
|
|
28597
28616
|
if (result.droppedCount === 0) return;
|
|
28598
28617
|
const MAX_LIST = 10;
|
|
@@ -28701,7 +28720,7 @@ async function runNpmAudit(projectPath, ctx, deps = REAL_DEPS) {
|
|
|
28701
28720
|
status: "ok",
|
|
28702
28721
|
reasonCode: "ok",
|
|
28703
28722
|
findings: crossChecked2.kept,
|
|
28704
|
-
rawJson:
|
|
28723
|
+
rawJson: auditSummary(ctx, crossChecked2.kept.length),
|
|
28705
28724
|
errorText: null,
|
|
28706
28725
|
durationMs: Date.now() - startedAt
|
|
28707
28726
|
};
|
|
@@ -28725,7 +28744,7 @@ async function runNpmAudit(projectPath, ctx, deps = REAL_DEPS) {
|
|
|
28725
28744
|
status: "ok",
|
|
28726
28745
|
reasonCode: "ok",
|
|
28727
28746
|
findings: crossChecked.kept,
|
|
28728
|
-
rawJson:
|
|
28747
|
+
rawJson: auditSummary(ctx, crossChecked.kept.length),
|
|
28729
28748
|
errorText: null,
|
|
28730
28749
|
durationMs: Date.now() - startedAt
|
|
28731
28750
|
};
|
|
@@ -28986,7 +29005,7 @@ function nameFromNodePath(nodePath) {
|
|
|
28986
29005
|
return slash < 0 ? tail : tail.slice(0, slash);
|
|
28987
29006
|
}
|
|
28988
29007
|
|
|
28989
|
-
// ../../node_modules/.pnpm/js-yaml@4.3.
|
|
29008
|
+
// ../../node_modules/.pnpm/js-yaml@4.3.1/node_modules/js-yaml/dist/js-yaml.mjs
|
|
28990
29009
|
function getDefaultExportFromCjs(x) {
|
|
28991
29010
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
28992
29011
|
}
|
|
@@ -29847,7 +29866,7 @@ function requireOmap() {
|
|
|
29847
29866
|
const _toString = Object.prototype.toString;
|
|
29848
29867
|
function resolveYamlOmap(data) {
|
|
29849
29868
|
if (data === null) return true;
|
|
29850
|
-
const objectKeys =
|
|
29869
|
+
const objectKeys = {};
|
|
29851
29870
|
const object2 = data;
|
|
29852
29871
|
for (let index = 0, length = object2.length; index < length; index += 1) {
|
|
29853
29872
|
const pair = object2[index];
|
|
@@ -29861,8 +29880,8 @@ function requireOmap() {
|
|
|
29861
29880
|
}
|
|
29862
29881
|
}
|
|
29863
29882
|
if (!pairHasKey) return false;
|
|
29864
|
-
if (
|
|
29865
|
-
|
|
29883
|
+
if (_hasOwnProperty.call(objectKeys, pairKey)) return false;
|
|
29884
|
+
Object.defineProperty(objectKeys, pairKey, { value: true });
|
|
29866
29885
|
}
|
|
29867
29886
|
return true;
|
|
29868
29887
|
}
|
|
@@ -33206,288 +33225,6 @@ function reconcileAgainstReported(findings, reportedByPackage) {
|
|
|
33206
33225
|
// ../../packages/scanners/src/index.ts
|
|
33207
33226
|
var registry2 = /* @__PURE__ */ new Map();
|
|
33208
33227
|
registry2.set(npmAuditPlugin.name, npmAuditPlugin);
|
|
33209
|
-
var CACHE_SCHEMA_VERSION = 1;
|
|
33210
|
-
function emptyMeta() {
|
|
33211
|
-
return { schemaVersion: CACHE_SCHEMA_VERSION, sources: { osv: {}, gemnasium: {} } };
|
|
33212
|
-
}
|
|
33213
|
-
function resolveCacheDir(override) {
|
|
33214
|
-
if (override && override.trim().length > 0) return path.resolve(override.trim());
|
|
33215
|
-
const fromEnv = process.env.SENTINELLO_CACHE_DIR;
|
|
33216
|
-
if (fromEnv && fromEnv.trim().length > 0) return path.resolve(fromEnv.trim());
|
|
33217
|
-
const xdg = process.env.XDG_CACHE_HOME;
|
|
33218
|
-
if (xdg && xdg.trim().length > 0) return path.join(path.resolve(xdg.trim()), "sentinello");
|
|
33219
|
-
return path.join(os.homedir(), ".cache", "sentinello");
|
|
33220
|
-
}
|
|
33221
|
-
function advisoryFilePath(cacheDir, source, ecosystem) {
|
|
33222
|
-
const slug = ecosystem.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
33223
|
-
return path.join(cacheDir, source + "-" + slug + ".ndjson.gz");
|
|
33224
|
-
}
|
|
33225
|
-
function metaPath(cacheDir) {
|
|
33226
|
-
return path.join(cacheDir, "meta.json");
|
|
33227
|
-
}
|
|
33228
|
-
async function ensureCacheDir(cacheDir) {
|
|
33229
|
-
await promises.mkdir(cacheDir, { recursive: true });
|
|
33230
|
-
}
|
|
33231
|
-
async function readCacheMeta(cacheDir) {
|
|
33232
|
-
let text;
|
|
33233
|
-
try {
|
|
33234
|
-
text = await promises.readFile(metaPath(cacheDir), "utf8");
|
|
33235
|
-
} catch {
|
|
33236
|
-
return emptyMeta();
|
|
33237
|
-
}
|
|
33238
|
-
let parsed;
|
|
33239
|
-
try {
|
|
33240
|
-
parsed = JSON.parse(text);
|
|
33241
|
-
} catch {
|
|
33242
|
-
return emptyMeta();
|
|
33243
|
-
}
|
|
33244
|
-
if (!parsed || typeof parsed !== "object") return emptyMeta();
|
|
33245
|
-
const meta3 = parsed;
|
|
33246
|
-
if (meta3.schemaVersion !== CACHE_SCHEMA_VERSION) return emptyMeta();
|
|
33247
|
-
const sources = meta3.sources;
|
|
33248
|
-
if (!sources || typeof sources !== "object") return emptyMeta();
|
|
33249
|
-
return {
|
|
33250
|
-
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
33251
|
-
sources: {
|
|
33252
|
-
osv: sources.osv ?? {},
|
|
33253
|
-
gemnasium: sources.gemnasium ?? {}
|
|
33254
|
-
}
|
|
33255
|
-
};
|
|
33256
|
-
}
|
|
33257
|
-
async function writeCacheMeta(cacheDir, meta3) {
|
|
33258
|
-
await ensureCacheDir(cacheDir);
|
|
33259
|
-
const target = metaPath(cacheDir);
|
|
33260
|
-
const tmp = target + ".tmp";
|
|
33261
|
-
await promises.writeFile(tmp, JSON.stringify(meta3, null, 4) + "\n", "utf8");
|
|
33262
|
-
await promises.rename(tmp, target);
|
|
33263
|
-
}
|
|
33264
|
-
function getSourceState(meta3, source, ecosystem) {
|
|
33265
|
-
return meta3.sources[source][ecosystem] ?? null;
|
|
33266
|
-
}
|
|
33267
|
-
function setSourceState(meta3, source, ecosystem, state) {
|
|
33268
|
-
meta3.sources[source][ecosystem] = state;
|
|
33269
|
-
}
|
|
33270
|
-
function isSeeded(meta3, source, ecosystem, normalizerVersion) {
|
|
33271
|
-
const state = getSourceState(meta3, source, ecosystem);
|
|
33272
|
-
if (!state) return false;
|
|
33273
|
-
return state.normalizerVersion === normalizerVersion && state.recordCount > 0;
|
|
33274
|
-
}
|
|
33275
|
-
async function tryAcquireLock(cacheDir) {
|
|
33276
|
-
await ensureCacheDir(cacheDir);
|
|
33277
|
-
const path$1 = path.join(cacheDir, ".lock");
|
|
33278
|
-
try {
|
|
33279
|
-
await promises.writeFile(path$1, String(process.pid), { flag: "wx" });
|
|
33280
|
-
} catch {
|
|
33281
|
-
if (await isStaleLock(path$1)) {
|
|
33282
|
-
await promises.rm(path$1, { force: true });
|
|
33283
|
-
return await tryAcquireLock(cacheDir);
|
|
33284
|
-
}
|
|
33285
|
-
return null;
|
|
33286
|
-
}
|
|
33287
|
-
async function release() {
|
|
33288
|
-
await promises.rm(path$1, { force: true });
|
|
33289
|
-
}
|
|
33290
|
-
return { release };
|
|
33291
|
-
}
|
|
33292
|
-
var LOCK_STALE_MS = 30 * 60 * 1e3;
|
|
33293
|
-
async function isStaleLock(path) {
|
|
33294
|
-
try {
|
|
33295
|
-
const info = await promises.stat(path);
|
|
33296
|
-
return Date.now() - info.mtimeMs > LOCK_STALE_MS;
|
|
33297
|
-
} catch {
|
|
33298
|
-
return false;
|
|
33299
|
-
}
|
|
33300
|
-
}
|
|
33301
|
-
var FIELD_SEPARATOR = " ";
|
|
33302
|
-
var LINE_SEPARATOR = "\n";
|
|
33303
|
-
function encodeRow(row) {
|
|
33304
|
-
const name = row.packageName.replace(/[\t\n\r]/g, "");
|
|
33305
|
-
const id = row.advisoryId.replace(/[\t\n\r]/g, "");
|
|
33306
|
-
return name + FIELD_SEPARATOR + id + FIELD_SEPARATOR + JSON.stringify(row);
|
|
33307
|
-
}
|
|
33308
|
-
function createRowWriter(path) {
|
|
33309
|
-
const tmpPath = path + ".tmp";
|
|
33310
|
-
const source = new stream.PassThrough();
|
|
33311
|
-
const gzip = zlib.createGzip();
|
|
33312
|
-
const done = promises$2.pipeline(source, gzip, fs.createWriteStream(tmpPath));
|
|
33313
|
-
let rowCount = 0;
|
|
33314
|
-
let failed = null;
|
|
33315
|
-
done.catch(function capture(err) {
|
|
33316
|
-
failed = err instanceof Error ? err : new Error(String(err));
|
|
33317
|
-
});
|
|
33318
|
-
async function push(chunk, added) {
|
|
33319
|
-
if (failed) throw failed;
|
|
33320
|
-
rowCount += added;
|
|
33321
|
-
if (!source.write(chunk)) {
|
|
33322
|
-
await new Promise(function waitForDrain(resolve5) {
|
|
33323
|
-
source.once("drain", resolve5);
|
|
33324
|
-
});
|
|
33325
|
-
}
|
|
33326
|
-
}
|
|
33327
|
-
async function write(rows) {
|
|
33328
|
-
if (rows.length === 0) return;
|
|
33329
|
-
await push(rows.map(encodeRow).join(LINE_SEPARATOR) + LINE_SEPARATOR, rows.length);
|
|
33330
|
-
}
|
|
33331
|
-
async function writeRaw(lines) {
|
|
33332
|
-
if (lines.length === 0) return;
|
|
33333
|
-
await push(lines.join(LINE_SEPARATOR) + LINE_SEPARATOR, lines.length);
|
|
33334
|
-
}
|
|
33335
|
-
async function commit() {
|
|
33336
|
-
source.end();
|
|
33337
|
-
await done;
|
|
33338
|
-
await promises.rename(tmpPath, path);
|
|
33339
|
-
return rowCount;
|
|
33340
|
-
}
|
|
33341
|
-
async function abort() {
|
|
33342
|
-
source.destroy();
|
|
33343
|
-
try {
|
|
33344
|
-
await done;
|
|
33345
|
-
} catch {
|
|
33346
|
-
}
|
|
33347
|
-
await promises.rm(tmpPath, { force: true });
|
|
33348
|
-
}
|
|
33349
|
-
return { write, writeRaw, commit, abort };
|
|
33350
|
-
}
|
|
33351
|
-
async function readRowsForPackages(path, packageNames) {
|
|
33352
|
-
const out = /* @__PURE__ */ new Map();
|
|
33353
|
-
if (packageNames.size === 0) return out;
|
|
33354
|
-
let raw;
|
|
33355
|
-
try {
|
|
33356
|
-
raw = await promises.readFile(path);
|
|
33357
|
-
} catch {
|
|
33358
|
-
return out;
|
|
33359
|
-
}
|
|
33360
|
-
const text = zlib.gunzipSync(raw).toString("utf8");
|
|
33361
|
-
let offset = 0;
|
|
33362
|
-
while (offset < text.length) {
|
|
33363
|
-
let end = text.indexOf(LINE_SEPARATOR, offset);
|
|
33364
|
-
if (end === -1) end = text.length;
|
|
33365
|
-
const nameEnd = text.indexOf(FIELD_SEPARATOR, offset);
|
|
33366
|
-
if (nameEnd === -1 || nameEnd > end) {
|
|
33367
|
-
offset = end + 1;
|
|
33368
|
-
continue;
|
|
33369
|
-
}
|
|
33370
|
-
const name = text.slice(offset, nameEnd);
|
|
33371
|
-
if (packageNames.has(name)) {
|
|
33372
|
-
const idEnd = text.indexOf(FIELD_SEPARATOR, nameEnd + 1);
|
|
33373
|
-
if (idEnd !== -1 && idEnd < end) {
|
|
33374
|
-
const parsed = safeParse3(text.slice(idEnd + 1, end));
|
|
33375
|
-
if (parsed) {
|
|
33376
|
-
const list = out.get(name);
|
|
33377
|
-
if (list) {
|
|
33378
|
-
list.push(parsed);
|
|
33379
|
-
} else {
|
|
33380
|
-
out.set(name, [parsed]);
|
|
33381
|
-
}
|
|
33382
|
-
}
|
|
33383
|
-
}
|
|
33384
|
-
}
|
|
33385
|
-
offset = end + 1;
|
|
33386
|
-
}
|
|
33387
|
-
return out;
|
|
33388
|
-
}
|
|
33389
|
-
async function rewriteRows(path, input) {
|
|
33390
|
-
let surviving = [];
|
|
33391
|
-
try {
|
|
33392
|
-
const raw = await promises.readFile(path);
|
|
33393
|
-
const text = zlib.gunzipSync(raw).toString("utf8");
|
|
33394
|
-
for (const line of text.split(LINE_SEPARATOR)) {
|
|
33395
|
-
if (line.length === 0) continue;
|
|
33396
|
-
const nameEnd = line.indexOf(FIELD_SEPARATOR);
|
|
33397
|
-
if (nameEnd === -1) continue;
|
|
33398
|
-
const idEnd = line.indexOf(FIELD_SEPARATOR, nameEnd + 1);
|
|
33399
|
-
if (idEnd === -1) continue;
|
|
33400
|
-
const advisoryId = line.slice(nameEnd + 1, idEnd);
|
|
33401
|
-
if (input.dropAdvisoryIds.has(advisoryId)) continue;
|
|
33402
|
-
surviving.push(line);
|
|
33403
|
-
}
|
|
33404
|
-
} catch {
|
|
33405
|
-
surviving = [];
|
|
33406
|
-
}
|
|
33407
|
-
const writer = createRowWriter(path);
|
|
33408
|
-
try {
|
|
33409
|
-
const CHUNK = 5e3;
|
|
33410
|
-
for (let i = 0; i < surviving.length; i += CHUNK) {
|
|
33411
|
-
await writer.writeRaw(surviving.slice(i, i + CHUNK));
|
|
33412
|
-
}
|
|
33413
|
-
await writer.write(input.append);
|
|
33414
|
-
return await writer.commit();
|
|
33415
|
-
} catch (err) {
|
|
33416
|
-
await writer.abort();
|
|
33417
|
-
throw err;
|
|
33418
|
-
}
|
|
33419
|
-
}
|
|
33420
|
-
async function countRows(path) {
|
|
33421
|
-
try {
|
|
33422
|
-
const raw = await promises.readFile(path);
|
|
33423
|
-
const text = zlib.gunzipSync(raw).toString("utf8");
|
|
33424
|
-
let count = 0;
|
|
33425
|
-
let offset = 0;
|
|
33426
|
-
for (; ; ) {
|
|
33427
|
-
const next = text.indexOf(LINE_SEPARATOR, offset);
|
|
33428
|
-
if (next === -1) break;
|
|
33429
|
-
count++;
|
|
33430
|
-
offset = next + 1;
|
|
33431
|
-
}
|
|
33432
|
-
return count;
|
|
33433
|
-
} catch {
|
|
33434
|
-
return 0;
|
|
33435
|
-
}
|
|
33436
|
-
}
|
|
33437
|
-
function safeParse3(json3) {
|
|
33438
|
-
try {
|
|
33439
|
-
return JSON.parse(json3);
|
|
33440
|
-
} catch {
|
|
33441
|
-
return null;
|
|
33442
|
-
}
|
|
33443
|
-
}
|
|
33444
|
-
|
|
33445
|
-
// src/cache/lookup.ts
|
|
33446
|
-
async function loadCacheForPackages(cacheDir, ecosystem, packageNames, sources) {
|
|
33447
|
-
const wanted = new Set(packageNames);
|
|
33448
|
-
const osv = /* @__PURE__ */ new Map();
|
|
33449
|
-
const gemnasium = /* @__PURE__ */ new Map();
|
|
33450
|
-
if (sources.includes("osv")) {
|
|
33451
|
-
const rows = await readRowsForPackages(advisoryFilePath(cacheDir, "osv", ecosystem), wanted);
|
|
33452
|
-
for (const [name, list] of rows.entries()) {
|
|
33453
|
-
osv.set(name, list.map(toOsvAdvisory));
|
|
33454
|
-
}
|
|
33455
|
-
}
|
|
33456
|
-
if (sources.includes("gemnasium")) {
|
|
33457
|
-
const rows = await readRowsForPackages(advisoryFilePath(cacheDir, "gemnasium", ecosystem), wanted);
|
|
33458
|
-
for (const [name, list] of rows.entries()) {
|
|
33459
|
-
gemnasium.set(name, list.map(toGemnasiumAdvisory));
|
|
33460
|
-
}
|
|
33461
|
-
}
|
|
33462
|
-
return { osv, gemnasium };
|
|
33463
|
-
}
|
|
33464
|
-
function cacheEcosystemKey(ecosystem) {
|
|
33465
|
-
const def = getEcosystem(ecosystem);
|
|
33466
|
-
return def ? def.osvEcosystem : ecosystem;
|
|
33467
|
-
}
|
|
33468
|
-
function toOsvAdvisory(row) {
|
|
33469
|
-
return {
|
|
33470
|
-
advisoryId: row.advisoryId,
|
|
33471
|
-
aliases: row.aliases,
|
|
33472
|
-
ranges: row.ranges,
|
|
33473
|
-
versions: row.versions,
|
|
33474
|
-
severity: row.severity,
|
|
33475
|
-
summary: row.summary,
|
|
33476
|
-
url: row.url,
|
|
33477
|
-
malicious: row.malicious
|
|
33478
|
-
};
|
|
33479
|
-
}
|
|
33480
|
-
function toGemnasiumAdvisory(row) {
|
|
33481
|
-
return {
|
|
33482
|
-
advisoryId: row.advisoryId,
|
|
33483
|
-
aliases: row.aliases,
|
|
33484
|
-
ranges: row.ranges,
|
|
33485
|
-
versions: row.versions,
|
|
33486
|
-
severity: row.severity,
|
|
33487
|
-
summary: row.summary,
|
|
33488
|
-
url: row.url
|
|
33489
|
-
};
|
|
33490
|
-
}
|
|
33491
33228
|
|
|
33492
33229
|
// ../../packages/feeds/src/osv/feed.ts
|
|
33493
33230
|
var import_unzipper = __toESM(require_unzip2());
|
|
@@ -33521,8 +33258,8 @@ function isSlowTransientStatus(status) {
|
|
|
33521
33258
|
var FAST_MAX_ATTEMPTS = 4;
|
|
33522
33259
|
var RETRY_BASE_MS = 1e3;
|
|
33523
33260
|
var RETRY_FACTOR = 3;
|
|
33524
|
-
var DEFAULT_RETRY_WAIT_MS =
|
|
33525
|
-
var SLOW_RETRY_WAITS_MS = [
|
|
33261
|
+
var DEFAULT_RETRY_WAIT_MS = 15e3;
|
|
33262
|
+
var SLOW_RETRY_WAITS_MS = [2e3, 5e3, 8e3];
|
|
33526
33263
|
function delay(ms) {
|
|
33527
33264
|
return new Promise(function schedule(resolve5) {
|
|
33528
33265
|
setTimeout(resolve5, ms);
|
|
@@ -33532,7 +33269,7 @@ function retryBudgetMs(options) {
|
|
|
33532
33269
|
if (options && typeof options.retryWaitMs === "number" && options.retryWaitMs >= 0) return options.retryWaitMs;
|
|
33533
33270
|
return DEFAULT_RETRY_WAIT_MS;
|
|
33534
33271
|
}
|
|
33535
|
-
async function
|
|
33272
|
+
async function withRetry(describe3, perform, discard, options, timeoutMs) {
|
|
33536
33273
|
const budgetMs = retryBudgetMs(options);
|
|
33537
33274
|
const startedAt = Date.now();
|
|
33538
33275
|
let lastStatus;
|
|
@@ -33541,19 +33278,19 @@ async function fetchWithRetry(url2, init, options, timeoutMs) {
|
|
|
33541
33278
|
let slowAttempts = 0;
|
|
33542
33279
|
for (; ; ) {
|
|
33543
33280
|
attempt++;
|
|
33544
|
-
const response = await
|
|
33281
|
+
const response = await perform(signalFor(options, timeoutMs));
|
|
33545
33282
|
const slow = isSlowTransientStatus(response.status);
|
|
33546
33283
|
if (!slow && !isFastTransientStatus(response.status)) return response;
|
|
33547
33284
|
lastStatus = response.status;
|
|
33548
|
-
|
|
33285
|
+
await discard(response);
|
|
33549
33286
|
if (options && options.abortSignal && options.abortSignal.aborted) break;
|
|
33550
33287
|
if (slow) {
|
|
33551
|
-
const
|
|
33552
|
-
if (
|
|
33288
|
+
const elapsedMs2 = Date.now() - startedAt;
|
|
33289
|
+
if (elapsedMs2 >= budgetMs) break;
|
|
33553
33290
|
const nominalMs = SLOW_RETRY_WAITS_MS[Math.min(slowAttempts, SLOW_RETRY_WAITS_MS.length - 1)];
|
|
33554
|
-
const waitMs = Math.min(nominalMs, budgetMs -
|
|
33291
|
+
const waitMs = Math.min(nominalMs, budgetMs - elapsedMs2);
|
|
33555
33292
|
slowAttempts++;
|
|
33556
|
-
if (options && options.onRetry) options.onRetry({ status: lastStatus, attempt, waitMs, elapsedMs, budgetMs });
|
|
33293
|
+
if (options && options.onRetry) options.onRetry({ status: lastStatus, attempt, waitMs, elapsedMs: elapsedMs2, budgetMs });
|
|
33557
33294
|
await delay(waitMs);
|
|
33558
33295
|
continue;
|
|
33559
33296
|
}
|
|
@@ -33561,11 +33298,50 @@ async function fetchWithRetry(url2, init, options, timeoutMs) {
|
|
|
33561
33298
|
if (fastAttempts >= FAST_MAX_ATTEMPTS) break;
|
|
33562
33299
|
await delay(RETRY_BASE_MS * Math.pow(RETRY_FACTOR, fastAttempts - 1));
|
|
33563
33300
|
}
|
|
33564
|
-
|
|
33301
|
+
const elapsedMs = Date.now() - startedAt;
|
|
33302
|
+
throw new Error(
|
|
33303
|
+
describe3 + " failed: HTTP " + lastStatus + " after " + attempt + " attempts over " + Math.round(elapsedMs / 1e3) + "s"
|
|
33304
|
+
);
|
|
33305
|
+
}
|
|
33306
|
+
async function fetchWithRetry(url2, init, options, timeoutMs) {
|
|
33307
|
+
return await withRetry(
|
|
33308
|
+
describeMethod(init) + " " + url2,
|
|
33309
|
+
function perform(signal) {
|
|
33310
|
+
return fetch(url2, { ...init, signal });
|
|
33311
|
+
},
|
|
33312
|
+
async function discard(response) {
|
|
33313
|
+
if (response.body) await response.body.cancel();
|
|
33314
|
+
},
|
|
33315
|
+
options,
|
|
33316
|
+
timeoutMs
|
|
33317
|
+
);
|
|
33565
33318
|
}
|
|
33566
33319
|
function describeMethod(init) {
|
|
33567
33320
|
return typeof init.method === "string" && init.method.length > 0 ? init.method : "GET";
|
|
33568
33321
|
}
|
|
33322
|
+
var MAX_REDIRECTS = 5;
|
|
33323
|
+
function openRawStream(url2, signal, redirectsLeft) {
|
|
33324
|
+
return new Promise(function attempt(resolve5, reject) {
|
|
33325
|
+
const target = new URL(url2);
|
|
33326
|
+
const send = target.protocol === "http:" ? http.request : https.request;
|
|
33327
|
+
const request = send(target, { method: "GET", headers: baseHeaders(), signal, agent: false }, function onResponse(response) {
|
|
33328
|
+
const status = response.statusCode;
|
|
33329
|
+
const location = response.headers.location;
|
|
33330
|
+
if (status >= 300 && status < 400 && location) {
|
|
33331
|
+
response.resume();
|
|
33332
|
+
if (redirectsLeft <= 0) {
|
|
33333
|
+
reject(new Error("GET " + url2 + " exceeded " + MAX_REDIRECTS + " redirects"));
|
|
33334
|
+
return;
|
|
33335
|
+
}
|
|
33336
|
+
resolve5(openRawStream(new URL(location, target).toString(), signal, redirectsLeft - 1));
|
|
33337
|
+
return;
|
|
33338
|
+
}
|
|
33339
|
+
resolve5({ status, headers: response.headers, stream: response });
|
|
33340
|
+
});
|
|
33341
|
+
request.on("error", reject);
|
|
33342
|
+
request.end();
|
|
33343
|
+
});
|
|
33344
|
+
}
|
|
33569
33345
|
async function getTextConditional(url2, etag, options) {
|
|
33570
33346
|
const headers = baseHeaders();
|
|
33571
33347
|
if (etag) headers["If-None-Match"] = etag;
|
|
@@ -33609,21 +33385,33 @@ async function headFile(url2, options) {
|
|
|
33609
33385
|
};
|
|
33610
33386
|
}
|
|
33611
33387
|
async function openDownloadStream(url2, onProgress, options) {
|
|
33612
|
-
const response = await
|
|
33613
|
-
|
|
33614
|
-
|
|
33615
|
-
|
|
33616
|
-
|
|
33388
|
+
const response = await withRetry(
|
|
33389
|
+
"GET " + url2,
|
|
33390
|
+
function perform(signal) {
|
|
33391
|
+
return openRawStream(url2, signal, MAX_REDIRECTS);
|
|
33392
|
+
},
|
|
33393
|
+
function discard(raw) {
|
|
33394
|
+
raw.stream.resume();
|
|
33395
|
+
},
|
|
33396
|
+
options,
|
|
33397
|
+
DOWNLOAD_TIMEOUT_MS
|
|
33398
|
+
);
|
|
33399
|
+
if (response.status < 200 || response.status >= 300) {
|
|
33400
|
+
response.stream.resume();
|
|
33401
|
+
throw new Error("GET " + url2 + " failed: HTTP " + response.status);
|
|
33402
|
+
}
|
|
33403
|
+
const rawLength = response.headers["content-length"];
|
|
33404
|
+
const parsedLength = rawLength === void 0 ? NaN : Number(rawLength);
|
|
33617
33405
|
const contentLength = Number.isFinite(parsedLength) ? parsedLength : null;
|
|
33618
|
-
const stream
|
|
33406
|
+
const stream = response.stream;
|
|
33619
33407
|
if (onProgress) {
|
|
33620
33408
|
let bytesRead = 0;
|
|
33621
|
-
stream
|
|
33409
|
+
stream.on("data", function countChunk(chunk) {
|
|
33622
33410
|
bytesRead += chunk.length;
|
|
33623
33411
|
onProgress(bytesRead, contentLength);
|
|
33624
33412
|
});
|
|
33625
33413
|
}
|
|
33626
|
-
return { stream
|
|
33414
|
+
return { stream, contentLength, lastModified: response.headers["last-modified"] ?? null };
|
|
33627
33415
|
}
|
|
33628
33416
|
|
|
33629
33417
|
// ../../packages/feeds/src/osv/normalize.ts
|
|
@@ -33768,24 +33556,28 @@ async function* streamOsvSeed(ecosystem, onProgress, options) {
|
|
|
33768
33556
|
const abortSignal = options && options.abortSignal;
|
|
33769
33557
|
let batch = [];
|
|
33770
33558
|
const zip = download.stream.pipe(import_unzipper.default.Parse({ forceStream: true }));
|
|
33771
|
-
|
|
33772
|
-
|
|
33773
|
-
|
|
33774
|
-
|
|
33775
|
-
|
|
33776
|
-
|
|
33777
|
-
entry.
|
|
33778
|
-
|
|
33559
|
+
try {
|
|
33560
|
+
for await (const entry of zip) {
|
|
33561
|
+
if (abortSignal && abortSignal.aborted) {
|
|
33562
|
+
entry.autodrain();
|
|
33563
|
+
throw new Error("aborted");
|
|
33564
|
+
}
|
|
33565
|
+
if (entry.type !== "File" || !String(entry.path).endsWith(".json")) {
|
|
33566
|
+
entry.autodrain();
|
|
33567
|
+
continue;
|
|
33568
|
+
}
|
|
33569
|
+
const content = await entry.buffer();
|
|
33570
|
+
for (const row of parseSeedEntry(content, ecosystem)) batch.push(row);
|
|
33571
|
+
if (batch.length >= BATCH_SIZE) {
|
|
33572
|
+
yield { rows: batch, lastModified: download.lastModified };
|
|
33573
|
+
batch = [];
|
|
33574
|
+
}
|
|
33779
33575
|
}
|
|
33780
|
-
|
|
33781
|
-
for (const row of parseSeedEntry(content, ecosystem)) batch.push(row);
|
|
33782
|
-
if (batch.length >= BATCH_SIZE) {
|
|
33576
|
+
if (batch.length > 0) {
|
|
33783
33577
|
yield { rows: batch, lastModified: download.lastModified };
|
|
33784
|
-
batch = [];
|
|
33785
33578
|
}
|
|
33786
|
-
}
|
|
33787
|
-
|
|
33788
|
-
yield { rows: batch, lastModified: download.lastModified };
|
|
33579
|
+
} finally {
|
|
33580
|
+
download.stream.destroy();
|
|
33789
33581
|
}
|
|
33790
33582
|
}
|
|
33791
33583
|
async function fetchOsvChangedIds(ecosystem, cursorMs, etag, options) {
|
|
@@ -34073,23 +33865,28 @@ function nonEmpty(s) {
|
|
|
34073
33865
|
}
|
|
34074
33866
|
|
|
34075
33867
|
// ../../packages/feeds/src/gemnasium/feed.ts
|
|
34076
|
-
var DEFAULT_ARCHIVE_URL = "https://gitlab.com/gitlab-org/security-products/gemnasium-db/-/archive/master/gemnasium-db-master.zip";
|
|
34077
33868
|
var DEFAULT_API_BASE = "https://gitlab.com/api/v4/projects/gitlab-org%2Fsecurity-products%2Fgemnasium-db";
|
|
34078
33869
|
var DEFAULT_REF = "master";
|
|
34079
33870
|
var BATCH_SIZE2 = 2e3;
|
|
34080
33871
|
var GEMNASIUM_COMPARE_MAX_FILES = 1e3;
|
|
34081
|
-
function
|
|
33872
|
+
function archiveOverride() {
|
|
34082
33873
|
const fromEnv = process.env.SENTINELLO_GEMNASIUM_FEED_URL;
|
|
34083
33874
|
if (fromEnv && fromEnv.trim().length > 0) return fromEnv.trim();
|
|
34084
|
-
return
|
|
33875
|
+
return null;
|
|
34085
33876
|
}
|
|
34086
33877
|
function apiBase() {
|
|
34087
33878
|
const fromEnv = process.env.SENTINELLO_GEMNASIUM_API_URL;
|
|
34088
33879
|
if (fromEnv && fromEnv.trim().length > 0) return fromEnv.trim();
|
|
34089
33880
|
return DEFAULT_API_BASE;
|
|
34090
33881
|
}
|
|
33882
|
+
function archiveUrl(ref) {
|
|
33883
|
+
const override = archiveOverride();
|
|
33884
|
+
if (override) return override;
|
|
33885
|
+
return apiBase() + "/repository/archive.zip?sha=" + encodeURIComponent(ref ?? DEFAULT_REF);
|
|
33886
|
+
}
|
|
34091
33887
|
function gemnasiumFeedDisabled() {
|
|
34092
|
-
|
|
33888
|
+
const override = archiveOverride();
|
|
33889
|
+
return override !== null && override.toLowerCase() === "off";
|
|
34093
33890
|
}
|
|
34094
33891
|
var PACKAGE_TYPE_TO_ECOSYSTEM = (function buildMap() {
|
|
34095
33892
|
const map3 = {};
|
|
@@ -34163,30 +33960,34 @@ function advisoryIdFromPath(path) {
|
|
|
34163
33960
|
const id = dot > 0 ? file2.slice(0, dot) : file2;
|
|
34164
33961
|
return id.length > 0 ? id : null;
|
|
34165
33962
|
}
|
|
34166
|
-
async function* streamGemnasiumArchive(onProgress, options) {
|
|
34167
|
-
const download = await openDownloadStream(archiveUrl(), onProgress, options);
|
|
33963
|
+
async function* streamGemnasiumArchive(ref, onProgress, options) {
|
|
33964
|
+
const download = await openDownloadStream(archiveUrl(ref), onProgress, options);
|
|
34168
33965
|
const abortSignal = options && options.abortSignal;
|
|
34169
33966
|
let batch = [];
|
|
34170
33967
|
const zip = download.stream.pipe(import_unzipper2.default.Parse({ forceStream: true }));
|
|
34171
|
-
|
|
34172
|
-
|
|
34173
|
-
|
|
34174
|
-
|
|
34175
|
-
|
|
34176
|
-
|
|
34177
|
-
|
|
34178
|
-
entry.
|
|
34179
|
-
|
|
33968
|
+
try {
|
|
33969
|
+
for await (const entry of zip) {
|
|
33970
|
+
if (abortSignal && abortSignal.aborted) {
|
|
33971
|
+
entry.autodrain();
|
|
33972
|
+
throw new Error("aborted");
|
|
33973
|
+
}
|
|
33974
|
+
const cell = advisoryPathEcosystem(String(entry.path), 1);
|
|
33975
|
+
if (entry.type !== "File" || !cell) {
|
|
33976
|
+
entry.autodrain();
|
|
33977
|
+
continue;
|
|
33978
|
+
}
|
|
33979
|
+
const content = await entry.buffer();
|
|
33980
|
+
for (const row of parseAdvisoryYaml(content, cell)) batch.push(row);
|
|
33981
|
+
if (batch.length >= BATCH_SIZE2) {
|
|
33982
|
+
yield { rows: batch, lastModified: download.lastModified };
|
|
33983
|
+
batch = [];
|
|
33984
|
+
}
|
|
34180
33985
|
}
|
|
34181
|
-
|
|
34182
|
-
for (const row of parseAdvisoryYaml(content, cell)) batch.push(row);
|
|
34183
|
-
if (batch.length >= BATCH_SIZE2) {
|
|
33986
|
+
if (batch.length > 0) {
|
|
34184
33987
|
yield { rows: batch, lastModified: download.lastModified };
|
|
34185
|
-
batch = [];
|
|
34186
33988
|
}
|
|
34187
|
-
}
|
|
34188
|
-
|
|
34189
|
-
yield { rows: batch, lastModified: download.lastModified };
|
|
33989
|
+
} finally {
|
|
33990
|
+
download.stream.destroy();
|
|
34190
33991
|
}
|
|
34191
33992
|
}
|
|
34192
33993
|
function parseAdvisoryYaml(content, cell) {
|
|
@@ -34198,6 +33999,288 @@ function parseAdvisoryYaml(content, cell) {
|
|
|
34198
33999
|
}
|
|
34199
34000
|
return normalizeGemnasiumRecord(parsed, cell.ecosystem, cell.slugPrefix);
|
|
34200
34001
|
}
|
|
34002
|
+
var CACHE_SCHEMA_VERSION = 1;
|
|
34003
|
+
function emptyMeta() {
|
|
34004
|
+
return { schemaVersion: CACHE_SCHEMA_VERSION, sources: { osv: {}, gemnasium: {} } };
|
|
34005
|
+
}
|
|
34006
|
+
function resolveCacheDir(override) {
|
|
34007
|
+
if (override && override.trim().length > 0) return path.resolve(override.trim());
|
|
34008
|
+
const fromEnv = process.env.SENTINELLO_CACHE_DIR;
|
|
34009
|
+
if (fromEnv && fromEnv.trim().length > 0) return path.resolve(fromEnv.trim());
|
|
34010
|
+
const xdg = process.env.XDG_CACHE_HOME;
|
|
34011
|
+
if (xdg && xdg.trim().length > 0) return path.join(path.resolve(xdg.trim()), "sentinello");
|
|
34012
|
+
return path.join(os.homedir(), ".cache", "sentinello");
|
|
34013
|
+
}
|
|
34014
|
+
function advisoryFilePath(cacheDir, source, ecosystem) {
|
|
34015
|
+
const slug = ecosystem.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
34016
|
+
return path.join(cacheDir, source + "-" + slug + ".ndjson.gz");
|
|
34017
|
+
}
|
|
34018
|
+
function metaPath(cacheDir) {
|
|
34019
|
+
return path.join(cacheDir, "meta.json");
|
|
34020
|
+
}
|
|
34021
|
+
async function ensureCacheDir(cacheDir) {
|
|
34022
|
+
await promises.mkdir(cacheDir, { recursive: true });
|
|
34023
|
+
}
|
|
34024
|
+
async function readCacheMeta(cacheDir) {
|
|
34025
|
+
let text;
|
|
34026
|
+
try {
|
|
34027
|
+
text = await promises.readFile(metaPath(cacheDir), "utf8");
|
|
34028
|
+
} catch {
|
|
34029
|
+
return emptyMeta();
|
|
34030
|
+
}
|
|
34031
|
+
let parsed;
|
|
34032
|
+
try {
|
|
34033
|
+
parsed = JSON.parse(text);
|
|
34034
|
+
} catch {
|
|
34035
|
+
return emptyMeta();
|
|
34036
|
+
}
|
|
34037
|
+
if (!parsed || typeof parsed !== "object") return emptyMeta();
|
|
34038
|
+
const meta3 = parsed;
|
|
34039
|
+
if (meta3.schemaVersion !== CACHE_SCHEMA_VERSION) return emptyMeta();
|
|
34040
|
+
const sources = meta3.sources;
|
|
34041
|
+
if (!sources || typeof sources !== "object") return emptyMeta();
|
|
34042
|
+
return {
|
|
34043
|
+
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
34044
|
+
sources: {
|
|
34045
|
+
osv: sources.osv ?? {},
|
|
34046
|
+
gemnasium: sources.gemnasium ?? {}
|
|
34047
|
+
}
|
|
34048
|
+
};
|
|
34049
|
+
}
|
|
34050
|
+
async function writeCacheMeta(cacheDir, meta3) {
|
|
34051
|
+
await ensureCacheDir(cacheDir);
|
|
34052
|
+
const target = metaPath(cacheDir);
|
|
34053
|
+
const tmp = target + ".tmp";
|
|
34054
|
+
await promises.writeFile(tmp, JSON.stringify(meta3, null, 4) + "\n", "utf8");
|
|
34055
|
+
await promises.rename(tmp, target);
|
|
34056
|
+
}
|
|
34057
|
+
function getSourceState(meta3, source, ecosystem) {
|
|
34058
|
+
return meta3.sources[source][ecosystem] ?? null;
|
|
34059
|
+
}
|
|
34060
|
+
function setSourceState(meta3, source, ecosystem, state) {
|
|
34061
|
+
meta3.sources[source][ecosystem] = state;
|
|
34062
|
+
}
|
|
34063
|
+
function isSeeded(meta3, source, ecosystem, normalizerVersion) {
|
|
34064
|
+
const state = getSourceState(meta3, source, ecosystem);
|
|
34065
|
+
if (!state) return false;
|
|
34066
|
+
return state.normalizerVersion === normalizerVersion && state.recordCount > 0;
|
|
34067
|
+
}
|
|
34068
|
+
async function tryAcquireLock(cacheDir) {
|
|
34069
|
+
await ensureCacheDir(cacheDir);
|
|
34070
|
+
const path$1 = path.join(cacheDir, ".lock");
|
|
34071
|
+
try {
|
|
34072
|
+
await promises.writeFile(path$1, String(process.pid), { flag: "wx" });
|
|
34073
|
+
} catch {
|
|
34074
|
+
if (await isStaleLock(path$1)) {
|
|
34075
|
+
await promises.rm(path$1, { force: true });
|
|
34076
|
+
return await tryAcquireLock(cacheDir);
|
|
34077
|
+
}
|
|
34078
|
+
return null;
|
|
34079
|
+
}
|
|
34080
|
+
async function release() {
|
|
34081
|
+
await promises.rm(path$1, { force: true });
|
|
34082
|
+
}
|
|
34083
|
+
return { release };
|
|
34084
|
+
}
|
|
34085
|
+
var LOCK_STALE_MS = 30 * 60 * 1e3;
|
|
34086
|
+
async function isStaleLock(path) {
|
|
34087
|
+
try {
|
|
34088
|
+
const info = await promises.stat(path);
|
|
34089
|
+
return Date.now() - info.mtimeMs > LOCK_STALE_MS;
|
|
34090
|
+
} catch {
|
|
34091
|
+
return false;
|
|
34092
|
+
}
|
|
34093
|
+
}
|
|
34094
|
+
var FIELD_SEPARATOR = " ";
|
|
34095
|
+
var LINE_SEPARATOR = "\n";
|
|
34096
|
+
function encodeRow(row) {
|
|
34097
|
+
const name = row.packageName.replace(/[\t\n\r]/g, "");
|
|
34098
|
+
const id = row.advisoryId.replace(/[\t\n\r]/g, "");
|
|
34099
|
+
return name + FIELD_SEPARATOR + id + FIELD_SEPARATOR + JSON.stringify(row);
|
|
34100
|
+
}
|
|
34101
|
+
function createRowWriter(path) {
|
|
34102
|
+
const tmpPath = path + ".tmp";
|
|
34103
|
+
const source = new stream.PassThrough();
|
|
34104
|
+
const gzip = zlib.createGzip();
|
|
34105
|
+
const done = promises$2.pipeline(source, gzip, fs.createWriteStream(tmpPath));
|
|
34106
|
+
let rowCount = 0;
|
|
34107
|
+
let failed = null;
|
|
34108
|
+
done.catch(function capture(err) {
|
|
34109
|
+
failed = err instanceof Error ? err : new Error(String(err));
|
|
34110
|
+
});
|
|
34111
|
+
async function push(chunk, added) {
|
|
34112
|
+
if (failed) throw failed;
|
|
34113
|
+
rowCount += added;
|
|
34114
|
+
if (!source.write(chunk)) {
|
|
34115
|
+
await new Promise(function waitForDrain(resolve5) {
|
|
34116
|
+
source.once("drain", resolve5);
|
|
34117
|
+
});
|
|
34118
|
+
}
|
|
34119
|
+
}
|
|
34120
|
+
async function write(rows) {
|
|
34121
|
+
if (rows.length === 0) return;
|
|
34122
|
+
await push(rows.map(encodeRow).join(LINE_SEPARATOR) + LINE_SEPARATOR, rows.length);
|
|
34123
|
+
}
|
|
34124
|
+
async function writeRaw(lines) {
|
|
34125
|
+
if (lines.length === 0) return;
|
|
34126
|
+
await push(lines.join(LINE_SEPARATOR) + LINE_SEPARATOR, lines.length);
|
|
34127
|
+
}
|
|
34128
|
+
async function commit() {
|
|
34129
|
+
source.end();
|
|
34130
|
+
await done;
|
|
34131
|
+
await promises.rename(tmpPath, path);
|
|
34132
|
+
return rowCount;
|
|
34133
|
+
}
|
|
34134
|
+
async function abort() {
|
|
34135
|
+
source.destroy();
|
|
34136
|
+
try {
|
|
34137
|
+
await done;
|
|
34138
|
+
} catch {
|
|
34139
|
+
}
|
|
34140
|
+
await promises.rm(tmpPath, { force: true });
|
|
34141
|
+
}
|
|
34142
|
+
return { write, writeRaw, commit, abort };
|
|
34143
|
+
}
|
|
34144
|
+
async function readRowsForPackages(path, packageNames) {
|
|
34145
|
+
const out = /* @__PURE__ */ new Map();
|
|
34146
|
+
if (packageNames.size === 0) return out;
|
|
34147
|
+
let raw;
|
|
34148
|
+
try {
|
|
34149
|
+
raw = await promises.readFile(path);
|
|
34150
|
+
} catch {
|
|
34151
|
+
return out;
|
|
34152
|
+
}
|
|
34153
|
+
const text = zlib.gunzipSync(raw).toString("utf8");
|
|
34154
|
+
let offset = 0;
|
|
34155
|
+
while (offset < text.length) {
|
|
34156
|
+
let end = text.indexOf(LINE_SEPARATOR, offset);
|
|
34157
|
+
if (end === -1) end = text.length;
|
|
34158
|
+
const nameEnd = text.indexOf(FIELD_SEPARATOR, offset);
|
|
34159
|
+
if (nameEnd === -1 || nameEnd > end) {
|
|
34160
|
+
offset = end + 1;
|
|
34161
|
+
continue;
|
|
34162
|
+
}
|
|
34163
|
+
const name = text.slice(offset, nameEnd);
|
|
34164
|
+
if (packageNames.has(name)) {
|
|
34165
|
+
const idEnd = text.indexOf(FIELD_SEPARATOR, nameEnd + 1);
|
|
34166
|
+
if (idEnd !== -1 && idEnd < end) {
|
|
34167
|
+
const parsed = safeParse3(text.slice(idEnd + 1, end));
|
|
34168
|
+
if (parsed) {
|
|
34169
|
+
const list = out.get(name);
|
|
34170
|
+
if (list) {
|
|
34171
|
+
list.push(parsed);
|
|
34172
|
+
} else {
|
|
34173
|
+
out.set(name, [parsed]);
|
|
34174
|
+
}
|
|
34175
|
+
}
|
|
34176
|
+
}
|
|
34177
|
+
}
|
|
34178
|
+
offset = end + 1;
|
|
34179
|
+
}
|
|
34180
|
+
return out;
|
|
34181
|
+
}
|
|
34182
|
+
async function rewriteRows(path, input) {
|
|
34183
|
+
let surviving = [];
|
|
34184
|
+
try {
|
|
34185
|
+
const raw = await promises.readFile(path);
|
|
34186
|
+
const text = zlib.gunzipSync(raw).toString("utf8");
|
|
34187
|
+
for (const line of text.split(LINE_SEPARATOR)) {
|
|
34188
|
+
if (line.length === 0) continue;
|
|
34189
|
+
const nameEnd = line.indexOf(FIELD_SEPARATOR);
|
|
34190
|
+
if (nameEnd === -1) continue;
|
|
34191
|
+
const idEnd = line.indexOf(FIELD_SEPARATOR, nameEnd + 1);
|
|
34192
|
+
if (idEnd === -1) continue;
|
|
34193
|
+
const advisoryId = line.slice(nameEnd + 1, idEnd);
|
|
34194
|
+
if (input.dropAdvisoryIds.has(advisoryId)) continue;
|
|
34195
|
+
surviving.push(line);
|
|
34196
|
+
}
|
|
34197
|
+
} catch {
|
|
34198
|
+
surviving = [];
|
|
34199
|
+
}
|
|
34200
|
+
const writer = createRowWriter(path);
|
|
34201
|
+
try {
|
|
34202
|
+
const CHUNK = 5e3;
|
|
34203
|
+
for (let i = 0; i < surviving.length; i += CHUNK) {
|
|
34204
|
+
await writer.writeRaw(surviving.slice(i, i + CHUNK));
|
|
34205
|
+
}
|
|
34206
|
+
await writer.write(input.append);
|
|
34207
|
+
return await writer.commit();
|
|
34208
|
+
} catch (err) {
|
|
34209
|
+
await writer.abort();
|
|
34210
|
+
throw err;
|
|
34211
|
+
}
|
|
34212
|
+
}
|
|
34213
|
+
async function countRows(path) {
|
|
34214
|
+
try {
|
|
34215
|
+
const raw = await promises.readFile(path);
|
|
34216
|
+
const text = zlib.gunzipSync(raw).toString("utf8");
|
|
34217
|
+
let count = 0;
|
|
34218
|
+
let offset = 0;
|
|
34219
|
+
for (; ; ) {
|
|
34220
|
+
const next = text.indexOf(LINE_SEPARATOR, offset);
|
|
34221
|
+
if (next === -1) break;
|
|
34222
|
+
count++;
|
|
34223
|
+
offset = next + 1;
|
|
34224
|
+
}
|
|
34225
|
+
return count;
|
|
34226
|
+
} catch {
|
|
34227
|
+
return 0;
|
|
34228
|
+
}
|
|
34229
|
+
}
|
|
34230
|
+
function safeParse3(json3) {
|
|
34231
|
+
try {
|
|
34232
|
+
return JSON.parse(json3);
|
|
34233
|
+
} catch {
|
|
34234
|
+
return null;
|
|
34235
|
+
}
|
|
34236
|
+
}
|
|
34237
|
+
|
|
34238
|
+
// src/cache/lookup.ts
|
|
34239
|
+
async function loadCacheForPackages(cacheDir, ecosystem, packageNames, sources) {
|
|
34240
|
+
const wanted = new Set(packageNames);
|
|
34241
|
+
const osv = /* @__PURE__ */ new Map();
|
|
34242
|
+
const gemnasium = /* @__PURE__ */ new Map();
|
|
34243
|
+
if (sources.includes("osv")) {
|
|
34244
|
+
const rows = await readRowsForPackages(advisoryFilePath(cacheDir, "osv", ecosystem), wanted);
|
|
34245
|
+
for (const [name, list] of rows.entries()) {
|
|
34246
|
+
osv.set(name, list.map(toOsvAdvisory));
|
|
34247
|
+
}
|
|
34248
|
+
}
|
|
34249
|
+
if (sources.includes("gemnasium")) {
|
|
34250
|
+
const rows = await readRowsForPackages(advisoryFilePath(cacheDir, "gemnasium", ecosystem), wanted);
|
|
34251
|
+
for (const [name, list] of rows.entries()) {
|
|
34252
|
+
gemnasium.set(name, list.map(toGemnasiumAdvisory));
|
|
34253
|
+
}
|
|
34254
|
+
}
|
|
34255
|
+
return { osv, gemnasium };
|
|
34256
|
+
}
|
|
34257
|
+
function cacheEcosystemKey(ecosystem) {
|
|
34258
|
+
const def = getEcosystem(ecosystem);
|
|
34259
|
+
return def ? def.osvEcosystem : ecosystem;
|
|
34260
|
+
}
|
|
34261
|
+
function toOsvAdvisory(row) {
|
|
34262
|
+
return {
|
|
34263
|
+
advisoryId: row.advisoryId,
|
|
34264
|
+
aliases: row.aliases,
|
|
34265
|
+
ranges: row.ranges,
|
|
34266
|
+
versions: row.versions,
|
|
34267
|
+
severity: row.severity,
|
|
34268
|
+
summary: row.summary,
|
|
34269
|
+
url: row.url,
|
|
34270
|
+
malicious: row.malicious
|
|
34271
|
+
};
|
|
34272
|
+
}
|
|
34273
|
+
function toGemnasiumAdvisory(row) {
|
|
34274
|
+
return {
|
|
34275
|
+
advisoryId: row.advisoryId,
|
|
34276
|
+
aliases: row.aliases,
|
|
34277
|
+
ranges: row.ranges,
|
|
34278
|
+
versions: row.versions,
|
|
34279
|
+
severity: row.severity,
|
|
34280
|
+
summary: row.summary,
|
|
34281
|
+
url: row.url
|
|
34282
|
+
};
|
|
34283
|
+
}
|
|
34201
34284
|
|
|
34202
34285
|
// src/cache/sync.ts
|
|
34203
34286
|
async function planSync(options) {
|
|
@@ -34388,7 +34471,7 @@ async function seedGemnasium(options, meta3, item) {
|
|
|
34388
34471
|
const writer = createRowWriter(path);
|
|
34389
34472
|
let rowCount;
|
|
34390
34473
|
try {
|
|
34391
|
-
for await (const batch of streamGemnasiumArchive(progressFor(options, item), fetchOptionsFor(options, item))) {
|
|
34474
|
+
for await (const batch of streamGemnasiumArchive(headSha, progressFor(options, item), fetchOptionsFor(options, item))) {
|
|
34392
34475
|
const rows = batch.rows.filter(function forEcosystem(row) {
|
|
34393
34476
|
return row.ecosystem === item.ecosystem;
|
|
34394
34477
|
});
|
|
@@ -34847,6 +34930,13 @@ function renderJson(summary, options, generatedAt) {
|
|
|
34847
34930
|
findings: summary.findings
|
|
34848
34931
|
}, null, 2) + "\n";
|
|
34849
34932
|
}
|
|
34933
|
+
function hasUnavailableSource(summary) {
|
|
34934
|
+
return summary.projects.some(function projectLostASource(project) {
|
|
34935
|
+
return project.unauditable.some(function lost(entry) {
|
|
34936
|
+
return isSourceUnavailableReason(entry.reasonCode);
|
|
34937
|
+
});
|
|
34938
|
+
});
|
|
34939
|
+
}
|
|
34850
34940
|
function shouldFail(summary, failOn) {
|
|
34851
34941
|
if (failOn === "none") return false;
|
|
34852
34942
|
if (failOn === "any") return summary.totalFindings > 0;
|
|
@@ -34994,7 +35084,7 @@ var SEVERITY_ORDER = ["critical", "high", "moderate", "low", "info"];
|
|
|
34994
35084
|
function formatBytes(bytes, estimated = false) {
|
|
34995
35085
|
if (bytes === null) return "unknown size";
|
|
34996
35086
|
const prefix = estimated ? "~" : "";
|
|
34997
|
-
if (bytes < 1024) return prefix + bytes + " B";
|
|
35087
|
+
if (bytes < 1024) return prefix + Math.round(bytes) + " B";
|
|
34998
35088
|
const mib = bytes / (1024 * 1024);
|
|
34999
35089
|
if (mib < 1) return prefix + (bytes / 1024).toFixed(0) + " KB";
|
|
35000
35090
|
if (mib < 1024) return prefix + mib.toFixed(1) + " MB";
|
|
@@ -35083,9 +35173,34 @@ function createUi(options) {
|
|
|
35083
35173
|
rl.close();
|
|
35084
35174
|
}
|
|
35085
35175
|
}
|
|
35086
|
-
function
|
|
35176
|
+
function sourcesSwitchedOff(sources) {
|
|
35177
|
+
const names = sources.map(sourceLabel).join(", ");
|
|
35178
|
+
write(" " + c.dim + "\xB7 " + names + " switched off and never seeded \u2014 not scanned" + c.reset);
|
|
35179
|
+
}
|
|
35180
|
+
async function confirmRetry(failed) {
|
|
35181
|
+
if (!interactive || !process.stdin.isTTY || !process.stderr.isTTY) return false;
|
|
35182
|
+
const names = failed.map(function label(outcome) {
|
|
35183
|
+
return sourceLabel(outcome.source);
|
|
35184
|
+
}).join(", ");
|
|
35185
|
+
write("");
|
|
35186
|
+
write(" " + c.yellow + names + " could not be downloaded." + c.reset);
|
|
35187
|
+
write(" " + c.dim + "The scan can continue without it, but anything only that source knows about" + c.reset);
|
|
35188
|
+
write(" " + c.dim + "will be missing from the report." + c.reset);
|
|
35189
|
+
const rl = promises$1.createInterface({ input: process.stdin, output: process.stderr });
|
|
35190
|
+
try {
|
|
35191
|
+
const answer = await rl.question(" Try the download again? " + c.dim + "[Y/n]" + c.reset + " ");
|
|
35192
|
+
const normalized = answer.trim().toLowerCase();
|
|
35193
|
+
return normalized === "" || normalized === "y" || normalized === "yes";
|
|
35194
|
+
} finally {
|
|
35195
|
+
rl.close();
|
|
35196
|
+
}
|
|
35197
|
+
}
|
|
35198
|
+
function seedDeclined(gated) {
|
|
35087
35199
|
write("");
|
|
35088
35200
|
write(" " + c.dim + "Skipped. Run again and accept, or pass --yes, to enable the advisory sources." + c.reset);
|
|
35201
|
+
if (gated) {
|
|
35202
|
+
write(" " + c.yellow + "--fail-on cannot be evaluated without them; exiting non-zero rather than reporting clean." + c.reset);
|
|
35203
|
+
}
|
|
35089
35204
|
write("");
|
|
35090
35205
|
}
|
|
35091
35206
|
function offlineNotice() {
|
|
@@ -35177,9 +35292,25 @@ function createUi(options) {
|
|
|
35177
35292
|
}
|
|
35178
35293
|
write(line);
|
|
35179
35294
|
}
|
|
35295
|
+
function lostSourceLines(runSummary) {
|
|
35296
|
+
const seen = /* @__PURE__ */ new Map();
|
|
35297
|
+
for (const project of runSummary.projects) {
|
|
35298
|
+
for (const entry of project.unauditable) {
|
|
35299
|
+
if (!isSourceUnavailableReason(entry.reasonCode)) continue;
|
|
35300
|
+
if (seen.has(entry.scanner)) continue;
|
|
35301
|
+
seen.set(entry.scanner, entry.errorText || entry.reasonCode);
|
|
35302
|
+
}
|
|
35303
|
+
}
|
|
35304
|
+
return Array.from(seen, function line([scanner, why]) {
|
|
35305
|
+
return sourceLabel(scanner) + c.dim + " " + why + c.reset;
|
|
35306
|
+
});
|
|
35307
|
+
}
|
|
35180
35308
|
function summary(runSummary, destination) {
|
|
35181
35309
|
write("");
|
|
35182
|
-
|
|
35310
|
+
const lost = lostSourceLines(runSummary);
|
|
35311
|
+
if (runSummary.totalFindings === 0 && lost.length > 0) {
|
|
35312
|
+
write(" " + c.yellow + c.bold + "No findings \u2014 but not everything could be checked." + c.reset);
|
|
35313
|
+
} else if (runSummary.totalFindings === 0) {
|
|
35183
35314
|
write(" " + c.green + c.bold + "No findings." + c.reset + c.dim + " " + runSummary.projects.length + " project" + (runSummary.projects.length === 1 ? "" : "s") + " clean." + c.reset);
|
|
35184
35315
|
} else {
|
|
35185
35316
|
const parts = [];
|
|
@@ -35200,6 +35331,13 @@ function createUi(options) {
|
|
|
35200
35331
|
write(" " + project.relPath.padEnd(38).slice(0, 38) + " " + breakdown);
|
|
35201
35332
|
}
|
|
35202
35333
|
}
|
|
35334
|
+
if (lost.length > 0) {
|
|
35335
|
+
write("");
|
|
35336
|
+
for (const line of lost) write(" " + c.yellow + "!" + c.reset + " " + line);
|
|
35337
|
+
if (options.failOn !== "none") {
|
|
35338
|
+
write(" " + c.dim + "--fail-on cannot be honoured by a scan that lost a source; exiting non-zero." + c.reset);
|
|
35339
|
+
}
|
|
35340
|
+
}
|
|
35203
35341
|
write("");
|
|
35204
35342
|
if (destination) {
|
|
35205
35343
|
write(" " + c.bold + "Advisory written to" + c.reset + " " + destination);
|
|
@@ -35220,6 +35358,8 @@ function createUi(options) {
|
|
|
35220
35358
|
discovered,
|
|
35221
35359
|
noProjects,
|
|
35222
35360
|
confirmSeed,
|
|
35361
|
+
confirmRetry,
|
|
35362
|
+
sourcesSwitchedOff,
|
|
35223
35363
|
seedDeclined,
|
|
35224
35364
|
offlineNotice,
|
|
35225
35365
|
syncStatus,
|
|
@@ -35236,8 +35376,8 @@ function createUi(options) {
|
|
|
35236
35376
|
|
|
35237
35377
|
// src/help.ts
|
|
35238
35378
|
var CLI_VERSION = (function resolveVersion() {
|
|
35239
|
-
if ("3.
|
|
35240
|
-
return "3.
|
|
35379
|
+
if ("3.1.0".length > 0) {
|
|
35380
|
+
return "3.1.0";
|
|
35241
35381
|
}
|
|
35242
35382
|
const fromEnv = process.env.SENTINELLO_VERSION;
|
|
35243
35383
|
if (fromEnv && fromEnv.trim().length > 0) return fromEnv.trim();
|
|
@@ -35270,10 +35410,10 @@ SOURCES
|
|
|
35270
35410
|
--offline Skip the freshness check and use the cached data as-is.
|
|
35271
35411
|
--cache-dir <path> Where the advisory cache lives.
|
|
35272
35412
|
Default: $XDG_CACHE_HOME/sentinello or ~/.cache/sentinello.
|
|
35273
|
-
--feed-wait <secs> How long to keep
|
|
35274
|
-
|
|
35275
|
-
|
|
35276
|
-
|
|
35413
|
+
--feed-wait <secs> How long to keep retrying a feed that declines a download.
|
|
35414
|
+
Default: 15 \u2014 short, because a declined download is offered
|
|
35415
|
+
as a retry rather than waited out. 0 gives up on the first
|
|
35416
|
+
refusal, leaving that source unseeded.
|
|
35277
35417
|
|
|
35278
35418
|
OUTPUT
|
|
35279
35419
|
--out <file|-> Write the advisory to a file, or "-" for stdout. With "-" the
|
|
@@ -35290,7 +35430,10 @@ OUTPUT
|
|
|
35290
35430
|
BEHAVIOUR
|
|
35291
35431
|
--fail-on <level> Exit 2 when a finding at this severity or above exists.
|
|
35292
35432
|
A severity, "any", or "none". Default: none.
|
|
35293
|
-
-y, --yes Accept every prompt, including the first-run download.
|
|
35433
|
+
-y, --yes Accept every prompt, including the first-run download. Required on a
|
|
35434
|
+
non-TTY: without it the download is refused, and under --fail-on
|
|
35435
|
+
that refusal exits 1 rather than reporting a clean scan. npx has a
|
|
35436
|
+
separate prompt of its own: in CI use npx --yes sentinello --yes
|
|
35294
35437
|
--doctor Print cache status, resolved settings, and detected projects.
|
|
35295
35438
|
-q, --quiet Suppress the human-readable output.
|
|
35296
35439
|
--verbose List every directory skipped by an ignore rule.
|
|
@@ -35300,7 +35443,8 @@ BEHAVIOUR
|
|
|
35300
35443
|
|
|
35301
35444
|
EXIT CODES
|
|
35302
35445
|
0 completed (findings alone are not a failure)
|
|
35303
|
-
1 a scan or configuration error
|
|
35446
|
+
1 a scan or configuration error, or a --fail-on run that lost an advisory source
|
|
35447
|
+
(a gate cannot be honoured by a scan that could not consult its sources)
|
|
35304
35448
|
2 the --fail-on threshold was met
|
|
35305
35449
|
|
|
35306
35450
|
CONFIG
|
|
@@ -35446,35 +35590,50 @@ async function runScan(options, cacheDir, ui) {
|
|
|
35446
35590
|
ui.noProjects(skipped);
|
|
35447
35591
|
return EXIT_OK;
|
|
35448
35592
|
}
|
|
35449
|
-
|
|
35450
|
-
|
|
35593
|
+
const sources = enabledSources(options.sources, await readCacheMeta(cacheDir));
|
|
35594
|
+
const switchedOff = options.sources.filter(function isOff(source) {
|
|
35595
|
+
return !sources.includes(source);
|
|
35596
|
+
});
|
|
35597
|
+
if (switchedOff.length > 0) ui.sourcesSwitchedOff(switchedOff);
|
|
35598
|
+
if (!options.offline && sources.length > 0) {
|
|
35599
|
+
const plan = await planSync({ cacheDir, sources, ecosystem: DEFAULT_ECOSYSTEM });
|
|
35451
35600
|
if (plan.needsConsent && !options.assumeYes) {
|
|
35452
35601
|
const approved = await ui.confirmSeed(plan);
|
|
35453
35602
|
if (!approved) {
|
|
35454
|
-
|
|
35455
|
-
|
|
35603
|
+
const gated = options.failOn !== "none";
|
|
35604
|
+
ui.seedDeclined(gated);
|
|
35605
|
+
return gated ? EXIT_ERROR : EXIT_OK;
|
|
35456
35606
|
}
|
|
35457
35607
|
}
|
|
35458
|
-
const
|
|
35608
|
+
const syncOptions = {
|
|
35459
35609
|
cacheDir,
|
|
35460
|
-
sources
|
|
35610
|
+
sources,
|
|
35461
35611
|
ecosystem: DEFAULT_ECOSYSTEM,
|
|
35462
35612
|
retryWaitMs: retryWaitMsFor(options.feedWaitSeconds),
|
|
35463
35613
|
onProgress: ui.syncProgress,
|
|
35464
35614
|
onRetry: ui.syncRetry,
|
|
35465
35615
|
onStatus: ui.syncStatus
|
|
35466
|
-
}
|
|
35616
|
+
};
|
|
35617
|
+
let outcomes = await runSync(syncOptions, plan);
|
|
35467
35618
|
ui.syncDone(outcomes);
|
|
35619
|
+
for (; ; ) {
|
|
35620
|
+
const failed = failedItems(plan, outcomes);
|
|
35621
|
+
if (failed.length === 0) break;
|
|
35622
|
+
const approved = await ui.confirmRetry(outcomes.filter(isErrorOutcome));
|
|
35623
|
+
if (!approved) break;
|
|
35624
|
+
outcomes = await runSync(syncOptions, { items: failed, seedBytes: 0, needsConsent: false });
|
|
35625
|
+
ui.syncDone(outcomes);
|
|
35626
|
+
}
|
|
35468
35627
|
} else if (options.offline) {
|
|
35469
35628
|
ui.offlineNotice();
|
|
35470
35629
|
}
|
|
35471
35630
|
const resolved = await resolveProjects(projects);
|
|
35472
35631
|
const packageNames = collectPackageNames(resolved);
|
|
35473
|
-
const cache = await loadCacheForPackages(cacheDir, DEFAULT_ECOSYSTEM, packageNames,
|
|
35632
|
+
const cache = await loadCacheForPackages(cacheDir, DEFAULT_ECOSYSTEM, packageNames, sources);
|
|
35474
35633
|
const meta3 = await readCacheMeta(cacheDir);
|
|
35475
35634
|
const setup = {
|
|
35476
35635
|
cacheDir,
|
|
35477
|
-
sources
|
|
35636
|
+
sources,
|
|
35478
35637
|
ecosystem: DEFAULT_ECOSYSTEM,
|
|
35479
35638
|
includeNpmAudit: options.includeNpmAudit,
|
|
35480
35639
|
seeded: {
|
|
@@ -35508,8 +35667,33 @@ async function runScan(options, cacheDir, ui) {
|
|
|
35508
35667
|
}
|
|
35509
35668
|
ui.summary(summary, destination);
|
|
35510
35669
|
if (shouldFail(summary, options.failOn)) return EXIT_THRESHOLD;
|
|
35670
|
+
if (options.failOn !== "none" && hasUnavailableSource(summary)) return EXIT_ERROR;
|
|
35511
35671
|
return EXIT_OK;
|
|
35512
35672
|
}
|
|
35673
|
+
function feedSwitchedOff(source) {
|
|
35674
|
+
if (source === "osv") return osvFeedDisabled();
|
|
35675
|
+
return gemnasiumFeedDisabled();
|
|
35676
|
+
}
|
|
35677
|
+
function normalizerVersionFor(source) {
|
|
35678
|
+
if (source === "osv") return OSV_NORMALIZER_VERSION;
|
|
35679
|
+
return GEMNASIUM_NORMALIZER_VERSION;
|
|
35680
|
+
}
|
|
35681
|
+
function enabledSources(sources, meta3) {
|
|
35682
|
+
return sources.filter(function isUsable(source) {
|
|
35683
|
+
if (!feedSwitchedOff(source)) return true;
|
|
35684
|
+
return isSeeded(meta3, source, DEFAULT_ECOSYSTEM, normalizerVersionFor(source));
|
|
35685
|
+
});
|
|
35686
|
+
}
|
|
35687
|
+
function isErrorOutcome(outcome) {
|
|
35688
|
+
return outcome.status === "error";
|
|
35689
|
+
}
|
|
35690
|
+
function failedItems(plan, outcomes) {
|
|
35691
|
+
return plan.items.filter(function wasError(item) {
|
|
35692
|
+
return outcomes.some(function matches(outcome) {
|
|
35693
|
+
return outcome.status === "error" && outcome.source === item.source && outcome.ecosystem === item.ecosystem;
|
|
35694
|
+
});
|
|
35695
|
+
});
|
|
35696
|
+
}
|
|
35513
35697
|
function retryWaitMsFor(feedWaitSeconds) {
|
|
35514
35698
|
if (feedWaitSeconds === null) return void 0;
|
|
35515
35699
|
return feedWaitSeconds * 1e3;
|