modelos-secure 0.1.0 → 0.1.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 +2 -0
- package/assets/login-BcHFyX6z.js +1 -0
- package/assets/{login-B21lxf7i.js → login-WwE-Gunj.js} +88 -16
- package/cli.js +7 -5
- package/package.json +1 -1
- package/assets/login-CVPBOTvx.js +0 -1
package/README.md
CHANGED
|
@@ -29,6 +29,8 @@ npx modelos-secure login --url <address> --client-id <client ID> --client-secret
|
|
|
29
29
|
|
|
30
30
|
Paste the workspace token when it asks. The token is never typed on the command line. Your browser then opens for you to sign in with Google. The address, the token and the Google sign-in are kept in `~/.modelos-secure/credentials.json`, readable only by you, and the sign-in is renewed as it runs out. Without `--client-id`, as on a local stack, there is no Google sign-in.
|
|
31
31
|
|
|
32
|
+
When the app is reached at an IP address and its certificate is issued for a name, the login command also has `--tls-name` and that name. The CLI then connects to the address and checks the certificate against the name: its chain, its dates and the name are all still checked. `MODELOS_SECURE_TLS_NAME` does the same for one run. When something cannot be reached, the CLI says why in plain words.
|
|
33
|
+
|
|
32
34
|
`MODELOS_SECURE_URL` and `MODELOS_SECURE_TOKEN` take the place of the saved address and token. `modelos-secure doctor` says who is signed in to Google, and why a request was turned away.
|
|
33
35
|
|
|
34
36
|
## In CI
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { t as loginCommand } from "./login-WwE-Gunj.js";
|
|
@@ -5,6 +5,8 @@ import { execFile, spawn } from "node:child_process";
|
|
|
5
5
|
import { createHash, randomBytes } from "node:crypto";
|
|
6
6
|
import { createServer } from "node:http";
|
|
7
7
|
import { emitKeypressEvents } from "node:readline";
|
|
8
|
+
import { request } from "node:https";
|
|
9
|
+
import { checkServerIdentity } from "node:tls";
|
|
8
10
|
// src/ui.ts
|
|
9
11
|
var color = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
10
12
|
var unicode = process.env.TERM !== "dumb" && !process.env.MODELOS_SECURE_ASCII;
|
|
@@ -276,6 +278,61 @@ function gcloudIdentityToken(audience) {
|
|
|
276
278
|
});
|
|
277
279
|
});
|
|
278
280
|
}
|
|
281
|
+
// src/tls.ts
|
|
282
|
+
/** A fetch that connects to the URL's host and checks its certificate against `name`. */
|
|
283
|
+
function fetchCheckingName(name) {
|
|
284
|
+
return (url, init = {}) => new Promise((resolve, reject) => {
|
|
285
|
+
const outgoing = request(new URL(url), {
|
|
286
|
+
method: init.method ?? "GET",
|
|
287
|
+
headers: init.headers,
|
|
288
|
+
servername: name,
|
|
289
|
+
checkServerIdentity: (_host, certificate) => checkServerIdentity(name, certificate),
|
|
290
|
+
signal: init.signal
|
|
291
|
+
}, (incoming) => {
|
|
292
|
+
const chunks = [];
|
|
293
|
+
incoming.on("data", (chunk) => chunks.push(chunk));
|
|
294
|
+
incoming.on("error", reject);
|
|
295
|
+
incoming.on("end", () => {
|
|
296
|
+
const headers = new Headers();
|
|
297
|
+
for (const [key, value] of Object.entries(incoming.headers)) if (value !== void 0) headers.set(key, Array.isArray(value) ? value.join(", ") : value);
|
|
298
|
+
const status = incoming.statusCode ?? 502;
|
|
299
|
+
resolve(new Response(status === 204 || status === 304 ? null : Buffer.concat(chunks), {
|
|
300
|
+
status,
|
|
301
|
+
headers
|
|
302
|
+
}));
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
outgoing.on("error", reject);
|
|
306
|
+
if (init.body !== void 0) outgoing.write(init.body);
|
|
307
|
+
outgoing.end();
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/** What went wrong reaching an address, in plain words, from the error Node gave. */
|
|
311
|
+
function networkMessage(error, url, tlsName) {
|
|
312
|
+
const cause = error?.cause;
|
|
313
|
+
const code = (typeof cause?.code === "string" ? cause.code : void 0) ?? error?.code;
|
|
314
|
+
let host = url;
|
|
315
|
+
try {
|
|
316
|
+
host = new URL(url).hostname;
|
|
317
|
+
} catch {}
|
|
318
|
+
switch (code) {
|
|
319
|
+
case "ERR_TLS_CERT_ALTNAME_INVALID": return tlsName ? `The certificate at ${host} is not issued for ${tlsName}. Copy the login command from the tokens page again.` : `The certificate at ${host} is not issued for that address. If it is issued for a name, such as one in your company's domain, log in again with --tls-name and that name.`;
|
|
320
|
+
case "DEPTH_ZERO_SELF_SIGNED_CERT":
|
|
321
|
+
case "SELF_SIGNED_CERT_IN_CHAIN":
|
|
322
|
+
case "UNABLE_TO_VERIFY_LEAF_SIGNATURE":
|
|
323
|
+
case "UNABLE_TO_GET_ISSUER_CERT_LOCALLY": return `This machine does not trust the certificate at ${host}. If your company adds its own certificates to this machine, set NODE_OPTIONS=--use-system-ca and try again.`;
|
|
324
|
+
case "CERT_HAS_EXPIRED": return `The certificate at ${host} has expired. Tell whoever runs that address.`;
|
|
325
|
+
case "ENOTFOUND":
|
|
326
|
+
case "EAI_AGAIN": return `${host} cannot be found from this machine. Check the address.`;
|
|
327
|
+
case "ECONNREFUSED":
|
|
328
|
+
case "ECONNRESET":
|
|
329
|
+
case "ETIMEDOUT":
|
|
330
|
+
case "EHOSTUNREACH":
|
|
331
|
+
case "ENETUNREACH":
|
|
332
|
+
case "UND_ERR_CONNECT_TIMEOUT": return `${host} cannot be reached from this machine. It may only be reachable on the company network.`;
|
|
333
|
+
default: return `The request to ${host} failed${code ? ` (${code})` : ""}.`;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
279
336
|
// src/iap.ts
|
|
280
337
|
/**
|
|
281
338
|
* The Google credential for one connection, in this order: MODELOS_SECURE_IAP_TOKEN as it is;
|
|
@@ -390,14 +447,14 @@ async function readConnection() {
|
|
|
390
447
|
const url = process.env.MODELOS_SECURE_URL ?? saved?.url;
|
|
391
448
|
const token = process.env.MODELOS_SECURE_TOKEN ?? saved?.token;
|
|
392
449
|
if (!url || !token) return null;
|
|
393
|
-
const
|
|
394
|
-
|
|
450
|
+
const same = Boolean(saved?.url && trim(saved.url) === trim(url));
|
|
451
|
+
const google = same ? saved?.google : void 0;
|
|
452
|
+
const tlsName = process.env.MODELOS_SECURE_TLS_NAME ?? (same ? saved?.tlsName : void 0);
|
|
453
|
+
return {
|
|
395
454
|
url: trim(url),
|
|
396
455
|
token,
|
|
397
|
-
google
|
|
398
|
-
|
|
399
|
-
url: trim(url),
|
|
400
|
-
token
|
|
456
|
+
...google ? { google } : {},
|
|
457
|
+
...tlsName ? { tlsName } : {}
|
|
401
458
|
};
|
|
402
459
|
}
|
|
403
460
|
/** The same connection at another address. The Google sign-in goes only to the same origin. */
|
|
@@ -431,7 +488,8 @@ async function saveRenewed(url, google) {
|
|
|
431
488
|
if (saved?.url && saved.token && trim(saved.url) === url && saved.google?.clientId === google.clientId) await saveConnection({
|
|
432
489
|
url: saved.url,
|
|
433
490
|
token: saved.token,
|
|
434
|
-
google
|
|
491
|
+
google,
|
|
492
|
+
...saved.tlsName ? { tlsName: saved.tlsName } : {}
|
|
435
493
|
});
|
|
436
494
|
}
|
|
437
495
|
var ApiError = class extends Error {
|
|
@@ -468,16 +526,26 @@ function apiClient(connection, options = {}) {
|
|
|
468
526
|
};
|
|
469
527
|
const call = async (method, path, body, signal) => {
|
|
470
528
|
const sent = await credential();
|
|
471
|
-
const
|
|
529
|
+
const url = `${connection.url}/api/v1${path}`;
|
|
530
|
+
const headers = {
|
|
531
|
+
authorization: `Bearer ${connection.token}`,
|
|
532
|
+
...sent.token ? { "proxy-authorization": `Bearer ${sent.token}` } : {},
|
|
533
|
+
...body ? { "content-type": body.type } : {}
|
|
534
|
+
};
|
|
535
|
+
const response = await (connection.tlsName ? fetchCheckingName(connection.tlsName)(url, {
|
|
536
|
+
method,
|
|
537
|
+
headers,
|
|
538
|
+
body: body?.text,
|
|
539
|
+
signal
|
|
540
|
+
}) : fetch(url, {
|
|
472
541
|
method,
|
|
473
542
|
redirect: "manual",
|
|
474
|
-
headers
|
|
475
|
-
authorization: `Bearer ${connection.token}`,
|
|
476
|
-
...sent.token ? { "proxy-authorization": `Bearer ${sent.token}` } : {},
|
|
477
|
-
...body ? { "content-type": body.type } : {}
|
|
478
|
-
},
|
|
543
|
+
headers,
|
|
479
544
|
body: body?.text,
|
|
480
545
|
signal
|
|
546
|
+
})).catch((error) => {
|
|
547
|
+
if (signal?.aborted) throw error;
|
|
548
|
+
throw new ApiError(networkMessage(error, connection.url, connection.tlsName), 0);
|
|
481
549
|
});
|
|
482
550
|
const text = await response.text();
|
|
483
551
|
let parsed;
|
|
@@ -524,25 +592,29 @@ function parseLoginLine(line) {
|
|
|
524
592
|
return {
|
|
525
593
|
url: after("url"),
|
|
526
594
|
clientId: after("client-id"),
|
|
527
|
-
clientSecret: after("client-secret")
|
|
595
|
+
clientSecret: after("client-secret"),
|
|
596
|
+
tlsName: after("tls-name")
|
|
528
597
|
};
|
|
529
598
|
}
|
|
530
599
|
async function loginCommand(given = {}) {
|
|
531
600
|
let url = given.url ?? process.env.MODELOS_SECURE_URL;
|
|
532
|
-
let { clientId, clientSecret } = given;
|
|
601
|
+
let { clientId, clientSecret, tlsName } = given;
|
|
533
602
|
if (!url) {
|
|
534
603
|
const pasted = parseLoginLine(await askText("Where is ModelOS Secure? Paste the login command from the tokens page, or type the address:"));
|
|
535
604
|
url = pasted.url;
|
|
536
605
|
clientId ??= pasted.clientId;
|
|
537
606
|
clientSecret ??= pasted.clientSecret;
|
|
607
|
+
tlsName ??= pasted.tlsName;
|
|
538
608
|
}
|
|
609
|
+
tlsName ??= process.env.MODELOS_SECURE_TLS_NAME;
|
|
539
610
|
if (!url) throw new Error("A login needs the address. Copy the login command from the tokens page.");
|
|
540
611
|
if (clientId && !clientSecret) throw new Error("The client ID needs its client secret. Copy the whole login command from the tokens page.");
|
|
541
612
|
const token = process.env.MODELOS_SECURE_TOKEN ?? await askText("Paste a workspace token from the tokens page:", true);
|
|
542
613
|
if (!token) throw new Error("A login needs a workspace token. Create one on the tokens page.");
|
|
543
614
|
const connection = {
|
|
544
615
|
url: url.replace(/\/$/, ""),
|
|
545
|
-
token
|
|
616
|
+
token,
|
|
617
|
+
...tlsName ? { tlsName } : {}
|
|
546
618
|
};
|
|
547
619
|
if (clientId && clientSecret) connection.google = await signInWithGoogle(clientId, clientSecret);
|
|
548
620
|
const me = await apiClient(connection).get("/whoami");
|
package/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as step, a as apiClient, c as Interrupted, d as blue, f as bold, g as say, h as red, i as IapRefusal, l as ask, m as grey, o as atAddress, p as dash, r as ApiError$1, s as readConnection, t as loginCommand, u as askText } from "./assets/login-
|
|
2
|
+
import { _ as step, a as apiClient, c as Interrupted, d as blue, f as bold, g as say, h as red, i as IapRefusal, l as ask, m as grey, o as atAddress, p as dash, r as ApiError$1, s as readConnection, t as loginCommand, u as askText } from "./assets/login-WwE-Gunj.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -19485,7 +19485,7 @@ async function scanCommand(dir, options) {
|
|
|
19485
19485
|
label: "Enter a token now",
|
|
19486
19486
|
recommended: true,
|
|
19487
19487
|
apply: async () => {
|
|
19488
|
-
const { loginCommand } = await import("./assets/login-
|
|
19488
|
+
const { loginCommand } = await import("./assets/login-BcHFyX6z.js");
|
|
19489
19489
|
await loginCommand();
|
|
19490
19490
|
const fresh = await readConnection();
|
|
19491
19491
|
api = fresh ? apiClient(fresh) : null;
|
|
@@ -19678,7 +19678,7 @@ function flag(name) {
|
|
|
19678
19678
|
const index = rest.indexOf(`--${name}`);
|
|
19679
19679
|
return index >= 0 ? rest[index + 1] : void 0;
|
|
19680
19680
|
}
|
|
19681
|
-
var positional = rest.filter((arg, i) => !arg.startsWith("--") && !rest[i - 1]?.match(/^--(format|out|system|instances|against|budget|url|client-id|client-secret)$/));
|
|
19681
|
+
var positional = rest.filter((arg, i) => !arg.startsWith("--") && !rest[i - 1]?.match(/^--(format|out|system|instances|against|budget|url|client-id|client-secret|tls-name)$/));
|
|
19682
19682
|
async function connection() {
|
|
19683
19683
|
const found = await readConnection();
|
|
19684
19684
|
if (!found) throw new Error("No workspace token found. Run modelos-secure login, or set MODELOS_SECURE_TOKEN.");
|
|
@@ -19687,7 +19687,8 @@ async function connection() {
|
|
|
19687
19687
|
var USAGE = `modelos-secure: test an AI system for Security, Safety and Quality.
|
|
19688
19688
|
|
|
19689
19689
|
login Saves a workspace token, and signs in with Google when the
|
|
19690
|
-
address needs it. --url, --client-id, --client-secret
|
|
19690
|
+
address needs it. --url, --client-id, --client-secret,
|
|
19691
|
+
--tls-name.
|
|
19691
19692
|
scan . Connects this repository. --offline, --explain, --yes.
|
|
19692
19693
|
review Starts a review of this system. --quick, --ci, --budget.
|
|
19693
19694
|
replay F-031 Replays one finding.
|
|
@@ -19703,7 +19704,8 @@ try {
|
|
|
19703
19704
|
if (command === "login") await loginCommand({
|
|
19704
19705
|
url: flag("url"),
|
|
19705
19706
|
clientId: flag("client-id"),
|
|
19706
|
-
clientSecret: flag("client-secret")
|
|
19707
|
+
clientSecret: flag("client-secret"),
|
|
19708
|
+
tlsName: flag("tls-name")
|
|
19707
19709
|
});
|
|
19708
19710
|
else if (command === "scan") await scanCommand(resolve(positional[0] ?? "."), {
|
|
19709
19711
|
offline: has("offline"),
|
package/package.json
CHANGED
package/assets/login-CVPBOTvx.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { t as loginCommand } from "./login-B21lxf7i.js";
|