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.
- package/README.md +59 -0
- package/bin/foxguard +169 -0
- 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
|
+
}
|