modelos-secure 0.3.2 → 0.3.3
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/cli.js +70 -19
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -3,9 +3,9 @@ import { _ as lastFinishedStep, a as apiClient, b as status, c as Interrupted, d
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
-
import { basename, delimiter, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
6
|
+
import { basename, delimiter, dirname, extname, join, posix, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { parseArgs } from "node:util";
|
|
8
|
-
import { access, appendFile, constants, glob, mkdir, readFile, readdir, realpath, rename, stat, writeFile } from "node:fs/promises";
|
|
8
|
+
import { access, appendFile, constants, glob, lstat, mkdir, readFile, readdir, realpath, rename, stat, writeFile } from "node:fs/promises";
|
|
9
9
|
import { tmpdir } from "node:os";
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
11
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -23,7 +23,7 @@ if (existsSync(resolve(here, "wasm"))) {
|
|
|
23
23
|
}
|
|
24
24
|
// package.json
|
|
25
25
|
var name = "modelos-secure";
|
|
26
|
-
var version$1 = "0.3.
|
|
26
|
+
var version$1 = "0.3.3";
|
|
27
27
|
var type = "module";
|
|
28
28
|
var bin = { "modelos-secure": "dist/cli.js" };
|
|
29
29
|
var exports$1 = { "./hook": "./dist/hook/hook.mjs" };
|
|
@@ -335,7 +335,17 @@ var SKIPPED = /* @__PURE__ */ new Set([
|
|
|
335
335
|
"e2e",
|
|
336
336
|
"coverage",
|
|
337
337
|
".turbo",
|
|
338
|
-
".cache"
|
|
338
|
+
".cache",
|
|
339
|
+
".idea",
|
|
340
|
+
".vscode",
|
|
341
|
+
".vs",
|
|
342
|
+
".terraform",
|
|
343
|
+
".ruff_cache",
|
|
344
|
+
".nox",
|
|
345
|
+
".gradle",
|
|
346
|
+
".yarn",
|
|
347
|
+
".svelte-kit",
|
|
348
|
+
".angular"
|
|
339
349
|
]);
|
|
340
350
|
var MAX_FILE_BYTES = 1048576;
|
|
341
351
|
var DOCUMENT = /* @__PURE__ */ new Set([
|
|
@@ -5621,6 +5631,30 @@ function docOf(node, file) {
|
|
|
5621
5631
|
// ../../packages/scan/src/start-candidates.ts
|
|
5622
5632
|
/** Words a README command starts with that start an app, not install or test one. */
|
|
5623
5633
|
var STARTS = /^(?:\$\s*)?((?:uvicorn|gunicorn|hypercorn|fastapi (?:dev|run)|flask (?:--app \S+ )?run|streamlit run|chainlit run|gradio|python3? (?:-m )?\S+\.py|python3? -m \S+|(?:npm|pnpm|yarn|bun) (?:run )?(?:dev|start|serve)|node \S+\.(?:m?js|ts)|npx tsx \S+|docker compose up|docker-compose up)\b.*)$/;
|
|
5634
|
+
/** The words of an instruction in its list form, or null when it is in the shell form. */
|
|
5635
|
+
function listForm(body) {
|
|
5636
|
+
try {
|
|
5637
|
+
const parts = JSON.parse(body);
|
|
5638
|
+
return Array.isArray(parts) ? parts.map(String) : null;
|
|
5639
|
+
} catch {
|
|
5640
|
+
return null;
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
/**
|
|
5644
|
+
* What a Dockerfile runs, as Docker reads it: the last stage's entrypoint with its command as the
|
|
5645
|
+
* arguments, unless the entrypoint is in the shell form, which ignores them. A health check's own
|
|
5646
|
+
* CMD is not it. Only arguments, starting with a dash, go to a program in the base image, which is
|
|
5647
|
+
* not here to name, so they are no command at all.
|
|
5648
|
+
*/
|
|
5649
|
+
function dockerCommand(dockerfile) {
|
|
5650
|
+
const lines = dockerfile.replace(/\\\r?\n/g, " ").split(/\r?\n/).map((l) => l.trim());
|
|
5651
|
+
const stage = lines.slice(lines.findLastIndex((l) => /^FROM\s/i.test(l)) + 1);
|
|
5652
|
+
const last = (word) => stage.findLast((l) => new RegExp(`^${word}\\s`, "i").test(l))?.slice(word.length).trim();
|
|
5653
|
+
const entrypoint = last("ENTRYPOINT");
|
|
5654
|
+
if (entrypoint && !listForm(entrypoint)) return entrypoint;
|
|
5655
|
+
const full = [entrypoint, last("CMD")].filter((body) => body !== void 0).map((body) => listForm(body)?.join(" ") ?? body).join(" ");
|
|
5656
|
+
return full && !full.startsWith("-") ? full : null;
|
|
5657
|
+
}
|
|
5624
5658
|
async function startCandidates(look, project) {
|
|
5625
5659
|
const files = new Set(project.files.map((f) => f.path));
|
|
5626
5660
|
const read = (file) => readFile(join(look.root, file), "utf8").catch(() => null);
|
|
@@ -5648,19 +5682,12 @@ async function startCandidates(look, project) {
|
|
|
5648
5682
|
from: `the main block at ${file.path}:${(main + 1).toString()}`
|
|
5649
5683
|
});
|
|
5650
5684
|
}
|
|
5651
|
-
const
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
if (Array.isArray(parts)) command = parts.map(String).join(" ");
|
|
5658
|
-
} catch {}
|
|
5659
|
-
if (command) found.push({
|
|
5660
|
-
command,
|
|
5661
|
-
from: "the Dockerfile's command"
|
|
5662
|
-
});
|
|
5663
|
-
}
|
|
5685
|
+
const dockerfile = await read("Dockerfile");
|
|
5686
|
+
const command = dockerfile ? dockerCommand(dockerfile) : null;
|
|
5687
|
+
if (command) found.push({
|
|
5688
|
+
command,
|
|
5689
|
+
from: "the Dockerfile's command"
|
|
5690
|
+
});
|
|
5664
5691
|
for (const name of [
|
|
5665
5692
|
"README.md",
|
|
5666
5693
|
"readme.md",
|
|
@@ -6395,15 +6422,36 @@ var LIMITS = {
|
|
|
6395
6422
|
maxFileBytes: 1e6
|
|
6396
6423
|
};
|
|
6397
6424
|
var CODE_TOOLS = ["grep", "read"];
|
|
6398
|
-
|
|
6425
|
+
var NOT_OPENED = "the scan never opens that folder";
|
|
6426
|
+
/** Whether the scan opens a folder at all: no part of its path is one it never walks. */
|
|
6427
|
+
var opensFolder = (dir) => !dir.split("/").some((part) => SKIPPED.has(part.toLowerCase()));
|
|
6428
|
+
/**
|
|
6429
|
+
* Every file in the repository the tools could see, and so what --explain counts. All of it by hand
|
|
6430
|
+
* rather than by glob, whose `*` leaves out names starting with a dot, which is where settings files
|
|
6431
|
+
* are. A link is not a file here: it could lead outside the repository. ADR 0093.
|
|
6432
|
+
*/
|
|
6399
6433
|
async function repositoryFiles(root, pattern = "**/*", most = 5e3) {
|
|
6400
6434
|
const found = [];
|
|
6435
|
+
if (pattern === "**/*") {
|
|
6436
|
+
const walk = async (dir) => {
|
|
6437
|
+
const entries = await readdir(join(root, dir), { withFileTypes: true }).catch(() => []);
|
|
6438
|
+
for (const entry of entries) {
|
|
6439
|
+
if (found.length >= most) return;
|
|
6440
|
+
const path = posix.join(dir, entry.name);
|
|
6441
|
+
if (entry.isFile()) found.push(path);
|
|
6442
|
+
else if (entry.isDirectory() && opensFolder(entry.name)) await walk(path);
|
|
6443
|
+
}
|
|
6444
|
+
};
|
|
6445
|
+
await walk("");
|
|
6446
|
+
return found.sort();
|
|
6447
|
+
}
|
|
6401
6448
|
for await (const entry of glob(pattern, {
|
|
6402
6449
|
cwd: root,
|
|
6403
6450
|
exclude: (name) => SKIPPED.has(String(name).split(/[\\/]/).at(-1) ?? "")
|
|
6404
6451
|
})) {
|
|
6405
6452
|
const path = String(entry).split("\\").join("/");
|
|
6406
|
-
if ((
|
|
6453
|
+
if (!opensFolder(posix.dirname(path))) continue;
|
|
6454
|
+
if ((await lstat(join(root, path)).catch(() => null))?.isFile()) found.push(path);
|
|
6407
6455
|
if (found.length >= most) break;
|
|
6408
6456
|
}
|
|
6409
6457
|
return found.sort();
|
|
@@ -6419,6 +6467,7 @@ function createTools(input) {
|
|
|
6419
6467
|
const dir = String(args.dir ?? ".");
|
|
6420
6468
|
const inRepo = await insideRepository(root, dir);
|
|
6421
6469
|
if (inRepo === null) return refused(dir, "it is not a folder in this repository");
|
|
6470
|
+
if (!opensFolder(inRepo)) return refused(inRepo, NOT_OPENED);
|
|
6422
6471
|
const entries = await readdir(join(root, inRepo), { withFileTypes: true }).catch(() => null);
|
|
6423
6472
|
if (!entries) return refused(dir, "it is not a folder in this repository");
|
|
6424
6473
|
return {
|
|
@@ -6463,6 +6512,7 @@ function createTools(input) {
|
|
|
6463
6512
|
if (inRepo === null) return refused(path, "it is not a file in this repository");
|
|
6464
6513
|
const why = neverRead(inRepo);
|
|
6465
6514
|
if (why) return refused(inRepo, why);
|
|
6515
|
+
if (!opensFolder(posix.dirname(inRepo))) return refused(inRepo, NOT_OPENED);
|
|
6466
6516
|
if ((await stat(join(root, inRepo))).size > LIMITS.maxFileBytes) return refused(inRepo, "it is over 1 MB");
|
|
6467
6517
|
const all = (await readFile(join(root, inRepo), "utf8")).split(/\r?\n/);
|
|
6468
6518
|
const from = Math.max(1, Number(args.from ?? (args.to === void 0 ? 1 : args.to - LIMITS.readLines + 1)));
|
|
@@ -22103,6 +22153,7 @@ async function scanCommand(dir, options) {
|
|
|
22103
22153
|
say(`\nNever sent, whatever is asked (${neverSent.length.toString()}):`);
|
|
22104
22154
|
for (const n of neverSent) say(` ${n.path}, because ${n.why}`);
|
|
22105
22155
|
if (neverSent.length === 0) say(" nothing here matches");
|
|
22156
|
+
say("\nThe scan never opens folders such as .git, node_modules and tests, so nothing in them is listed or read.");
|
|
22106
22157
|
say(`\nWhat was actually sent on earlier runs is in .modelos-secure/sent.jsonl.`);
|
|
22107
22158
|
return;
|
|
22108
22159
|
}
|