foxguard 0.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.
Files changed (3) hide show
  1. package/README.md +59 -0
  2. package/bin/foxguard +169 -0
  3. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # foxguard
2
+
3
+ Fast local security guard for changed files, built-in rules, and Semgrep-compatible YAML. Written in Rust.
4
+
5
+ This is the npm wrapper for foxguard. It downloads the correct prebuilt binary for your platform from GitHub Releases.
6
+
7
+ foxguard scans JS/TS, Python, and Go with built-in security rules by default and can load a useful Semgrep-compatible YAML subset with `--rules`.
8
+ Built-ins now cover local code risks like SSRF client variants, file/path traversal sinks, session/cookie misconfig, transport misconfig, and framework-specific auth issues.
9
+ Current built-ins include Express/JWT/session lifecycle checks on JavaScript plus Flask/Django session, CSRF, Flask-WTF, host, redirect, and exemption hardening checks on Python.
10
+
11
+ Use `--rules` to add external rules on top of the built-ins. Use `--no-builtins --rules ...` for an external-rules-only compatibility run.
12
+
13
+ It also includes a dedicated `secrets` mode for common leaked credentials and private key material, with redacted output, binary-file skipping, and baseline-safe suppression data.
14
+ Secrets mode also supports path-scoped excludes and per-rule ignores for fixtures, generated files, or intentionally fake tokens.
15
+ foxguard can also auto-discover a repo config file such as `.foxguard.yml` for shared baselines, rule paths, and secrets defaults.
16
+ The Semgrep-compatible subset also supports regex clauses like `pattern-regex` and `pattern-not-regex`.
17
+ It also supports rule-level path filters like `paths.include` and `paths.exclude`.
18
+ It also supports `metavariable-regex` for filtering bound metavariables in structural rules.
19
+ It also supports `pattern-not-inside` for excluding safe wrapper contexts.
20
+
21
+ Local-first workflow:
22
+
23
+ ```sh
24
+ npx foxguard --changed .
25
+ npx foxguard secrets --changed .
26
+ npx foxguard baseline --output .foxguard/baseline.json
27
+ npx foxguard init
28
+ ```
29
+
30
+ `foxguard init` also writes a starter `.foxguard.yml` when the repo does not already have one.
31
+
32
+ ## Usage
33
+
34
+ ```sh
35
+ npx foxguard .
36
+ ```
37
+
38
+ Or install globally:
39
+
40
+ ```sh
41
+ npm install -g foxguard
42
+ foxguard .
43
+ ```
44
+
45
+ ## How it works
46
+
47
+ 1. If foxguard is installed via `cargo install foxguard`, the npm wrapper uses that binary directly.
48
+ 2. Otherwise, it downloads the prebuilt binary for your platform from GitHub Releases.
49
+ 3. The binary is cached in `node_modules/.cache/foxguard/` for subsequent runs.
50
+
51
+ ## Supported platforms
52
+
53
+ - macOS (x64, arm64)
54
+ - Linux (x64, arm64)
55
+ - Windows (x64)
56
+
57
+ ## Full documentation
58
+
59
+ See the [main repository](https://github.com/peaktwilight/foxguard) for full documentation, rules reference, and configuration options.
package/bin/foxguard ADDED
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execFileSync, execSync } = require("child_process");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const os = require("os");
7
+ const https = require("https");
8
+
9
+ const VERSION = require("../package.json").version;
10
+ const REPO = "peaktwilight/foxguard";
11
+
12
+ // Platform mapping: [node os, node arch] -> GitHub release asset suffix
13
+ const PLATFORM_MAP = {
14
+ "darwin-x64": "x86_64-apple-darwin",
15
+ "darwin-arm64": "aarch64-apple-darwin",
16
+ "linux-x64": "x86_64-unknown-linux-gnu",
17
+ "linux-arm64": "aarch64-unknown-linux-gnu",
18
+ "win32-x64": "x86_64-pc-windows-msvc",
19
+ };
20
+
21
+ function getPlatformKey() {
22
+ const key = `${os.platform()}-${os.arch()}`;
23
+ const target = PLATFORM_MAP[key];
24
+ if (!target) {
25
+ console.error(
26
+ `foxguard: unsupported platform ${key}. Supported: ${Object.keys(PLATFORM_MAP).join(", ")}`
27
+ );
28
+ process.exit(1);
29
+ }
30
+ return target;
31
+ }
32
+
33
+ function getCacheDir() {
34
+ // Cache in node_modules/.cache/foxguard/ (standard convention)
35
+ const nmCache = path.join(__dirname, "..", "node_modules", ".cache", "foxguard");
36
+ try {
37
+ fs.mkdirSync(nmCache, { recursive: true });
38
+ return nmCache;
39
+ } catch {
40
+ // Fallback to home directory
41
+ const home = path.join(os.homedir(), ".cache", "foxguard");
42
+ fs.mkdirSync(home, { recursive: true });
43
+ return home;
44
+ }
45
+ }
46
+
47
+ function getBinaryName() {
48
+ return os.platform() === "win32" ? "foxguard.exe" : "foxguard";
49
+ }
50
+
51
+ // Check if cargo-installed foxguard exists
52
+ function findCargoInstall() {
53
+ try {
54
+ const cargoHome = process.env.CARGO_HOME || path.join(os.homedir(), ".cargo");
55
+ const cargoBin = path.join(cargoHome, "bin", getBinaryName());
56
+ if (fs.existsSync(cargoBin)) {
57
+ return cargoBin;
58
+ }
59
+ } catch {}
60
+
61
+ // Try PATH
62
+ try {
63
+ const which = os.platform() === "win32" ? "where" : "which";
64
+ const result = execSync(`${which} foxguard 2>/dev/null`, {
65
+ encoding: "utf8",
66
+ }).trim();
67
+ if (result && !result.includes("node_modules")) {
68
+ return result;
69
+ }
70
+ } catch {}
71
+
72
+ return null;
73
+ }
74
+
75
+ function download(url) {
76
+ return new Promise((resolve, reject) => {
77
+ const get = (u) => {
78
+ https
79
+ .get(u, { headers: { "User-Agent": "foxguard-npm" } }, (res) => {
80
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
81
+ get(res.headers.location);
82
+ return;
83
+ }
84
+ if (res.statusCode !== 200) {
85
+ reject(new Error(`HTTP ${res.statusCode} fetching ${u}`));
86
+ return;
87
+ }
88
+ const chunks = [];
89
+ res.on("data", (c) => chunks.push(c));
90
+ res.on("end", () => resolve(Buffer.concat(chunks)));
91
+ res.on("error", reject);
92
+ })
93
+ .on("error", reject);
94
+ };
95
+ get(url);
96
+ });
97
+ }
98
+
99
+ async function downloadBinary() {
100
+ const target = getPlatformKey();
101
+ const cacheDir = getCacheDir();
102
+ const cachedBin = path.join(cacheDir, getBinaryName());
103
+
104
+ // Check version marker
105
+ const versionFile = path.join(cacheDir, ".version");
106
+ if (fs.existsSync(cachedBin) && fs.existsSync(versionFile)) {
107
+ const cached = fs.readFileSync(versionFile, "utf8").trim();
108
+ if (cached === VERSION) {
109
+ return cachedBin;
110
+ }
111
+ }
112
+
113
+ const [arch, vendor, osPart] = target.split("-");
114
+ let assetName;
115
+ if (osPart === "darwin") {
116
+ assetName = `foxguard-macos-${arch}`;
117
+ } else if (osPart === "linux") {
118
+ assetName = `foxguard-linux-${arch}`;
119
+ } else if (osPart === "windows") {
120
+ assetName = `foxguard-windows-${arch}.exe`;
121
+ } else {
122
+ throw new Error(`unsupported release target: ${target}`);
123
+ }
124
+ const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${assetName}`;
125
+
126
+ console.error(`foxguard: downloading v${VERSION} for ${target}...`);
127
+
128
+ try {
129
+ const data = await download(url);
130
+ fs.writeFileSync(cachedBin, data);
131
+
132
+ // Make executable
133
+ if (os.platform() !== "win32") {
134
+ fs.chmodSync(cachedBin, 0o755);
135
+ }
136
+
137
+ // Write version marker
138
+ fs.writeFileSync(versionFile, VERSION);
139
+
140
+ return cachedBin;
141
+ } catch (err) {
142
+ console.error(`foxguard: failed to download binary — ${err.message}`);
143
+ console.error(`foxguard: you can install manually with: cargo install foxguard`);
144
+ process.exit(1);
145
+ }
146
+ }
147
+
148
+ async function main() {
149
+ // 1. Check for cargo-installed binary
150
+ const cargoBin = findCargoInstall();
151
+ if (cargoBin) {
152
+ try {
153
+ execFileSync(cargoBin, process.argv.slice(2), { stdio: "inherit" });
154
+ return;
155
+ } catch (e) {
156
+ process.exit(e.status || 1);
157
+ }
158
+ }
159
+
160
+ // 2. Download or use cached binary
161
+ const bin = await downloadBinary();
162
+ try {
163
+ execFileSync(bin, process.argv.slice(2), { stdio: "inherit" });
164
+ } catch (e) {
165
+ process.exit(e.status || 1);
166
+ }
167
+ }
168
+
169
+ main();
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "foxguard",
3
+ "version": "0.1.0",
4
+ "description": "Fast local security guard for changed files, built-in rules, and Semgrep-compatible YAML.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/peaktwilight/foxguard.git"
9
+ },
10
+ "homepage": "https://foxguard.dev",
11
+ "keywords": [
12
+ "security",
13
+ "linter",
14
+ "static-analysis",
15
+ "sast",
16
+ "vulnerability",
17
+ "scanner"
18
+ ],
19
+ "bin": {
20
+ "foxguard": "bin/foxguard"
21
+ },
22
+ "files": [
23
+ "bin/",
24
+ "README.md"
25
+ ],
26
+ "os": [
27
+ "darwin",
28
+ "linux",
29
+ "win32"
30
+ ],
31
+ "cpu": [
32
+ "x64",
33
+ "arm64"
34
+ ],
35
+ "engines": {
36
+ "node": ">=14"
37
+ }
38
+ }