lynxor 1.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 ADDED
@@ -0,0 +1,12 @@
1
+ # lynxor
2
+
3
+ A 10-second security sanity check for Git repositories: committed secrets, exposed keys, risky Dockerfile patterns, CI/CD misconfigurations, and known-vulnerable dependencies.
4
+
5
+ This package is a thin installer: `postinstall` downloads the precompiled `lynxor` binary matching your platform from [GitHub Releases](https://github.com/xchebila/lynxor/releases), verifies its checksum, and wires it up as the `lynxor` command. Supported: Linux/macOS/Windows on amd64/arm64.
6
+
7
+ ```bash
8
+ npm install -g lynxor
9
+ lynxor scan .
10
+ ```
11
+
12
+ Full documentation, flags, and design decisions: [github.com/xchebila/lynxor](https://github.com/xchebila/lynxor).
package/bin/lynxor.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const path = require("node:path");
5
+ const { spawnSync } = require("node:child_process");
6
+
7
+ const binName = process.platform === "win32" ? "lynxor.exe" : "lynxor";
8
+ const binPath = path.join(__dirname, "..", ".bin", binName);
9
+
10
+ const result = spawnSync(binPath, process.argv.slice(2), { stdio: "inherit" });
11
+ if (result.error) {
12
+ console.error(
13
+ `lynxor: failed to launch ${binPath}: ${result.error.message}\n` +
14
+ `Try reinstalling: npm install lynxor`,
15
+ );
16
+ process.exit(1);
17
+ }
18
+ process.exit(result.status === null ? 1 : result.status);
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "lynxor",
3
+ "version": "1.1.0",
4
+ "description": "A 10-second security sanity check for Git repositories -- committed secrets, exposed keys, risky Dockerfile patterns, CI/CD misconfigurations, and known-vulnerable dependencies. This package downloads the matching precompiled lynxor binary from GitHub Releases on install.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/xchebila/lynxor.git",
9
+ "directory": "npm"
10
+ },
11
+ "homepage": "https://github.com/xchebila/lynxor",
12
+ "bugs": "https://github.com/xchebila/lynxor/issues",
13
+ "bin": {
14
+ "lynxor": "bin/lynxor.js"
15
+ },
16
+ "files": [
17
+ "bin",
18
+ "scripts"
19
+ ],
20
+ "scripts": {
21
+ "postinstall": "node scripts/install.js"
22
+ },
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "os": [
27
+ "linux",
28
+ "darwin",
29
+ "win32"
30
+ ],
31
+ "cpu": [
32
+ "x64",
33
+ "arm64"
34
+ ]
35
+ }
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // Downloads the lynxor binary matching this package's own version from
5
+ // GitHub Releases (published by .goreleaser.yaml / release.yml), verifies it
6
+ // against the release's checksums.txt, and extracts it into ../.bin.
7
+ // bin/lynxor.js execs whatever ends up there -- this script's only job is
8
+ // to make sure the right binary is present and actually runs before install
9
+ // finishes, so failures surface at `npm install` time, not at first use.
10
+
11
+ const fs = require("node:fs");
12
+ const path = require("node:path");
13
+ const os = require("node:os");
14
+ const crypto = require("node:crypto");
15
+ const { spawnSync } = require("node:child_process");
16
+
17
+ const PLATFORMS = { linux: "linux", darwin: "darwin", win32: "windows" };
18
+ const ARCHES = { x64: "amd64", arm64: "arm64" };
19
+
20
+ function fail(message) {
21
+ console.error(`lynxor: ${message}`);
22
+ process.exit(1);
23
+ }
24
+
25
+ async function download(url) {
26
+ const res = await fetch(url);
27
+ if (!res.ok) {
28
+ fail(`download failed (${res.status} ${res.statusText}): ${url}`);
29
+ }
30
+ return Buffer.from(await res.arrayBuffer());
31
+ }
32
+
33
+ async function main() {
34
+ const platform = PLATFORMS[process.platform];
35
+ const arch = ARCHES[process.arch];
36
+ if (!platform || !arch) {
37
+ fail(
38
+ `unsupported platform/arch combination: ${process.platform}/${process.arch}. ` +
39
+ `Supported: ${Object.keys(PLATFORMS).join(", ")} x ${Object.keys(ARCHES).join(", ")}. ` +
40
+ `Build from source instead: https://github.com/xchebila/lynxor#install--build`,
41
+ );
42
+ }
43
+
44
+ const pkg = require("../package.json");
45
+ const version = process.env.LYNXOR_INSTALL_VERSION || pkg.version;
46
+ const tag = `v${version}`;
47
+ const ext = platform === "windows" ? "zip" : "tar.gz";
48
+ const archiveName = `lynxor_${platform}_${arch}.${ext}`;
49
+ const base = `https://github.com/xchebila/lynxor/releases/download/${tag}`;
50
+
51
+ console.log(`lynxor: fetching ${archiveName} (${tag})...`);
52
+ const [archive, checksums] = await Promise.all([
53
+ download(`${base}/${archiveName}`),
54
+ download(`${base}/checksums.txt`),
55
+ ]);
56
+
57
+ const expectedLine = checksums
58
+ .toString("utf8")
59
+ .split("\n")
60
+ .find((line) => line.trim().endsWith(archiveName));
61
+ if (!expectedLine) {
62
+ fail(`${archiveName} not listed in checksums.txt for ${tag}`);
63
+ }
64
+ const expectedSum = expectedLine.trim().split(/\s+/)[0];
65
+ const actualSum = crypto.createHash("sha256").update(archive).digest("hex");
66
+ if (actualSum !== expectedSum) {
67
+ fail(
68
+ `checksum mismatch for ${archiveName}: expected ${expectedSum}, got ${actualSum}. ` +
69
+ `Download may be corrupted or tampered with -- not installing.`,
70
+ );
71
+ }
72
+
73
+ const destDir = path.join(__dirname, "..", ".bin");
74
+ fs.rmSync(destDir, { recursive: true, force: true });
75
+ fs.mkdirSync(destDir, { recursive: true });
76
+
77
+ const archivePath = path.join(os.tmpdir(), `${archiveName}-${process.pid}`);
78
+ fs.writeFileSync(archivePath, archive);
79
+ try {
80
+ // tar -xf handles both tar.gz and zip: GNU tar and macOS's bsdtar cover
81
+ // the tar.gz case (linux/darwin), and Windows has shipped bsdtar as
82
+ // tar.exe since Windows 10 1803 -- it extracts zip too. No npm
83
+ // dependency needed for extraction on any of the three platforms.
84
+ const result = spawnSync("tar", ["-xf", archivePath, "-C", destDir], {
85
+ stdio: "inherit",
86
+ });
87
+ if (result.status !== 0) {
88
+ fail(`extracting ${archiveName} failed (tar exited ${result.status})`);
89
+ }
90
+ } finally {
91
+ fs.rmSync(archivePath, { force: true });
92
+ }
93
+
94
+ const binName = platform === "windows" ? "lynxor.exe" : "lynxor";
95
+ const binPath = path.join(destDir, binName);
96
+ if (!fs.existsSync(binPath)) {
97
+ fail(`${binName} not found in ${archiveName} after extraction`);
98
+ }
99
+ if (platform !== "windows") {
100
+ fs.chmodSync(binPath, 0o755);
101
+ }
102
+
103
+ const check = spawnSync(binPath, ["--version"], { encoding: "utf8" });
104
+ if (check.status !== 0) {
105
+ fail(`downloaded binary failed to run: ${check.stderr || check.error}`);
106
+ }
107
+ console.log(`lynxor: installed ${check.stdout.trim()}`);
108
+ }
109
+
110
+ main().catch((err) => fail(err.stack || String(err)));