vigilnz 0.0.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 ADDED
@@ -0,0 +1,278 @@
1
+ # vigilnz
2
+
3
+ **Vigilnz is a security scanner for your code and container images.** It runs
4
+ SAST, secret, SCA (dependency), IaC and SBOM scans against your local
5
+ working directory, prints a readable report, and can sync findings to your
6
+ Vigilnz dashboard.
7
+
8
+ This npm package is a **thin installer/wrapper** around the native Vigilnz
9
+ CLI binary — it downloads the right prebuilt binary for your platform,
10
+ verifies it against a checksum pinned inside this package, and runs it with
11
+ your arguments passed through unchanged.
12
+
13
+ - Scans run **entirely against your local filesystem**. Nothing is cloned or
14
+ downloaded, and no Git remote is required.
15
+ - **No source code is ever uploaded** — only findings metadata, and only
16
+ when you're signed in. Use `--no-upload` to keep a run fully local.
17
+
18
+ ## Install
19
+
20
+ **As a command-line tool (recommended):**
21
+
22
+ ```bash
23
+ npm install -g vigilnz
24
+ vigilnz scan .
25
+ ```
26
+
27
+ **Without installing:**
28
+
29
+ ```bash
30
+ npx vigilnz scan .
31
+ ```
32
+
33
+ ## Quick start
34
+
35
+ ```bash
36
+ vigilnz login # sign in via your browser
37
+ vigilnz scan # scan the current directory
38
+ ```
39
+
40
+ ## Commands
41
+
42
+ | Command | What it does |
43
+ |---|---|
44
+ | `vigilnz scan [path]` | Scan a local directory (SAST, secret, SCA, IaC, SBOM) |
45
+ | `vigilnz scan-image <image>` | Scan a local Docker image for vulnerabilities |
46
+ | `vigilnz login` | Authenticate the CLI with your Vigilnz account |
47
+ | `vigilnz logout` | Remove stored credentials |
48
+ | `vigilnz whoami` | Show which account you're signed in as |
49
+ | `vigilnz config` | Show sign-in status and local configuration |
50
+ | `vigilnz version` | Print the CLI version |
51
+
52
+ Run `vigilnz <command> --help` for the full, authoritative flag list.
53
+
54
+ ## `vigilnz scan` — scan a directory
55
+
56
+ The path is optional; with no path the current directory is scanned.
57
+
58
+ ```bash
59
+ vigilnz scan # scan the current directory
60
+ vigilnz scan . # same, explicit
61
+ vigilnz scan ./src/lib # scan a specific directory
62
+ vigilnz scan /abs/path # relative or absolute
63
+ ```
64
+
65
+ ### What it scans
66
+
67
+ | Type | What it finds |
68
+ |---|---|
69
+ | `sast` | Insecure code patterns |
70
+ | `secret` | Hardcoded secrets and keys (values are masked, never uploaded) |
71
+ | `sca` | Known vulnerabilities in your dependencies |
72
+ | `iac` | Infrastructure-as-code misconfigurations (including Dockerfiles) |
73
+ | `sbom` | Software bill of materials for your dependencies |
74
+
75
+ All five run by default. SCA and SBOM work by collecting only your
76
+ dependency manifest and lock files (`package.json`, `go.mod`, `pom.xml`,
77
+ `requirements.txt`, `Cargo.toml`, …) — never source code. Because they
78
+ require that upload, both are skipped under `--no-upload`; use
79
+ `--local-audit` for a fully offline dependency check.
80
+
81
+ ### Options
82
+
83
+ | Flag | Description |
84
+ |---|---|
85
+ | `--scan-types <list>` | Which scans to run (default `sast,secret,sca,iac,sbom`) |
86
+ | `--fail-on <severity>` | Exit `1` if any finding is at or above `critical`\|`high`\|`medium`\|`low` |
87
+ | `--json` | Print the report as a single JSON document |
88
+ | `--no-upload` | Never send results anywhere; keep the scan entirely local |
89
+ | `--remote` | Require the upload to succeed — fail the scan if it can't be sent (CI) |
90
+ | `--include <paths>` | Comma-separated repo-relative paths to limit the scan to |
91
+ | `--no-gitignore` | Don't skip files matched by `.gitignore` |
92
+ | `--local-audit` | Also run the ecosystem's own audit tools alongside Vigilnz SCA |
93
+ | `--max-concurrency <n>` | Max parallel file-scanning workers (`0` = auto) |
94
+
95
+ `--local-audit` additionally runs `npm audit`, `pip-audit`, `govulncheck` or
96
+ `cargo-audit` where available. Its results are reported and counted
97
+ separately from Vigilnz SCA, so the two are never confused. Java/Maven/Gradle
98
+ projects have no comparable local tool and are skipped with a notice — scan
99
+ them with the default flow instead.
100
+
101
+ ### Examples
102
+
103
+ ```bash
104
+ vigilnz scan ./src --scan-types sast,secret # only these two scans
105
+ vigilnz scan --json > report.json # machine-readable output
106
+ vigilnz scan ./backend --fail-on high # fail on high+ findings
107
+ vigilnz scan . --local-audit # add local dependency audit
108
+ vigilnz scan . --no-upload # never leaves this machine
109
+ vigilnz scan . --remote --fail-on high # strict CI mode
110
+ ```
111
+
112
+ ## `vigilnz scan-image` — scan a Docker image
113
+
114
+ Scans an image **already present in your local Docker daemon**. It never
115
+ pulls or downloads an image.
116
+
117
+ ```bash
118
+ vigilnz scan-image nginx:latest
119
+ vigilnz scan-image myapp:v1 --json
120
+ ```
121
+
122
+ Reports four separate categories: vulnerabilities, misconfigurations,
123
+ secrets, and binary-hash vulnerabilities. Misconfiguration checks cover both
124
+ image config/history (root user, exposed ports, missing healthcheck, …) and
125
+ the image filesystem (SSH keys, credential files, `.env` files, world-writable
126
+ paths, SUID/SGID binaries, EOL base image, …).
127
+
128
+ Only small metadata leaves your machine — the detected package inventory,
129
+ binary hashes, the image's own OCI config/history, per-layer file path lists,
130
+ and already-masked findings. Never the image, its layers, or raw secret
131
+ values.
132
+
133
+ Dockerfile analysis is **not** part of this command — run
134
+ `vigilnz scan <path>` against a repo containing a Dockerfile for that.
135
+
136
+ | Flag | Description |
137
+ |---|---|
138
+ | `--json` | Print the report as a single JSON document |
139
+
140
+ ## Authentication
141
+
142
+ A Vigilnz account is required to scan.
143
+
144
+ ```bash
145
+ vigilnz login # browser sign-in
146
+ vigilnz login --no-browser # print the URL instead of opening a browser
147
+ vigilnz login --force # switch accounts without logging out first
148
+ ```
149
+
150
+ The authorization URL is always printed first, so sign-in still works over
151
+ SSH, in a container, or on a headless host — copy it to a browser anywhere.
152
+
153
+ **For non-interactive environments**, supply an API key instead and no browser
154
+ is involved:
155
+
156
+ ```bash
157
+ vigilnz login --api-key <key>
158
+ VIGILNZ_API_KEY=<key> vigilnz login
159
+ ```
160
+
161
+ Create the key from your Vigilnz dashboard, and keep it in a secret store —
162
+ never in your repository. The resulting token is written to your user config
163
+ directory with owner-only permissions; the raw API key itself is never
164
+ written to disk.
165
+
166
+ `--api-key` is read by `vigilnz login`, which exchanges it for that stored
167
+ token — so sign in first, then scan:
168
+
169
+ ```bash
170
+ vigilnz login --api-key <key>
171
+ vigilnz scan . --fail-on high
172
+ ```
173
+
174
+ ```bash
175
+ vigilnz whoami # which account am I signed in as?
176
+ vigilnz config # sign-in status + config file path
177
+ vigilnz logout # remove stored credentials
178
+ ```
179
+
180
+ ## Exit codes
181
+
182
+ Stable across releases, so CI can rely on them:
183
+
184
+ | Code | Meaning |
185
+ |---|---|
186
+ | `0` | Scan completed, no policy violation |
187
+ | `1` | Findings violate the configured policy (`--fail-on`) |
188
+ | `2` | Scanner or runtime error |
189
+ | `3` | Authentication or configuration error |
190
+
191
+ This wrapper mirrors the binary's exit code and signal exactly, so these
192
+ work identically through `npx`.
193
+
194
+ ## Global options
195
+
196
+ | Flag | Description |
197
+ |---|---|
198
+ | `--api-key <key>` | API key to sign in with (see [Authentication](#authentication)) |
199
+ | `--config <path>` | Path to the config file (default: your OS user config dir) |
200
+ | `-v`, `--verbose` | Show redacted diagnostic detail on errors (never secrets) |
201
+
202
+ ## This package exposes no importable module
203
+
204
+ `vigilnz` is a **CLI-only** package. It has no `main` and no `exports` entry
205
+ point, so there is nothing to `require()` or `import`:
206
+
207
+ ```js
208
+ const vigilnz = require('vigilnz'); // ✗ throws MODULE_NOT_FOUND
209
+ import 'vigilnz'; // ✗ throws ERR_MODULE_NOT_FOUND
210
+ ```
211
+
212
+ Run it as a command instead (`npx vigilnz ...` or a global install). There is
213
+ deliberately no JavaScript API: the CLI is a Go binary
214
+ with its own authentication and configuration, and every distribution channel
215
+ wraps that one binary rather than reimplementing it. To trigger a scan from
216
+ Node, spawn the CLI as a child process and read its exit code — exit codes and
217
+ stdio pass through this wrapper unchanged.
218
+
219
+ ## Supported platforms
220
+
221
+ | OS | Architecture |
222
+ |---|---|
223
+ | Windows | x64 (arm64 runs the x64 build via Windows' built-in emulation — no native windows-arm64 binary is published) |
224
+ | Linux | x64, arm64 |
225
+ | macOS | x64, arm64 |
226
+
227
+ This package deliberately does **not** declare npm's `os`/`cpu` fields. Those
228
+ are independent lists and cannot express "every combination except
229
+ windows+arm64", so declaring them would hard-fail installs on platforms that
230
+ actually work. Platform support is enforced at runtime instead, with an
231
+ actionable error.
232
+
233
+ ## If the install-time download fails
234
+
235
+ The `postinstall` hook is **fail-soft**: if it cannot reach the download host
236
+ it prints a warning and lets your install succeed. Nothing is broken — the
237
+ binary is downloaded automatically the first time you run `vigilnz`. This
238
+ also covers installs that skip lifecycle scripts entirely
239
+ (`npm install --ignore-scripts`).
240
+
241
+ That means a failed download can never break your `npm install` because of a
242
+ proxy, an offline machine, or a CDN blip.
243
+
244
+ ## Environment variables
245
+
246
+ **CLI**
247
+
248
+ - `VIGILNZ_API_KEY` — API key for `vigilnz login`, for non-interactive use. Equivalent to `--api-key`.
249
+
250
+ **This npm wrapper**
251
+
252
+ - `VIGILNZ_CLI_CDN_URL` — override the download host (default `https://releases.vigilnz.com/releases`), for private mirrors or air-gapped environments.
253
+ - `VIGILNZ_CLI_SKIP_DOWNLOAD=1` — skip the postinstall download entirely; you're responsible for placing the binary at `bin/vigilnz-bin/vigilnz(.exe)` yourself.
254
+
255
+ ## How downloads are verified
256
+
257
+ The expected SHA256 digests live in `checksums/checksums.json`, **inside this
258
+ published package**, pinned when the version is released.
259
+
260
+ They are deliberately not fetched from the release host at install time:
261
+ verifying a download against a checksum from the same host only proves that
262
+ host agrees with itself. Pinning the digests here puts them behind npm
263
+ registry integrity instead.
264
+
265
+ Verification **fails closed** — if the pinned digest is missing, malformed, or
266
+ stamped for a different version, the download is refused rather than falling
267
+ back to trusting the host.
268
+
269
+ Every distribution channel (direct binary, this npm package, Homebrew,
270
+ Docker) runs the exact same CLI binary — only the reported distribution
271
+ differs, so the Vigilnz Platform can show which channel a scan came from
272
+ (e.g. "Vigilnz CLI · npm").
273
+
274
+ ## License
275
+
276
+ The license for this wrapper is not finalized yet. The Vigilnz CLI binary it
277
+ downloads, and the Vigilnz platform it connects to, are governed by the
278
+ Vigilnz terms of service.
package/bin/vigilnz.js ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ // File: vigilnz.js
3
+ // Purpose: npm distribution wrapper entry point. Locates the native
4
+ // Vigilnz CLI binary (downloaded by install.js) and execs it with
5
+ // every argument passed through unmodified — this file contains
6
+ // NO scan logic of its own. Every distribution channel (binary,
7
+ // npm, Homebrew, Docker, ...) wraps one real CLI implementation.
8
+ //
9
+ // Self-heals a missing binary (e.g. `npx vigilnz` on an npm
10
+ // version that skips lifecycle scripts, or an install that used
11
+ // --ignore-scripts) by running the same download step
12
+ // install.js's postinstall hook already ran, once, on demand.
13
+ // Author: Vigilnz
14
+ // Date: 2026-08-23
15
+ // Modified: 2026-08-27 — verify the self-healed download against the digest
16
+ // pinned in this tarball (lib/checksums.js), the same source
17
+ // install.js's postinstall path uses. This supersedes an earlier
18
+ // same-day fix that passed fallbackNames through to a CDN-fetched
19
+ // SHA256SUMS: that file is no longer fetched at runtime at all.
20
+ // Because postinstall is now fail-soft, this path is the real
21
+ // install for anyone behind a proxy at install time — it must
22
+ // stay working.
23
+
24
+ 'use strict';
25
+
26
+ const fs = require('fs');
27
+ const { spawnSync } = require('child_process');
28
+ const { resolvePlatform, artifactUrl } = require('../lib/platform');
29
+ const { binaryDestPath } = require('../lib/binaryPath');
30
+ const { downloadVerified } = require('../lib/download');
31
+ const { pinnedChecksumFor } = require('../lib/checksums');
32
+
33
+ async function ensureBinary() {
34
+ const { platformDir, binName, remoteFileName } = resolvePlatform();
35
+ const destPath = binaryDestPath(binName);
36
+ if (fs.existsSync(destPath)) {
37
+ return destPath;
38
+ }
39
+
40
+ const pkg = require('../package.json');
41
+ process.stderr.write('[vigilnz] Binary not found — downloading now (this only happens once)...\n');
42
+ await downloadVerified({
43
+ binaryUrl: artifactUrl(pkg.version, platformDir, remoteFileName),
44
+ expectedSha256: pinnedChecksumFor(remoteFileName, pkg.version),
45
+ artifactName: remoteFileName,
46
+ destPath,
47
+ });
48
+ return destPath;
49
+ }
50
+
51
+ async function main() {
52
+ let binaryPath;
53
+ try {
54
+ binaryPath = await ensureBinary();
55
+ } catch (err) {
56
+ process.stderr.write(`[vigilnz] Unable to locate or download the Vigilnz CLI binary: ${err.message}\n`);
57
+ process.exitCode = 1;
58
+ return;
59
+ }
60
+
61
+ // Pass every argument through untouched, inherit stdio so interactive
62
+ // prompts (vigilnz login) and colored output work exactly as running
63
+ // the binary directly would. VIGILNZ_CLI_DISTRIBUTION overrides the
64
+ // binary's compiled-in default so scan uploads report this channel
65
+ // correctly — npm never builds its own binary, it downloads the same
66
+ // prebuilt artifact every channel uses, so the compile-time value alone
67
+ // would misreport here. A caller that already set the var (e.g. for
68
+ // testing) is left untouched.
69
+ const result = spawnSync(binaryPath, process.argv.slice(2), {
70
+ stdio: 'inherit',
71
+ env: { ...process.env, VIGILNZ_CLI_DISTRIBUTION: process.env.VIGILNZ_CLI_DISTRIBUTION || 'npm' },
72
+ });
73
+
74
+ if (result.error) {
75
+ process.stderr.write(`[vigilnz] Failed to run the Vigilnz CLI binary: ${result.error.message}\n`);
76
+ process.exitCode = 1;
77
+ return;
78
+ }
79
+ // Mirror the child's exit code/signal exactly, so CI pipelines relying on
80
+ // `vigilnz scan --fail-on ...`'s exit code see the same result they would
81
+ // running the binary directly.
82
+ if (result.signal) {
83
+ process.kill(process.pid, result.signal);
84
+ return;
85
+ }
86
+ process.exitCode = result.status ?? 1;
87
+ }
88
+
89
+ main();
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": "0.0.1",
3
+ "artifacts": {
4
+ "vigilnz-windows-amd64.exe": "a6c4a4d1e4a0a70249241c3466b7c6931f4ef1bb34fedb2cf23c50017f89dc32",
5
+ "vigilnz-linux-amd64": "572bc1ad4b9ac20e1fbe32168174e36304488be990f0d9e6b8260e73e3ed4392",
6
+ "vigilnz-linux-arm64": "87bc197fa1e4734e3e41e7ac06b52cc2790c734568d02a17490df7ad419548e3",
7
+ "vigilnz-darwin-amd64": "42f0da8eb6a0333e9b09b906a5c593206f3f04de2dbdb979075174e582fdff53",
8
+ "vigilnz-darwin-arm64": "5bcef2b111d12d337852aeca4ad7fd13dec7b58c2668b6a2fd295130bc26bd1d"
9
+ }
10
+ }
package/install.js ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ // File: install.js
3
+ // Purpose: npm postinstall hook — downloads the correct prebuilt Vigilnz
4
+ // CLI binary for the current OS/architecture from the public
5
+ // release CDN, verifies it against the SHA256 digest pinned in
6
+ // this tarball (checksums/checksums.json), and stores it inside
7
+ // this package (bin/vigilnz-bin/). This package never builds or
8
+ // reimplements the CLI itself — every distribution channel wraps
9
+ // one real implementation.
10
+ //
11
+ // FAIL-SOFT BY DESIGN: this hook never exits non-zero. npm treats
12
+ // a failing postinstall as a failed install, which would break the
13
+ // whole dependency install of any project that merely lists
14
+ // vigilnz as a devDependency — and npm does NOT roll the package
15
+ // back, so it leaves a half-installed tree behind. A blocked
16
+ // proxy or an offline CI runner must not do that. Instead we warn
17
+ // here and let bin/vigilnz.js download on first actual use — the
18
+ // same fail-soft posture other native-binary npm wrappers take.
19
+ //
20
+ // VIGILNZ_CLI_SKIP_DOWNLOAD=1 skips this entirely — for offline/
21
+ // air-gapped installs where the binary is placed by some other
22
+ // means (e.g. a corporate mirror step) before first run.
23
+ // Author: Vigilnz
24
+ // Date: 2026-08-23
25
+ // Modified: 2026-08-27 — dropped an unused `path` import; verify against
26
+ // the pinned digest instead of a CDN-fetched SHA256SUMS; made
27
+ // the hook fail-soft (warn + exit 0) so it cannot break a
28
+ // consumer's `npm install`.
29
+
30
+ 'use strict';
31
+
32
+ const fs = require('fs');
33
+ const { resolvePlatform, artifactUrl } = require('./lib/platform');
34
+ const { downloadVerified } = require('./lib/download');
35
+ const { binaryDestPath } = require('./lib/binaryPath');
36
+ const { pinnedChecksumFor } = require('./lib/checksums');
37
+
38
+ /**
39
+ * Explain a non-fatal failure without dumping a stack trace into a
40
+ * consumer's install log, and point at every real escape hatch.
41
+ *
42
+ * @param {Error} err
43
+ */
44
+ function warnNonFatal(err) {
45
+ process.stderr.write(
46
+ `\n[vigilnz] WARNING: could not install the Vigilnz CLI binary now:\n` +
47
+ ` ${err.message}\n` +
48
+ '\n[vigilnz] This is not fatal — your install continues, and the binary will be\n' +
49
+ '[vigilnz] downloaded automatically the first time you run `vigilnz`.\n' +
50
+ '[vigilnz] If that machine also cannot reach the download host, you can:\n' +
51
+ '[vigilnz] - Set VIGILNZ_CLI_CDN_URL to a private mirror\n' +
52
+ '[vigilnz] - Set VIGILNZ_CLI_SKIP_DOWNLOAD=1 and place the binary yourself\n\n'
53
+ );
54
+ }
55
+
56
+ async function main() {
57
+ if (process.env.VIGILNZ_CLI_SKIP_DOWNLOAD === '1') {
58
+ console.log('[vigilnz] VIGILNZ_CLI_SKIP_DOWNLOAD=1 set — skipping binary download.');
59
+ return;
60
+ }
61
+
62
+ const pkg = require('./package.json');
63
+ const version = pkg.version;
64
+ const { platformDir, binName, remoteFileName } = resolvePlatform();
65
+ const destPath = binaryDestPath(binName);
66
+
67
+ if (fs.existsSync(destPath)) {
68
+ console.log(`[vigilnz] Binary already present at ${destPath} — skipping download.`);
69
+ return;
70
+ }
71
+
72
+ console.log(`[vigilnz] Downloading Vigilnz CLI v${version} for ${platformDir}...`);
73
+ await downloadVerified({
74
+ binaryUrl: artifactUrl(version, platformDir, remoteFileName),
75
+ expectedSha256: pinnedChecksumFor(remoteFileName, version),
76
+ artifactName: remoteFileName,
77
+ destPath,
78
+ });
79
+ console.log(`[vigilnz] Installed to ${destPath}`);
80
+ }
81
+
82
+ main().catch((err) => {
83
+ warnNonFatal(err);
84
+ // Deliberately exit 0 — see the FAIL-SOFT note in the file header.
85
+ process.exitCode = 0;
86
+ });
@@ -0,0 +1,19 @@
1
+ // File: binaryPath.js
2
+ // Purpose: Single source of truth for where the downloaded Vigilnz CLI
3
+ // binary lives on disk, shared by install.js and bin/vigilnz.js.
4
+ // Author: Vigilnz
5
+ // Date: 2026-08-23
6
+
7
+ 'use strict';
8
+
9
+ const path = require('path');
10
+
11
+ /**
12
+ * @param {string} binName - "vigilnz" or "vigilnz.exe" (see lib/platform.js)
13
+ * @returns {string} absolute path inside this package where the binary is stored
14
+ */
15
+ function binaryDestPath(binName) {
16
+ return path.join(__dirname, '..', 'bin', 'vigilnz-bin', binName);
17
+ }
18
+
19
+ module.exports = { binaryDestPath };
@@ -0,0 +1,129 @@
1
+ // File: checksums.js
2
+ // Purpose: Reads the SHA256 digests that are PINNED INSIDE this npm
3
+ // tarball (checksums/checksums.json), rather than fetching a
4
+ // SHA256SUMS file from the same CDN the binary itself comes from.
5
+ //
6
+ // Why this matters: verifying a download against a checksum
7
+ // fetched from that same host only proves "the host agrees with
8
+ // itself" — whoever can tamper with one can tamper with both.
9
+ // Pinning the digests in the published package puts them behind
10
+ // npm registry integrity instead, so the trust anchor is the
11
+ // registry rather than the release host. The Homebrew channel
12
+ // does the equivalent by baking digests into its formula.
13
+ //
14
+ // The digest file is generated at publish time and is deliberately
15
+ // NOT committed, so a stale digest set can never be published by
16
+ // accident; the release pipeline verifies it before publishing.
17
+ // Author: Vigilnz
18
+ // Date: 2026-08-27
19
+
20
+ 'use strict';
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+
25
+ /** Location of the generated, tarball-pinned digest set. */
26
+ const CHECKSUMS_PATH = path.join(__dirname, '..', 'checksums', 'checksums.json');
27
+
28
+ /**
29
+ * Every platform the release pipeline actually publishes. windows-arm64 is
30
+ * absent on purpose (see lib/platform.js) — keep this in sync with what
31
+ * the pipeline builds.
32
+ */
33
+ const PUBLISHED_PLATFORMS = [
34
+ 'windows-amd64',
35
+ 'linux-amd64',
36
+ 'linux-arm64',
37
+ 'darwin-amd64',
38
+ 'darwin-arm64',
39
+ ];
40
+
41
+ const SHA256_HEX_LENGTH = 64;
42
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/;
43
+
44
+ /**
45
+ * Load and structurally validate the pinned digest set.
46
+ *
47
+ * @param {string} [filePath] - override, for tests only. Production callers
48
+ * must use the default: tests that wrote to the real path used to
49
+ * leave a bogus digest set behind, which `npm pack` then shipped.
50
+ * @returns {{ version: string, artifacts: Record<string, string> }}
51
+ * @throws {Error} if the file is missing, unparseable, or malformed
52
+ */
53
+ function loadPinnedChecksums(filePath = CHECKSUMS_PATH) {
54
+ let raw;
55
+ try {
56
+ raw = fs.readFileSync(filePath, 'utf8');
57
+ } catch (err) {
58
+ throw new Error(
59
+ `No pinned checksum file at ${filePath} (${err.code}). This package was ` +
60
+ 'published without its checksums being stamped, or you are running from ' +
61
+ 'a source checkout where they have not been generated yet.'
62
+ );
63
+ }
64
+
65
+ let parsed;
66
+ try {
67
+ parsed = JSON.parse(raw);
68
+ } catch (err) {
69
+ throw new Error(`Pinned checksum file ${filePath} is not valid JSON: ${err.message}`);
70
+ }
71
+
72
+ if (!parsed || typeof parsed.version !== 'string' || !parsed.artifacts) {
73
+ throw new Error(
74
+ `Pinned checksum file ${filePath} is malformed: expected {version, artifacts}.`
75
+ );
76
+ }
77
+ return parsed;
78
+ }
79
+
80
+ /**
81
+ * Look up the pinned digest for one release artifact, failing closed on any
82
+ * problem — a missing entry, a malformed digest, or a digest set stamped for
83
+ * a different CLI version. There is deliberately NO fallback to downloading
84
+ * SHA256SUMS from the CDN: that fallback would silently restore exactly the
85
+ * weaker trust model this module exists to remove.
86
+ *
87
+ * @param {string} artifactName - remote artifact name, e.g. "vigilnz-linux-amd64"
88
+ * @param {string} expectedVersion - the version being installed (package.json's own)
89
+ * @param {string} [filePath] - override, for tests only (see loadPinnedChecksums)
90
+ * @returns {string} lowercase 64-char hex digest
91
+ * @throws {Error} if no trustworthy digest is available
92
+ */
93
+ function pinnedChecksumFor(artifactName, expectedVersion, filePath = CHECKSUMS_PATH) {
94
+ const { version, artifacts } = loadPinnedChecksums(filePath);
95
+
96
+ // Guards against stamping drift: a checksums.json left over from an
97
+ // earlier version would otherwise verify this version's binary against
98
+ // the wrong digest and fail with a confusing "corrupted download".
99
+ if (version !== expectedVersion) {
100
+ throw new Error(
101
+ `Pinned checksums are for CLI v${version} but this package is v${expectedVersion}. ` +
102
+ 'This package was built with a mismatched checksum set — please report it.'
103
+ );
104
+ }
105
+
106
+ const digest = artifacts[artifactName];
107
+ if (typeof digest !== 'string') {
108
+ throw new Error(
109
+ `Pinned checksums have no entry for ${artifactName} ` +
110
+ `(have: ${Object.keys(artifacts).sort().join(', ') || 'none'}).`
111
+ );
112
+ }
113
+
114
+ const normalized = digest.trim().toLowerCase();
115
+ if (!SHA256_PATTERN.test(normalized)) {
116
+ throw new Error(
117
+ `Pinned digest for ${artifactName} is not a ${SHA256_HEX_LENGTH}-char hex sha256: "${digest}".`
118
+ );
119
+ }
120
+ return normalized;
121
+ }
122
+
123
+ module.exports = {
124
+ loadPinnedChecksums,
125
+ pinnedChecksumFor,
126
+ CHECKSUMS_PATH,
127
+ PUBLISHED_PLATFORMS,
128
+ SHA256_PATTERN,
129
+ };
@@ -0,0 +1,141 @@
1
+ // File: download.js
2
+ // Purpose: Download + SHA256-verify one Vigilnz CLI release artifact.
3
+ // Shared by install.js (postinstall) and bin/vigilnz.js's
4
+ // lazy-self-heal path (npx / --ignore-scripts installs that
5
+ // skipped postinstall). Uses only Node built-ins — no runtime
6
+ // dependency footprint for a package whose whole job is being a
7
+ // thin wrapper.
8
+ // Author: Vigilnz
9
+ // Date: 2026-08-23
10
+ // Modified: 2026-08-27 — downloadVerified now takes the expected digest
11
+ // from the caller (lib/checksums.js reads it from the pinned,
12
+ // in-tarball checksums.json) instead of fetching SHA256SUMS from
13
+ // the same host as the binary, which only proved that host agreed
14
+ // with itself. findChecksum stays exported: the release pipeline's
15
+ // stamping step still parses the sha256sum-format SHA256SUMS at
16
+ // publish time to build that pinned set.
17
+
18
+ 'use strict';
19
+
20
+ const fs = require('fs');
21
+ const http = require('http');
22
+ const https = require('https');
23
+ const crypto = require('crypto');
24
+ const path = require('path');
25
+
26
+ const MAX_REDIRECTS = 5;
27
+
28
+ /**
29
+ * GET a URL to a Buffer, following redirects (S3/CloudFront commonly
30
+ * 301/302 on a stale cache miss). Rejects on any non-2xx final status.
31
+ * Picks http vs https by URL scheme — the default public CDN is always
32
+ * https, but a private mirror (VIGILNZ_CLI_CDN_URL, or a test server)
33
+ * may reasonably be plain http on an internal network.
34
+ *
35
+ * @param {string} url
36
+ * @param {number} [redirectsLeft]
37
+ * @returns {Promise<Buffer>}
38
+ */
39
+ function fetchBuffer(url, redirectsLeft = MAX_REDIRECTS) {
40
+ const transport = url.startsWith('http://') ? http : https;
41
+ return new Promise((resolve, reject) => {
42
+ const req = transport.get(url, { headers: { 'User-Agent': 'vigilnz-npm-cli' } }, (res) => {
43
+ const { statusCode, headers } = res;
44
+ if (statusCode >= 300 && statusCode < 400 && headers.location && redirectsLeft > 0) {
45
+ res.resume();
46
+ fetchBuffer(headers.location, redirectsLeft - 1).then(resolve, reject);
47
+ return;
48
+ }
49
+ if (statusCode !== 200) {
50
+ res.resume();
51
+ reject(new Error(`GET ${url} returned HTTP ${statusCode}`));
52
+ return;
53
+ }
54
+ const chunks = [];
55
+ res.on('data', (chunk) => chunks.push(chunk));
56
+ res.on('end', () => resolve(Buffer.concat(chunks)));
57
+ res.on('error', reject);
58
+ });
59
+ req.on('error', reject);
60
+ req.setTimeout(60_000, () => req.destroy(new Error(`GET ${url} timed out after 60s`)));
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Parse a `sha256sum`-format SHA256SUMS file (`<hex> <filename>` per line,
66
+ * matching the release pipeline's checksum generation) and return the hex
67
+ * digest for one file name.
68
+ *
69
+ * @param {string} sha256sumsText
70
+ * @param {string} fileName
71
+ * @returns {string | null}
72
+ */
73
+ function findChecksum(sha256sumsText, fileName, fallbackNames = []) {
74
+ const entries = [];
75
+
76
+ for (const line of sha256sumsText.split('\n')) {
77
+ const trimmed = line.trim();
78
+ if (!trimmed) continue;
79
+ const match = trimmed.match(/^([a-f0-9]{64})\s+\*?(.+)$/i);
80
+ if (match) {
81
+ entries.push({
82
+ digest: match[1].toLowerCase(),
83
+ name: path.basename(match[2].trim()),
84
+ });
85
+ }
86
+ }
87
+
88
+ for (const wanted of [fileName, ...fallbackNames]) {
89
+ const hit = entries.find((entry) => entry.name === wanted);
90
+ if (hit) return hit.digest;
91
+ }
92
+
93
+ // Deliberately NO "if there's only one entry, use it" fallback: a lone
94
+ // entry naming a different artifact would then be verified against,
95
+ // which is exactly the case test/download.test.js guards. Matching is by
96
+ // name only — an unmatched name returns null and the caller fails.
97
+ return null;
98
+ }
99
+
100
+ /**
101
+ * Download one binary artifact, verify it against a digest the CALLER
102
+ * supplies (pinned in this tarball — see lib/checksums.js), and write it to
103
+ * destPath (0755 on non-Windows). Throws with a clear message on any
104
+ * network, HTTP, or checksum-mismatch failure — never writes a partial or
105
+ * unverified file to destPath.
106
+ *
107
+ * @param {{ binaryUrl: string, expectedSha256: string, artifactName: string, destPath: string }} opts
108
+ */
109
+ async function downloadVerified({ binaryUrl, expectedSha256, artifactName, destPath }) {
110
+ const expected = String(expectedSha256 || '')
111
+ .trim()
112
+ .toLowerCase();
113
+ if (!/^[a-f0-9]{64}$/.test(expected)) {
114
+ // Fail before spending bandwidth: an absent/garbage expected digest
115
+ // must never degrade into "download it and hope".
116
+ throw new Error(
117
+ `Refusing to download ${artifactName} without a valid pinned sha256 digest ` +
118
+ `(got "${expectedSha256}").`
119
+ );
120
+ }
121
+
122
+ const binary = await fetchBuffer(binaryUrl);
123
+
124
+ const actual = crypto.createHash('sha256').update(binary).digest('hex');
125
+ if (actual !== expected) {
126
+ throw new Error(
127
+ `Checksum mismatch for ${artifactName}: expected ${expected}, got ${actual}. ` +
128
+ 'The download may be corrupted or tampered with — not installing it.'
129
+ );
130
+ }
131
+
132
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
133
+ const tmpPath = `${destPath}.download-${process.pid}`;
134
+ fs.writeFileSync(tmpPath, binary, { mode: 0o755 });
135
+ fs.renameSync(tmpPath, destPath); // atomic on the same filesystem — no partially-written binary left behind
136
+ if (process.platform !== 'win32') {
137
+ fs.chmodSync(destPath, 0o755);
138
+ }
139
+ }
140
+
141
+ module.exports = { downloadVerified, fetchBuffer, findChecksum };
@@ -0,0 +1,97 @@
1
+ // File: platform.js
2
+ // Purpose: OS/CPU-architecture detection and download-URL construction for
3
+ // the Vigilnz CLI's npm distribution wrapper. This is the ONLY
4
+ // place that knows the published release-artifact layout — a
5
+ // version bump or a new platform target only needs a change here,
6
+ // never in install.js/bin/vigilnz.js.
7
+ //
8
+ // This package never reimplements any scan logic — it only
9
+ // locates and executes the single native Vigilnz CLI binary.
10
+ // Author: Vigilnz
11
+ // Date: 2026-08-23
12
+
13
+ 'use strict';
14
+
15
+ /**
16
+ * Public host serving CLI release artifacts — deliberately SEPARATE from
17
+ * the API the CLI talks to at runtime. Never point this at an application
18
+ * backend.
19
+ */
20
+ const DEFAULT_CDN_BASE_URL = 'https://releases.vigilnz.com/releases';
21
+
22
+ /** Override for private mirrors / air-gapped installs / CI testing. */
23
+ function cdnBaseUrl() {
24
+ return (process.env.VIGILNZ_CLI_CDN_URL || DEFAULT_CDN_BASE_URL).replace(/\/+$/, '');
25
+ }
26
+
27
+ /**
28
+ * Maps Node's process.platform to the release's OS name and the local
29
+ * binary filename — must stay in sync with the platforms the release
30
+ * pipeline publishes.
31
+ */
32
+ const PLATFORM_MAP = {
33
+ win32: { os: 'windows', binName: 'vigilnz.exe' },
34
+ linux: { os: 'linux', binName: 'vigilnz' },
35
+ darwin: { os: 'darwin', binName: 'vigilnz' },
36
+ };
37
+
38
+ /** Maps Node's process.arch to the architecture names releases publish under. */
39
+ const ARCH_MAP = {
40
+ x64: 'amd64',
41
+ arm64: 'arm64',
42
+ };
43
+
44
+ /**
45
+ * Resolve the current process's platform/arch into this release's naming
46
+ * scheme, or throw a clear, actionable error for an unsupported combination
47
+ * — either one the release pipeline never produces (e.g. 32-bit Windows),
48
+ * or windows/arm64 specifically, which is deliberately excluded.
49
+ *
50
+ * @returns {{ platformDir: string, binName: string, remoteFileName: string }}
51
+ */
52
+ function resolvePlatform() {
53
+ const platformEntry = PLATFORM_MAP[process.platform];
54
+ const goArch = ARCH_MAP[process.arch];
55
+
56
+ if (!platformEntry || !goArch) {
57
+ throw new Error(
58
+ `Vigilnz CLI has no prebuilt binary for ${process.platform}/${process.arch}. ` +
59
+ 'Supported: Windows/Linux/macOS on x64 or arm64 (Windows/arm64 excepted — see below). ' +
60
+ 'Build the CLI from source for this platform, or set VIGILNZ_CLI_SKIP_DOWNLOAD=1 ' +
61
+ 'and place a binary yourself.'
62
+ );
63
+ }
64
+
65
+ if (platformEntry.os === 'windows' && goArch === 'arm64') {
66
+ throw new Error(
67
+ 'Vigilnz CLI has no prebuilt binary for windows/arm64. Windows 11 on ARM can ' +
68
+ 'run the windows-amd64 build via its built-in x64 emulation instead: set ' +
69
+ 'VIGILNZ_CLI_SKIP_DOWNLOAD=1 and place that binary yourself.'
70
+ );
71
+ }
72
+
73
+ const platformDir = `${platformEntry.os}-${goArch}`;
74
+ const ext = platformEntry.os === 'windows' ? '.exe' : '';
75
+ return {
76
+ platformDir,
77
+ binName: platformEntry.binName,
78
+ // The name the file actually has on releases.vigilnz.com — distinct
79
+ // from binName, which is the LOCAL name this package stores/execs the
80
+ // binary as once downloaded (see lib/binaryPath.js).
81
+ remoteFileName: `vigilnz-${platformDir}${ext}`,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Build the download URL for one release artifact.
87
+ *
88
+ * @param {string} version - CLI version (matches this package's own package.json version)
89
+ * @param {string} platformDir - e.g. "linux-amd64" (from resolvePlatform())
90
+ * @param {string} fileName - the remote binary name (resolvePlatform().remoteFileName) or "SHA256SUMS"
91
+ * @returns {string}
92
+ */
93
+ function artifactUrl(version, platformDir, fileName) {
94
+ return `${cdnBaseUrl()}/v${version}/${platformDir}/${fileName}`;
95
+ }
96
+
97
+ module.exports = { resolvePlatform, artifactUrl, cdnBaseUrl, DEFAULT_CDN_BASE_URL };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "vigilnz",
3
+ "version": "0.0.1",
4
+ "description": "Vigilnz security scanner CLI — thin installer/wrapper that downloads and runs the native Vigilnz CLI binary. No scan logic lives in this package.",
5
+ "bin": {
6
+ "vigilnz": "bin/vigilnz.js"
7
+ },
8
+ "files": [
9
+ "bin/vigilnz.js",
10
+ "lib/",
11
+ "checksums/",
12
+ "install.js",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "postinstall": "node install.js",
17
+ "stamp-checksums": "node scripts/stamp-checksums.js",
18
+ "prepack": "node scripts/stamp-checksums.js --check",
19
+ "prepublishOnly": "node scripts/stamp-checksums.js --check",
20
+ "test": "node --test test/*.test.js"
21
+ },
22
+ "engines": {
23
+ "node": ">=16"
24
+ },
25
+ "keywords": [
26
+ "vigilnz",
27
+ "security",
28
+ "sast",
29
+ "sca",
30
+ "secret-scanning",
31
+ "iac",
32
+ "sbom",
33
+ "container-scanning"
34
+ ],
35
+ "author": "Vigilnz",
36
+ "license": "UNKNOWN",
37
+ "homepage": "https://vigilnz.com"
38
+ }