@matthesketh/fleet 1.8.1 → 1.11.1
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 +186 -16
- package/dist/bin/fleet-agent.d.ts +2 -0
- package/dist/bin/fleet-agent.js +7 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +73 -31
- package/dist/commands/add.d.ts +2 -1
- package/dist/commands/add.js +66 -59
- package/dist/commands/audit.d.ts +1 -0
- package/dist/commands/audit.js +144 -0
- package/dist/commands/backup.d.ts +1 -0
- package/dist/commands/backup.js +510 -0
- package/dist/commands/boot-start.d.ts +3 -1
- package/dist/commands/boot-start.js +39 -47
- package/dist/commands/completions.d.ts +6 -0
- package/dist/commands/completions.js +83 -0
- package/dist/commands/config.d.ts +16 -0
- package/dist/commands/config.js +96 -0
- package/dist/commands/deploy.js +3 -2
- package/dist/commands/deps.js +5 -1
- package/dist/commands/doctor.d.ts +32 -0
- package/dist/commands/doctor.js +186 -0
- package/dist/commands/egress.d.ts +1 -1
- package/dist/commands/egress.js +13 -10
- package/dist/commands/freeze.d.ts +8 -4
- package/dist/commands/freeze.js +77 -59
- package/dist/commands/git.js +2 -2
- package/dist/commands/health.d.ts +2 -1
- package/dist/commands/health.js +38 -56
- package/dist/commands/init.d.ts +2 -1
- package/dist/commands/init.js +83 -73
- package/dist/commands/install-mcp.d.ts +3 -1
- package/dist/commands/install-mcp.js +53 -34
- package/dist/commands/list.d.ts +2 -1
- package/dist/commands/list.js +22 -19
- package/dist/commands/logs.js +1 -1
- package/dist/commands/patch-systemd.d.ts +7 -1
- package/dist/commands/patch-systemd.js +71 -31
- package/dist/commands/remove.d.ts +3 -1
- package/dist/commands/remove.js +37 -26
- package/dist/commands/restart.d.ts +4 -1
- package/dist/commands/restart.js +17 -20
- package/dist/commands/rollback.d.ts +4 -1
- package/dist/commands/rollback.js +33 -42
- package/dist/commands/secrets.js +157 -9
- package/dist/commands/start.d.ts +4 -1
- package/dist/commands/start.js +17 -20
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.js +21 -26
- package/dist/commands/stop.d.ts +4 -1
- package/dist/commands/stop.js +17 -20
- package/dist/commands/testflight.d.ts +1 -0
- package/dist/commands/testflight.js +193 -0
- package/dist/commands/update.d.ts +16 -0
- package/dist/commands/update.js +95 -0
- package/dist/core/audit/cache.d.ts +4 -0
- package/dist/core/audit/cache.js +37 -0
- package/dist/core/audit/config.d.ts +5 -0
- package/dist/core/audit/config.js +35 -0
- package/dist/core/audit/greenlight.d.ts +11 -0
- package/dist/core/audit/greenlight.js +81 -0
- package/dist/core/audit/reporters/cli.d.ts +3 -0
- package/dist/core/audit/reporters/cli.js +68 -0
- package/dist/core/audit/suppress.d.ts +6 -0
- package/dist/core/audit/suppress.js +37 -0
- package/dist/core/audit/target.d.ts +5 -0
- package/dist/core/audit/target.js +26 -0
- package/dist/core/audit/types.d.ts +54 -0
- package/dist/core/audit/types.js +5 -0
- package/dist/core/backup/browser-api.d.ts +66 -0
- package/dist/core/backup/browser-api.js +197 -0
- package/dist/core/backup/browser-server.d.ts +11 -0
- package/dist/core/backup/browser-server.js +241 -0
- package/dist/core/backup/browser-ui.d.ts +5 -0
- package/dist/core/backup/browser-ui.js +268 -0
- package/dist/core/backup/cloudflare.d.ts +7 -0
- package/dist/core/backup/cloudflare.js +82 -0
- package/dist/core/backup/config.d.ts +9 -0
- package/dist/core/backup/config.js +80 -0
- package/dist/core/backup/detect.d.ts +11 -0
- package/dist/core/backup/detect.js +71 -0
- package/dist/core/backup/dump.d.ts +11 -0
- package/dist/core/backup/dump.js +82 -0
- package/dist/core/backup/index.d.ts +9 -0
- package/dist/core/backup/index.js +9 -0
- package/dist/core/backup/repo.d.ts +71 -0
- package/dist/core/backup/repo.js +256 -0
- package/dist/core/backup/schedule.d.ts +17 -0
- package/dist/core/backup/schedule.js +90 -0
- package/dist/core/backup/sensitive.d.ts +5 -0
- package/dist/core/backup/sensitive.js +37 -0
- package/dist/core/backup/status.d.ts +3 -0
- package/dist/core/backup/status.js +29 -0
- package/dist/core/backup/statuspage.d.ts +23 -0
- package/dist/core/backup/statuspage.js +145 -0
- package/dist/core/backup/system.d.ts +24 -0
- package/dist/core/backup/system.js +209 -0
- package/dist/core/backup/totp.d.ts +16 -0
- package/dist/core/backup/totp.js +116 -0
- package/dist/core/backup/types.d.ts +70 -0
- package/dist/core/backup/types.js +7 -0
- package/dist/core/backup/unlock.d.ts +19 -0
- package/dist/core/backup/unlock.js +69 -0
- package/dist/core/boot-refresh.d.ts +1 -1
- package/dist/core/boot-refresh.js +10 -9
- package/dist/core/deps/actors/pr-creator.d.ts +5 -3
- package/dist/core/deps/actors/pr-creator.js +71 -18
- package/dist/core/deps/collectors/fetch-with-timeout.d.ts +7 -0
- package/dist/core/deps/collectors/fetch-with-timeout.js +16 -0
- package/dist/core/deps/collectors/npm.js +3 -1
- package/dist/core/deps/collectors/vulnerability.d.ts +8 -0
- package/dist/core/deps/collectors/vulnerability.js +31 -2
- package/dist/core/deps/config.js +6 -0
- package/dist/core/deps/scanner.js +1 -1
- package/dist/core/deps/types.d.ts +8 -0
- package/dist/core/env.d.ts +3 -0
- package/dist/core/env.js +11 -0
- package/dist/core/exec.d.ts +1 -0
- package/dist/core/exec.js +4 -0
- package/dist/core/file-lock.d.ts +18 -0
- package/dist/core/file-lock.js +44 -0
- package/dist/core/git-onboard.js +10 -13
- package/dist/core/github.d.ts +3 -1
- package/dist/core/github.js +10 -7
- package/dist/core/logs-policy.d.ts +5 -0
- package/dist/core/logs-policy.js +20 -1
- package/dist/core/operator.d.ts +21 -0
- package/dist/core/operator.js +54 -0
- package/dist/core/registry.d.ts +18 -0
- package/dist/core/registry.js +26 -0
- package/dist/core/routines/schema.d.ts +11 -11
- package/dist/core/routines/schema.js +14 -3
- package/dist/core/routines/store.d.ts +8 -8
- package/dist/core/secrets-ops.d.ts +31 -6
- package/dist/core/secrets-ops.js +208 -102
- package/dist/core/secrets-providers.js +2 -2
- package/dist/core/secrets-rotation.d.ts +1 -1
- package/dist/core/secrets-rotation.js +58 -52
- package/dist/core/secrets-v2-cleanup.d.ts +19 -0
- package/dist/core/secrets-v2-cleanup.js +94 -0
- package/dist/core/secrets-v2-creds.d.ts +9 -0
- package/dist/core/secrets-v2-creds.js +44 -0
- package/dist/core/secrets-v2-install.d.ts +13 -0
- package/dist/core/secrets-v2-install.js +76 -0
- package/dist/core/secrets-v2-keypair.d.ts +10 -0
- package/dist/core/secrets-v2-keypair.js +31 -0
- package/dist/core/secrets-v2-migrate.d.ts +29 -0
- package/dist/core/secrets-v2-migrate.js +395 -0
- package/dist/core/secrets-v2-ops.d.ts +36 -0
- package/dist/core/secrets-v2-ops.js +184 -0
- package/dist/core/secrets-v2-protocol.d.ts +19 -0
- package/dist/core/secrets-v2-protocol.js +60 -0
- package/dist/core/secrets-v2-snapshot.d.ts +36 -0
- package/dist/core/secrets-v2-snapshot.js +115 -0
- package/dist/core/secrets-v2.d.ts +21 -0
- package/dist/core/secrets-v2.js +249 -0
- package/dist/core/secrets.d.ts +39 -4
- package/dist/core/secrets.js +91 -11
- package/dist/core/self-update.d.ts +32 -11
- package/dist/core/self-update.js +52 -14
- package/dist/core/testflight/asc.d.ts +12 -0
- package/dist/core/testflight/asc.js +101 -0
- package/dist/core/testflight/credentials.d.ts +3 -0
- package/dist/core/testflight/credentials.js +35 -0
- package/dist/core/testflight/resolve.d.ts +6 -0
- package/dist/core/testflight/resolve.js +44 -0
- package/dist/core/testflight/types.d.ts +13 -0
- package/dist/core/testflight/types.js +3 -0
- package/dist/core/testflight/workflow.d.ts +17 -0
- package/dist/core/testflight/workflow.js +65 -0
- package/dist/core/validate.d.ts +1 -0
- package/dist/core/validate.js +8 -0
- package/dist/index.js +0 -0
- package/dist/mcp/audit-tools.d.ts +2 -0
- package/dist/mcp/audit-tools.js +94 -0
- package/dist/mcp/git-tools.js +1 -1
- package/dist/mcp/registry-bridge.d.ts +10 -0
- package/dist/mcp/registry-bridge.js +65 -0
- package/dist/mcp/secrets-tools.js +2 -2
- package/dist/mcp/server.js +16 -82
- package/dist/mcp/testflight-tools.d.ts +2 -0
- package/dist/mcp/testflight-tools.js +52 -0
- package/dist/registry/context.d.ts +7 -0
- package/dist/registry/context.js +37 -0
- package/dist/registry/index.d.ts +5 -0
- package/dist/registry/index.js +44 -0
- package/dist/registry/parse-args.d.ts +13 -0
- package/dist/registry/parse-args.js +74 -0
- package/dist/registry/registry.d.ts +24 -0
- package/dist/registry/registry.js +26 -0
- package/dist/registry/render.d.ts +3 -0
- package/dist/registry/render.js +29 -0
- package/dist/registry/types.d.ts +50 -0
- package/dist/registry/types.js +1 -0
- package/dist/templates/agent-unit.d.ts +5 -0
- package/dist/templates/agent-unit.js +40 -0
- package/dist/templates/app-unit-edit.d.ts +2 -0
- package/dist/templates/app-unit-edit.js +46 -0
- package/dist/templates/compose-edit.d.ts +2 -0
- package/dist/templates/compose-edit.js +156 -0
- package/dist/templates/nginx.js +11 -0
- package/dist/templates/systemd.js +6 -0
- package/dist/tui/components/ArgForm.d.ts +7 -0
- package/dist/tui/components/ArgForm.js +64 -0
- package/dist/tui/components/ArgForm.test.d.ts +1 -0
- package/dist/tui/components/ArgForm.test.js +19 -0
- package/dist/tui/components/KeyHint.js +5 -0
- package/dist/tui/hooks/use-secrets.d.ts +8 -8
- package/dist/tui/hooks/use-secrets.js +7 -7
- package/dist/tui/router.d.ts +1 -0
- package/dist/tui/router.js +26 -9
- package/dist/tui/router.test.d.ts +1 -0
- package/dist/tui/router.test.js +13 -0
- package/dist/tui/routines/components/SignalsGrid.test.js +2 -2
- package/dist/tui/routines/tabs/ScaffoldTab.js +1 -1
- package/dist/tui/tests/redaction-rerender.test.d.ts +1 -0
- package/dist/tui/tests/redaction-rerender.test.js +53 -0
- package/dist/tui/tests/scroll-flicker-proof.test.d.ts +1 -0
- package/dist/tui/tests/scroll-flicker-proof.test.js +145 -0
- package/dist/tui/types.d.ts +1 -1
- package/dist/tui/views/CommandPalette.d.ts +5 -0
- package/dist/tui/views/CommandPalette.js +90 -0
- package/dist/tui/views/CommandPalette.test.d.ts +1 -0
- package/dist/tui/views/CommandPalette.test.js +117 -0
- package/dist/tui/views/Dashboard.js +9 -6
- package/dist/tui/views/HealthView.js +9 -4
- package/dist/tui/views/SecretEdit.js +15 -16
- package/dist/tui/views/SecretEdit.test.d.ts +1 -0
- package/dist/tui/views/SecretEdit.test.js +82 -0
- package/dist/tui/views/SecretsView.js +26 -16
- package/package.json +8 -5
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
const B32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
3
|
+
function base32Encode(buf) {
|
|
4
|
+
let bits = 0;
|
|
5
|
+
let value = 0;
|
|
6
|
+
let out = '';
|
|
7
|
+
for (const byte of buf) {
|
|
8
|
+
value = (value << 8) | byte;
|
|
9
|
+
bits += 8;
|
|
10
|
+
while (bits >= 5) {
|
|
11
|
+
out += B32[(value >>> (bits - 5)) & 31];
|
|
12
|
+
bits -= 5;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (bits > 0)
|
|
16
|
+
out += B32[(value << (5 - bits)) & 31];
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
function base32Decode(s) {
|
|
20
|
+
const clean = s.replace(/=+$/, '').toUpperCase().replace(/\s/g, '');
|
|
21
|
+
let bits = 0;
|
|
22
|
+
let value = 0;
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const c of clean) {
|
|
25
|
+
const idx = B32.indexOf(c);
|
|
26
|
+
if (idx === -1)
|
|
27
|
+
throw new Error(`invalid base32 char: ${c}`);
|
|
28
|
+
value = (value << 5) | idx;
|
|
29
|
+
bits += 5;
|
|
30
|
+
if (bits >= 8) {
|
|
31
|
+
out.push((value >>> (bits - 8)) & 0xff);
|
|
32
|
+
bits -= 8;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return Buffer.from(out);
|
|
36
|
+
}
|
|
37
|
+
function hotp(secret, counter) {
|
|
38
|
+
const buf = Buffer.alloc(8);
|
|
39
|
+
buf.writeBigUInt64BE(BigInt(counter));
|
|
40
|
+
const hmac = createHmac('sha1', secret).update(buf).digest();
|
|
41
|
+
const offset = hmac[hmac.length - 1] & 0x0f;
|
|
42
|
+
const bin = ((hmac[offset] & 0x7f) << 24) |
|
|
43
|
+
((hmac[offset + 1] & 0xff) << 16) |
|
|
44
|
+
((hmac[offset + 2] & 0xff) << 8) |
|
|
45
|
+
(hmac[offset + 3] & 0xff);
|
|
46
|
+
return (bin % 1_000_000).toString().padStart(6, '0');
|
|
47
|
+
}
|
|
48
|
+
function timingSafeEqualStr(a, b) {
|
|
49
|
+
const ba = Buffer.from(a);
|
|
50
|
+
const bb = Buffer.from(b);
|
|
51
|
+
if (ba.length !== bb.length)
|
|
52
|
+
return false;
|
|
53
|
+
return timingSafeEqual(ba, bb);
|
|
54
|
+
}
|
|
55
|
+
/** new random 20-byte secret, base32-encoded for authenticator apps. */
|
|
56
|
+
export function generateSecret() {
|
|
57
|
+
return base32Encode(randomBytes(20));
|
|
58
|
+
}
|
|
59
|
+
/** the 6-digit TOTP code for a base32 secret at a given epoch-ms time. */
|
|
60
|
+
export function totpCode(secretB32, atMs = Date.now()) {
|
|
61
|
+
const counter = Math.floor(atMs / 1000 / 30);
|
|
62
|
+
return hotp(base32Decode(secretB32), counter);
|
|
63
|
+
}
|
|
64
|
+
/** true if `code` is valid for the secret within a +/-1 step window. */
|
|
65
|
+
export function verifyTotp(secretB32, code, atMs = Date.now()) {
|
|
66
|
+
if (!/^\d{6}$/.test(code))
|
|
67
|
+
return false;
|
|
68
|
+
const secret = base32Decode(secretB32);
|
|
69
|
+
const counter = Math.floor(atMs / 1000 / 30);
|
|
70
|
+
for (let w = -1; w <= 1; w++) {
|
|
71
|
+
if (timingSafeEqualStr(hotp(secret, counter + w), code))
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
/** otpauth:// enrolment URI — paste into 1Password or an authenticator app. */
|
|
77
|
+
export function totpUri(secretB32, label, issuer) {
|
|
78
|
+
const l = encodeURIComponent(label);
|
|
79
|
+
const i = encodeURIComponent(issuer);
|
|
80
|
+
return `otpauth://totp/${i}:${l}?secret=${secretB32}&issuer=${i}&period=30&digits=6&algorithm=SHA1`;
|
|
81
|
+
}
|
|
82
|
+
function b64url(buf) {
|
|
83
|
+
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
84
|
+
}
|
|
85
|
+
function b64urlDecode(s) {
|
|
86
|
+
return Buffer.from(s.replace(/-/g, '+').replace(/_/g, '/'), 'base64');
|
|
87
|
+
}
|
|
88
|
+
/** signs a session payload as `<body>.<hmac>` (hmac-sha256). */
|
|
89
|
+
export function signSession(payload, secret) {
|
|
90
|
+
const body = b64url(Buffer.from(JSON.stringify(payload)));
|
|
91
|
+
const sig = b64url(createHmac('sha256', secret).update(body).digest());
|
|
92
|
+
return `${body}.${sig}`;
|
|
93
|
+
}
|
|
94
|
+
/** verifies a session cookie; returns the payload or null if invalid/expired. */
|
|
95
|
+
export function verifySession(cookie, secret, nowMs = Date.now()) {
|
|
96
|
+
const parts = cookie.split('.');
|
|
97
|
+
if (parts.length !== 2)
|
|
98
|
+
return null;
|
|
99
|
+
const [body, sig] = parts;
|
|
100
|
+
const expected = b64url(createHmac('sha256', secret).update(body).digest());
|
|
101
|
+
if (!timingSafeEqualStr(sig, expected))
|
|
102
|
+
return null;
|
|
103
|
+
let payload;
|
|
104
|
+
try {
|
|
105
|
+
payload = JSON.parse(b64urlDecode(body).toString('utf-8'));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
if (payload === null || typeof payload !== 'object')
|
|
111
|
+
return null;
|
|
112
|
+
const exp = payload.exp;
|
|
113
|
+
if (typeof exp !== 'number' || exp < nowMs)
|
|
114
|
+
return null;
|
|
115
|
+
return { exp };
|
|
116
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type Retention = {
|
|
2
|
+
hourly?: number;
|
|
3
|
+
daily?: number;
|
|
4
|
+
weekly?: number;
|
|
5
|
+
monthly?: number;
|
|
6
|
+
yearly?: number;
|
|
7
|
+
};
|
|
8
|
+
export type Schedule = 'hourly' | '*-*-* 00/3:00:00' | '*-*-* 00/6:00:00' | '*-*-* 00/12:00:00' | 'daily' | 'weekly';
|
|
9
|
+
export type DumpType = 'postgres' | 'mysql' | 'mongo' | 'redis';
|
|
10
|
+
export interface DumpHook {
|
|
11
|
+
type: DumpType;
|
|
12
|
+
container: string;
|
|
13
|
+
/** for postgres/mysql: database name. for mongo: optional db filter. for redis: ignored. */
|
|
14
|
+
db?: string;
|
|
15
|
+
/** literal user value passed to the dump tool. takes precedence over userEnv.
|
|
16
|
+
* defaults sensibly per type (postgres: $POSTGRES_USER, mysql: root, mongo: root). */
|
|
17
|
+
user?: string;
|
|
18
|
+
/** port the db listens on inside the container. only used for redis (which
|
|
19
|
+
* has a default of 6379 but glitchtip et al re-bind to non-standard ports). */
|
|
20
|
+
port?: number;
|
|
21
|
+
/** path inside the container that holds the password (docker secrets pattern).
|
|
22
|
+
* preferred over passwordEnv because shared-* containers use _FILE secrets. */
|
|
23
|
+
passwordFile?: string;
|
|
24
|
+
/** shell command executed on the HOST (outside the container) whose stdout
|
|
25
|
+
* becomes the password. used when the secret lives in a host .env file that
|
|
26
|
+
* was consumed by docker-compose at startup but is no longer present in
|
|
27
|
+
* the container. fleet runs as root so it can read those files. */
|
|
28
|
+
passwordHostCommand?: string;
|
|
29
|
+
/** env var name in the container that holds the user. legacy. */
|
|
30
|
+
userEnv?: string;
|
|
31
|
+
/** env var name in the container that holds the password. legacy. */
|
|
32
|
+
passwordEnv?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface AppBackupConfig {
|
|
35
|
+
/** the app name as known to fleet (or `system`, `root-home`, `user-home` for pseudo-apps). */
|
|
36
|
+
app: string;
|
|
37
|
+
/** systemd OnCalendar expression. */
|
|
38
|
+
schedule: Schedule;
|
|
39
|
+
/** filesystem paths to include. */
|
|
40
|
+
paths: string[];
|
|
41
|
+
/** glob patterns to exclude. */
|
|
42
|
+
exclude: string[];
|
|
43
|
+
/** named docker volumes to dump alongside fs paths. */
|
|
44
|
+
volumes?: string[];
|
|
45
|
+
/** db dump to run before snapshot. */
|
|
46
|
+
preDump?: DumpHook;
|
|
47
|
+
/** post-snapshot cmd to run inside the host (rare). */
|
|
48
|
+
postHook?: string;
|
|
49
|
+
/** retention policy applied after every snapshot via restic forget --prune. */
|
|
50
|
+
retention: Retention;
|
|
51
|
+
/** disable: skip this app entirely. */
|
|
52
|
+
disabled?: boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface SnapshotInfo {
|
|
55
|
+
id: string;
|
|
56
|
+
shortId: string;
|
|
57
|
+
time: string;
|
|
58
|
+
hostname: string;
|
|
59
|
+
paths: string[];
|
|
60
|
+
tags: string[];
|
|
61
|
+
sizeBytes?: number;
|
|
62
|
+
}
|
|
63
|
+
export interface RepoStats {
|
|
64
|
+
totalSize: number;
|
|
65
|
+
totalFileCount: number;
|
|
66
|
+
snapshotCount: number;
|
|
67
|
+
}
|
|
68
|
+
export declare const PSEUDO_APPS: readonly ["system", "root-home", "user-home", "shared-postgres", "shared-mysql", "shared-mongodb"];
|
|
69
|
+
export type PseudoApp = typeof PSEUDO_APPS[number];
|
|
70
|
+
export declare function isPseudoApp(name: string): name is PseudoApp;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FleetError } from '../errors.js';
|
|
2
|
+
/** absolute path to the age recipient public key. */
|
|
3
|
+
export declare function agePubPath(): string;
|
|
4
|
+
/** systemd credstore entry holding the age identity. */
|
|
5
|
+
export declare function ageKeyCred(): string;
|
|
6
|
+
/** absolute path to the age unlock helper script. */
|
|
7
|
+
export declare function unlockScript(): string;
|
|
8
|
+
export declare class UnlockError extends FleetError {
|
|
9
|
+
}
|
|
10
|
+
/** path to the per-app age-encrypted restic password file. */
|
|
11
|
+
export declare function vaultPath(app: string): string;
|
|
12
|
+
/** path the running fleet binary points restic at as RESTIC_PASSWORD_COMMAND. */
|
|
13
|
+
export declare function passwordCommandFor(app: string): string;
|
|
14
|
+
/** returns the age public key (the recipient we encrypt restic passwords to). */
|
|
15
|
+
export declare function readPubKey(): string;
|
|
16
|
+
/** generate a random base64 password and age-encrypt it to the vault file. */
|
|
17
|
+
export declare function generateAndStorePassword(app: string): void;
|
|
18
|
+
/** read+decrypt the restic password for an app (held in memory only). */
|
|
19
|
+
export declare function fetchPassword(app: string): string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { requireEnv } from '../env.js';
|
|
4
|
+
import { FleetError } from '../errors.js';
|
|
5
|
+
import { execSafe } from '../exec.js';
|
|
6
|
+
import { backupVaultDir } from './config.js';
|
|
7
|
+
/** absolute path to the age recipient public key. */
|
|
8
|
+
export function agePubPath() { return requireEnv('FLEET_BACKUP_AGE_PUB'); }
|
|
9
|
+
/** systemd credstore entry holding the age identity. */
|
|
10
|
+
export function ageKeyCred() { return requireEnv('FLEET_BACKUP_AGE_KEY_CRED'); }
|
|
11
|
+
/** absolute path to the age unlock helper script. */
|
|
12
|
+
export function unlockScript() { return requireEnv('FLEET_BACKUP_UNLOCK_SCRIPT'); }
|
|
13
|
+
export class UnlockError extends FleetError {
|
|
14
|
+
}
|
|
15
|
+
/** path to the per-app age-encrypted restic password file. */
|
|
16
|
+
export function vaultPath(app) {
|
|
17
|
+
return join(backupVaultDir(), `${app}.age`);
|
|
18
|
+
}
|
|
19
|
+
/** path the running fleet binary points restic at as RESTIC_PASSWORD_COMMAND. */
|
|
20
|
+
export function passwordCommandFor(app) {
|
|
21
|
+
return `/usr/local/sbin/fleet-restic-app-key.sh ${app}`;
|
|
22
|
+
}
|
|
23
|
+
/** returns the age public key (the recipient we encrypt restic passwords to). */
|
|
24
|
+
export function readPubKey() {
|
|
25
|
+
const pubPath = agePubPath();
|
|
26
|
+
if (!existsSync(pubPath)) {
|
|
27
|
+
throw new UnlockError(`age public key not found at ${pubPath}. run fleet backup init.`);
|
|
28
|
+
}
|
|
29
|
+
const r = execSafe('cat', [pubPath], { timeout: 2_000 });
|
|
30
|
+
if (!r.ok)
|
|
31
|
+
throw new UnlockError(`failed reading ${pubPath}: ${r.stderr}`);
|
|
32
|
+
const pub = r.stdout.trim();
|
|
33
|
+
if (!pub.startsWith('age1'))
|
|
34
|
+
throw new UnlockError(`malformed age pubkey at ${pubPath}`);
|
|
35
|
+
return pub;
|
|
36
|
+
}
|
|
37
|
+
/** generate a random base64 password and age-encrypt it to the vault file. */
|
|
38
|
+
export function generateAndStorePassword(app) {
|
|
39
|
+
const dir = backupVaultDir();
|
|
40
|
+
if (!existsSync(dir)) {
|
|
41
|
+
execSafe('mkdir', ['-p', '--mode=700', dir], { timeout: 2_000 });
|
|
42
|
+
}
|
|
43
|
+
const pub = readPubKey();
|
|
44
|
+
// pipe: openssl rand -base64 48 | tr -d \n | age -e -r <pub> > vault
|
|
45
|
+
const out = vaultPath(app);
|
|
46
|
+
const r = execSafe('sh', ['-c', `openssl rand -base64 48 | tr -d '\\n' | age -e -r ${pub} > ${shellEscape(out)} && chmod 600 ${shellEscape(out)}`], { timeout: 10_000 });
|
|
47
|
+
if (!r.ok)
|
|
48
|
+
throw new UnlockError(`vault write failed: ${r.stderr}`);
|
|
49
|
+
}
|
|
50
|
+
/** read+decrypt the restic password for an app (held in memory only). */
|
|
51
|
+
export function fetchPassword(app) {
|
|
52
|
+
if (!existsSync(vaultPath(app))) {
|
|
53
|
+
throw new UnlockError(`no vault entry for app ${app}. run: fleet backup init ${app}`);
|
|
54
|
+
}
|
|
55
|
+
const keyCred = ageKeyCred();
|
|
56
|
+
if (!existsSync(keyCred)) {
|
|
57
|
+
throw new UnlockError(`age key credential not found at ${keyCred}. setup incomplete.`);
|
|
58
|
+
}
|
|
59
|
+
const r = execSafe('sh', ['-c', `${shellEscape(unlockScript())} | age -d -i /dev/stdin ${shellEscape(vaultPath(app))}`], { timeout: 5_000 });
|
|
60
|
+
if (!r.ok)
|
|
61
|
+
throw new UnlockError(`password decrypt failed: ${r.stderr}`);
|
|
62
|
+
const pass = r.stdout;
|
|
63
|
+
if (!pass)
|
|
64
|
+
throw new UnlockError(`decrypted password empty`);
|
|
65
|
+
return pass;
|
|
66
|
+
}
|
|
67
|
+
function shellEscape(s) {
|
|
68
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
69
|
+
}
|
|
@@ -33,7 +33,7 @@ export type BuildResult = {
|
|
|
33
33
|
reason: 'build-failed';
|
|
34
34
|
};
|
|
35
35
|
export declare function buildIfStale(app: AppEntry, currentHead: string): BuildResult;
|
|
36
|
-
export declare function recordBuiltCommit(appName: string, commit: string): void
|
|
36
|
+
export declare function recordBuiltCommit(appName: string, commit: string): Promise<void>;
|
|
37
37
|
export declare const KILL_SWITCH = "/etc/fleet/no-auto-refresh";
|
|
38
38
|
export declare const DEFAULT_WALL_CLOCK_MS = 900000;
|
|
39
39
|
export type RefreshResult = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { isGitRepo, getGitStatus } from './git.js';
|
|
3
3
|
import { execGit } from './exec.js';
|
|
4
|
-
import {
|
|
4
|
+
import { withRegistry } from './registry.js';
|
|
5
5
|
import { composeBuild } from './docker.js';
|
|
6
6
|
export function preflight(projectRoot) {
|
|
7
7
|
if (!isGitRepo(projectRoot))
|
|
@@ -53,13 +53,14 @@ export function buildIfStale(app, currentHead) {
|
|
|
53
53
|
return { ok: false, reason: 'build-failed' };
|
|
54
54
|
return { ok: true, built: true };
|
|
55
55
|
}
|
|
56
|
-
export function recordBuiltCommit(appName, commit) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
export async function recordBuiltCommit(appName, commit) {
|
|
57
|
+
await withRegistry(reg => {
|
|
58
|
+
const i = reg.apps.findIndex(a => a.name === appName);
|
|
59
|
+
if (i < 0)
|
|
60
|
+
return reg;
|
|
61
|
+
reg.apps[i] = { ...reg.apps[i], lastBuiltCommit: commit };
|
|
62
|
+
return reg;
|
|
63
|
+
});
|
|
63
64
|
}
|
|
64
65
|
export const KILL_SWITCH = '/etc/fleet/no-auto-refresh';
|
|
65
66
|
function killSwitchPath() {
|
|
@@ -80,7 +81,7 @@ async function doRefresh(app) {
|
|
|
80
81
|
if (!build.ok)
|
|
81
82
|
return { kind: 'failed-safe', step: 'build', detail: build.reason };
|
|
82
83
|
if (build.built)
|
|
83
|
-
recordBuiltCommit(app.name, ff.newHead);
|
|
84
|
+
await recordBuiltCommit(app.name, ff.newHead);
|
|
84
85
|
if (!ff.changed && !build.built)
|
|
85
86
|
return { kind: 'no-change', head: ff.newHead };
|
|
86
87
|
return { kind: 'refreshed', head: ff.newHead, built: build.built };
|
|
@@ -2,13 +2,15 @@ import type { AppEntry } from '../../registry.js';
|
|
|
2
2
|
import type { Finding } from '../types.js';
|
|
3
3
|
export interface VersionBump {
|
|
4
4
|
file: string;
|
|
5
|
-
|
|
5
|
+
searchRegex: RegExp;
|
|
6
6
|
replace: string;
|
|
7
7
|
}
|
|
8
8
|
export declare function generateVersionBump(finding: Finding): VersionBump | null;
|
|
9
9
|
export declare function buildPrBody(findings: Finding[]): string;
|
|
10
|
-
export
|
|
10
|
+
export interface CreateDepsPrResult {
|
|
11
11
|
branch: string;
|
|
12
12
|
bumps: VersionBump[];
|
|
13
13
|
prUrl?: string;
|
|
14
|
-
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function createDepsPr(app: AppEntry, findings: Finding[], dryRun: boolean): CreateDepsPrResult;
|
|
@@ -1,34 +1,41 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { execSafe } from '../../exec.js';
|
|
4
|
+
import { getGitStatus } from '../../git.js';
|
|
5
|
+
function escapeRegex(s) {
|
|
6
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
7
|
+
}
|
|
4
8
|
export function generateVersionBump(finding) {
|
|
5
9
|
if (!finding.fixable || !finding.package || !finding.currentVersion || !finding.latestVersion) {
|
|
6
10
|
return null;
|
|
7
11
|
}
|
|
12
|
+
const escName = escapeRegex(finding.package);
|
|
13
|
+
const escCurrent = escapeRegex(finding.currentVersion);
|
|
8
14
|
switch (finding.source) {
|
|
9
15
|
case 'npm':
|
|
10
16
|
return {
|
|
11
17
|
file: 'package.json',
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
// capture optional leading range operator (^, ~, >=, <=, >, <, =) so it survives the rewrite
|
|
19
|
+
searchRegex: new RegExp(`("${escName}"\\s*:\\s*")([\\^~>=<]*)${escCurrent}(")`),
|
|
20
|
+
replace: `$1$2${finding.latestVersion}$3`,
|
|
14
21
|
};
|
|
15
22
|
case 'composer':
|
|
16
23
|
return {
|
|
17
24
|
file: 'composer.json',
|
|
18
|
-
|
|
19
|
-
replace:
|
|
25
|
+
searchRegex: new RegExp(`("${escName}"\\s*:\\s*")([\\^~>=<]*)${escCurrent}(")`),
|
|
26
|
+
replace: `$1$2${finding.latestVersion}$3`,
|
|
20
27
|
};
|
|
21
28
|
case 'pip':
|
|
22
29
|
return {
|
|
23
30
|
file: 'requirements.txt',
|
|
24
|
-
|
|
25
|
-
replace: `${finding.
|
|
31
|
+
searchRegex: new RegExp(`(${escName}==)${escCurrent}\\b`),
|
|
32
|
+
replace: `$1${finding.latestVersion}`,
|
|
26
33
|
};
|
|
27
34
|
case 'docker-image':
|
|
28
35
|
return {
|
|
29
36
|
file: 'Dockerfile',
|
|
30
|
-
|
|
31
|
-
replace: `${finding.
|
|
37
|
+
searchRegex: new RegExp(`(${escName}:)${escCurrent}\\b`),
|
|
38
|
+
replace: `$1${finding.latestVersion}`,
|
|
32
39
|
};
|
|
33
40
|
default:
|
|
34
41
|
return null;
|
|
@@ -74,25 +81,71 @@ export function createDepsPr(app, findings, dryRun) {
|
|
|
74
81
|
if (dryRun) {
|
|
75
82
|
return { branch, bumps };
|
|
76
83
|
}
|
|
84
|
+
// Working-tree precheck: refuse to operate on a dirty repo. Otherwise the
|
|
85
|
+
// checkout/pull below can fail mid-way and leave the repo in a partial state,
|
|
86
|
+
// or worse, run on stale content.
|
|
87
|
+
const status = getGitStatus(app.composePath);
|
|
88
|
+
if (!status.initialised) {
|
|
89
|
+
return { branch: '', bumps: [], error: `${app.composePath} is not a git repo` };
|
|
90
|
+
}
|
|
91
|
+
if (!status.clean) {
|
|
92
|
+
return {
|
|
93
|
+
branch: '',
|
|
94
|
+
bumps: [],
|
|
95
|
+
error: `working tree at ${app.composePath} is dirty (${status.staged} staged, ${status.modified} modified, ${status.untracked} untracked) — commit or stash before running deps fix`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
77
98
|
const sshEnv = process.env.SSH_AUTH_SOCK ? { SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK } : {};
|
|
78
|
-
execSafe('git', ['checkout', 'develop'], { cwd: app.composePath });
|
|
79
|
-
|
|
80
|
-
|
|
99
|
+
const checkoutDevelop = execSafe('git', ['checkout', 'develop'], { cwd: app.composePath });
|
|
100
|
+
if (!checkoutDevelop.ok) {
|
|
101
|
+
return { branch: '', bumps: [], error: `git checkout develop failed: ${checkoutDevelop.stderr}` };
|
|
102
|
+
}
|
|
103
|
+
const pull = execSafe('git', ['pull'], { cwd: app.composePath, env: sshEnv });
|
|
104
|
+
if (!pull.ok) {
|
|
105
|
+
return { branch: '', bumps: [], error: `git pull failed: ${pull.stderr}` };
|
|
106
|
+
}
|
|
107
|
+
const checkoutBranch = execSafe('git', ['checkout', '-b', branch], { cwd: app.composePath });
|
|
108
|
+
if (!checkoutBranch.ok) {
|
|
109
|
+
return { branch: '', bumps: [], error: `git checkout -b ${branch} failed: ${checkoutBranch.stderr}` };
|
|
110
|
+
}
|
|
111
|
+
// Apply each bump and track which files actually changed.
|
|
112
|
+
const changedFiles = new Set();
|
|
81
113
|
for (const bump of bumps) {
|
|
82
114
|
const filePath = join(app.composePath, bump.file);
|
|
83
115
|
if (!existsSync(filePath))
|
|
84
116
|
continue;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
117
|
+
const before = readFileSync(filePath, 'utf-8');
|
|
118
|
+
const after = before.replace(bump.searchRegex, bump.replace);
|
|
119
|
+
if (after !== before) {
|
|
120
|
+
writeFileSync(filePath, after);
|
|
121
|
+
changedFiles.add(bump.file);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Range mismatch / unknown file content / nothing to update — abort before
|
|
125
|
+
// committing or pushing so we never open an empty PR.
|
|
126
|
+
if (changedFiles.size === 0) {
|
|
127
|
+
return {
|
|
128
|
+
branch: '',
|
|
129
|
+
bumps: [],
|
|
130
|
+
error: `no files changed: regex did not match any of ${[...new Set(bumps.map(b => b.file))].join(', ')}; the manifest may already be at the target version, or the version range syntax is unsupported`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const files = [...changedFiles];
|
|
134
|
+
const add = execSafe('git', ['add', ...files], { cwd: app.composePath });
|
|
135
|
+
if (!add.ok) {
|
|
136
|
+
return { branch: '', bumps: [], error: `git add failed: ${add.stderr}` };
|
|
88
137
|
}
|
|
89
|
-
const files = [...new Set(bumps.map(b => b.file))];
|
|
90
|
-
execSafe('git', ['add', ...files], { cwd: app.composePath });
|
|
91
138
|
const commitMsg = bumps.length === 1
|
|
92
139
|
? `chore(deps): update ${fixable[0].package} from ${fixable[0].currentVersion} to ${fixable[0].latestVersion}`
|
|
93
140
|
: `chore(deps): update ${bumps.length} dependencies`;
|
|
94
|
-
execSafe('git', ['commit', '-m', commitMsg], { cwd: app.composePath });
|
|
95
|
-
|
|
141
|
+
const commit = execSafe('git', ['commit', '-m', commitMsg], { cwd: app.composePath });
|
|
142
|
+
if (!commit.ok) {
|
|
143
|
+
return { branch: '', bumps: [], error: `git commit failed: ${commit.stderr}` };
|
|
144
|
+
}
|
|
145
|
+
const push = execSafe('git', ['push', '-u', 'origin', branch], { cwd: app.composePath, env: sshEnv });
|
|
146
|
+
if (!push.ok) {
|
|
147
|
+
return { branch: '', bumps: [], error: `git push failed: ${push.stderr}` };
|
|
148
|
+
}
|
|
96
149
|
if (!app.gitRepo)
|
|
97
150
|
return { branch, bumps };
|
|
98
151
|
const prBody = buildPrBody(fixable);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fetch wrapper that aborts after `timeoutMs`.
|
|
3
|
+
*
|
|
4
|
+
* Used by collectors that hit third-party HTTP services (OSV, npm registry).
|
|
5
|
+
* Without this, a hung peer would stall an entire scan indefinitely.
|
|
6
|
+
*/
|
|
7
|
+
export declare function fetchWithTimeout(url: string, init?: RequestInit, timeoutMs?: number): Promise<Response>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fetch wrapper that aborts after `timeoutMs`.
|
|
3
|
+
*
|
|
4
|
+
* Used by collectors that hit third-party HTTP services (OSV, npm registry).
|
|
5
|
+
* Without this, a hung peer would stall an entire scan indefinitely.
|
|
6
|
+
*/
|
|
7
|
+
export async function fetchWithTimeout(url, init = {}, timeoutMs = 10_000) {
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
10
|
+
try {
|
|
11
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { severityFromVersionDelta } from '../severity.js';
|
|
4
|
+
import { fetchWithTimeout } from './fetch-with-timeout.js';
|
|
5
|
+
const NPM_REGISTRY_TIMEOUT_MS = 10_000;
|
|
4
6
|
export class NpmCollector {
|
|
5
7
|
overrides;
|
|
6
8
|
type = 'npm';
|
|
@@ -37,7 +39,7 @@ export class NpmCollector {
|
|
|
37
39
|
async checkPackage(appName, name, currentRaw) {
|
|
38
40
|
const current = currentRaw.replace(/^[^\d]*/, '');
|
|
39
41
|
try {
|
|
40
|
-
const res = await
|
|
42
|
+
const res = await fetchWithTimeout(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`, {}, NPM_REGISTRY_TIMEOUT_MS);
|
|
41
43
|
if (!res.ok)
|
|
42
44
|
return null;
|
|
43
45
|
const data = await res.json();
|
|
@@ -2,6 +2,14 @@ import type { AppEntry } from '../../registry.js';
|
|
|
2
2
|
import type { Collector, Finding } from '../types.js';
|
|
3
3
|
export declare class VulnerabilityCollector implements Collector {
|
|
4
4
|
type: "vulnerability";
|
|
5
|
+
private skipRegexes;
|
|
6
|
+
/**
|
|
7
|
+
* @param osvSkipPatterns regex strings matched against bare package names.
|
|
8
|
+
* Matching packages are NOT sent to OSV (https://api.osv.dev), which is a
|
|
9
|
+
* third-party service. This prevents leaking private/internal package
|
|
10
|
+
* manifests to a third party.
|
|
11
|
+
*/
|
|
12
|
+
constructor(osvSkipPatterns?: string[]);
|
|
5
13
|
detect(appPath: string): boolean;
|
|
6
14
|
collect(app: AppEntry): Promise<Finding[]>;
|
|
7
15
|
private extractPackages;
|
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { severityFromCvss } from '../severity.js';
|
|
4
|
+
import { fetchWithTimeout } from './fetch-with-timeout.js';
|
|
5
|
+
const OSV_TIMEOUT_MS = 10_000;
|
|
4
6
|
export class VulnerabilityCollector {
|
|
5
7
|
type = 'vulnerability';
|
|
8
|
+
skipRegexes;
|
|
9
|
+
/**
|
|
10
|
+
* @param osvSkipPatterns regex strings matched against bare package names.
|
|
11
|
+
* Matching packages are NOT sent to OSV (https://api.osv.dev), which is a
|
|
12
|
+
* third-party service. This prevents leaking private/internal package
|
|
13
|
+
* manifests to a third party.
|
|
14
|
+
*/
|
|
15
|
+
constructor(osvSkipPatterns = []) {
|
|
16
|
+
this.skipRegexes = osvSkipPatterns
|
|
17
|
+
.map(p => {
|
|
18
|
+
try {
|
|
19
|
+
return new RegExp(p);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.filter((r) => r !== null);
|
|
26
|
+
}
|
|
6
27
|
detect(appPath) {
|
|
7
28
|
return (existsSync(join(appPath, 'package.json')) ||
|
|
8
29
|
existsSync(join(appPath, 'composer.json')) ||
|
|
@@ -63,18 +84,26 @@ export class VulnerabilityCollector {
|
|
|
63
84
|
}
|
|
64
85
|
catch { /* skip */ }
|
|
65
86
|
}
|
|
87
|
+
// Filter out packages that match any configured skip pattern. OSV is a
|
|
88
|
+
// third-party service (api.osv.dev, operated by Google), so the request
|
|
89
|
+
// payload is the package name + version. Internal/private package names
|
|
90
|
+
// would otherwise be exfiltrated. The default config skips the user's
|
|
91
|
+
// own npm scope.
|
|
92
|
+
if (this.skipRegexes.length > 0) {
|
|
93
|
+
return packages.filter(p => !this.skipRegexes.some(rx => rx.test(p.name)));
|
|
94
|
+
}
|
|
66
95
|
return packages;
|
|
67
96
|
}
|
|
68
97
|
async queryOsv(appName, pkg) {
|
|
69
98
|
try {
|
|
70
|
-
const res = await
|
|
99
|
+
const res = await fetchWithTimeout('https://api.osv.dev/v1/query', {
|
|
71
100
|
method: 'POST',
|
|
72
101
|
headers: { 'Content-Type': 'application/json' },
|
|
73
102
|
body: JSON.stringify({
|
|
74
103
|
version: pkg.version,
|
|
75
104
|
package: { name: pkg.name, ecosystem: pkg.ecosystem },
|
|
76
105
|
}),
|
|
77
|
-
});
|
|
106
|
+
}, OSV_TIMEOUT_MS);
|
|
78
107
|
if (!res.ok)
|
|
79
108
|
return [];
|
|
80
109
|
const data = await res.json();
|
package/dist/core/deps/config.js
CHANGED
|
@@ -21,6 +21,12 @@ export function defaultConfig() {
|
|
|
21
21
|
minorVersionBehind: 'medium',
|
|
22
22
|
patchVersionBehind: 'low',
|
|
23
23
|
},
|
|
24
|
+
// Skip OSV lookups for the user's own npm scope by default — OSV is a
|
|
25
|
+
// third-party service (Google) and sending internal package names there
|
|
26
|
+
// leaks the proprietary dependency manifest. Backwards compat: if an
|
|
27
|
+
// existing deps-config.json is missing this field, mergeConfig fills it
|
|
28
|
+
// in from these defaults.
|
|
29
|
+
osvSkipPatterns: ['^@matthesketh/'],
|
|
24
30
|
};
|
|
25
31
|
}
|
|
26
32
|
export function mergeConfig(base, overrides) {
|
|
@@ -14,7 +14,7 @@ export function createCollectors(config) {
|
|
|
14
14
|
new DockerImageCollector(config.severityOverrides),
|
|
15
15
|
new DockerRunningCollector(config.severityOverrides),
|
|
16
16
|
new EolCollector(config.severityOverrides.eolDaysWarning),
|
|
17
|
-
new VulnerabilityCollector(),
|
|
17
|
+
new VulnerabilityCollector(config.osvSkipPatterns),
|
|
18
18
|
new GitHubPrCollector(),
|
|
19
19
|
];
|
|
20
20
|
}
|
|
@@ -48,6 +48,14 @@ export interface DepsConfig {
|
|
|
48
48
|
minorVersionBehind: Severity;
|
|
49
49
|
patchVersionBehind: Severity;
|
|
50
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Regex strings (matched against bare package name) of packages that must
|
|
53
|
+
* NOT be queried against OSV (https://api.osv.dev). OSV is a third-party
|
|
54
|
+
* service operated by Google; sending private/internal package names there
|
|
55
|
+
* leaks the proprietary dependency manifest. Default skips the user's own
|
|
56
|
+
* npm scope.
|
|
57
|
+
*/
|
|
58
|
+
osvSkipPatterns: string[];
|
|
51
59
|
}
|
|
52
60
|
export interface DepsCache {
|
|
53
61
|
version: 1;
|