aletheia-firewall 0.5.0 → 0.6.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/.helios-baseline +1 -1
- package/README.md +44 -12
- package/index.js +5 -0
- package/package.json +2 -2
- package/src/ast-scan.js +1283 -0
- package/src/behavior-tracker.js +37 -15
- package/src/detector.js +85 -1
package/.helios-baseline
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
fea07f1e4d6c145de22dccff70a8069e3c9653441313df2f00bb60796abc9ec9
|
package/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Aletheia Firewall
|
|
2
2
|
|
|
3
|
-
Zero-dependency runtime
|
|
3
|
+
Zero-dependency, opt-in runtime enforcement layer for supported Node.js module-loading paths. It
|
|
4
|
+
scans modules as they are compiled/loaded (`require()` of `.js`/`.cjs`, and `import` of `file://`
|
|
5
|
+
ESM on supported Node versions) using Aho-Corasick signature matching, behavioral-sequence
|
|
6
|
+
correlation, and — opt-in — a narrow AST obfuscation tier, then enforces a signed policy. It is
|
|
7
|
+
defense-in-depth, **not** a comprehensive malware blocker: it covers specific module-loading paths
|
|
8
|
+
(see the coverage table below) and a documented set of techniques, with known gaps listed honestly.
|
|
4
9
|
|
|
5
10
|
## Install
|
|
6
11
|
|
|
@@ -55,11 +60,30 @@ Aletheia is a **runtime enforcement layer**: it watches what a dependency does o
|
|
|
55
60
|
already in your CommonJS require graph, after `npm install` has finished. It is not an
|
|
56
61
|
install-time scanner and does not intercept package installation.
|
|
57
62
|
|
|
58
|
-
Detection
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
Detection runs in tiers: **signature + behavioral by default**, plus an **opt-in AST obfuscation
|
|
64
|
+
tier** (`FW_ENABLE_AST=1`, off by default pending a broader benign-package soak). By default,
|
|
65
|
+
payloads can still evade static matching through string-splitting, encoding, or indirection; the
|
|
66
|
+
AST tier folds and structurally resolves many of those. Our adversarial corpus documents both
|
|
67
|
+
honestly:
|
|
68
|
+
|
|
69
|
+
| Configuration | Malicious caught | Known bypasses | False positives* |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| **Default** (`FW_ENABLE_AST` unset) | **105/142 (73.9%)** | 37 | 0 / 36 controls |
|
|
72
|
+
| **`FW_ENABLE_AST=1`** (opt-in) | **127/142 (89.4%)** | 15 | 0 / 36 controls |
|
|
73
|
+
|
|
74
|
+
\* "0 false positives" means zero across the **36 curated benign controls** — it is **not** a
|
|
75
|
+
measured general false-positive rate on arbitrary packages, which is one reason the AST tier ships
|
|
76
|
+
opt-in. Run them yourself from the repository root: `npm run redteam` (default) and
|
|
77
|
+
`npm run redteam:ast` (AST tier).
|
|
78
|
+
|
|
79
|
+
**Bounded, prioritized AST scanning.** The AST tier parses a bounded number of candidate spans per
|
|
80
|
+
file and scans them **highest-risk-first** (never in file order), so a payload can't be starved by a
|
|
81
|
+
flood of harmless decoy spans placed ahead of it. If a genuinely high-risk span is left unanalyzed
|
|
82
|
+
because the budget was exhausted, the scan is reported *incomplete* and `FW_AST_INCOMPLETE_POLICY`
|
|
83
|
+
decides what happens (see the environment-variable table).
|
|
84
|
+
|
|
85
|
+
Every bypass class is listed in [`red-team/README.md`](../../red-team/README.md) and the monorepo's
|
|
86
|
+
`docs/THREAT-COVERAGE.md`.
|
|
63
87
|
|
|
64
88
|
## Environment Variables
|
|
65
89
|
|
|
@@ -67,6 +91,10 @@ in [`red-team/README.md`](../../red-team/README.md).
|
|
|
67
91
|
|----------|---------|-------------|
|
|
68
92
|
| `FW_ENABLE_DETECTION` | `0` | Set to `1` to activate the firewall (required) |
|
|
69
93
|
| `FW_ENABLE_BEHAVIORAL` | `1` | Set to `0` to disable the behavioral pass while keeping signature scanning active. Useful as an escape hatch if behavioral detection produces false positives. Note: several detections (credential exfiltration, dynamic-code/exec chains, base64→eval obfuscation) rely on the behavioral pass — disabling it falls back to signature-only coverage. |
|
|
94
|
+
| `FW_ENABLE_AST` | `0` | Set to `1` to enable the opt-in AST obfuscation tier (`src/ast-scan.js`). Folds/resolves bracket-, alias-, unicode-escape-, concat-, decode-, and constructor-based obfuscation of `eval`/`Function`/`require`. Raises malicious coverage from 73.9% to 89.4% on the corpus (see the table above). Off by default pending a broader benign-package soak; it is internally fail-open (any error falls back to signature+behavioral). |
|
|
95
|
+
| `FW_AST_INCOMPLETE_POLICY` | `quarantine` | Only relevant when `FW_ENABLE_AST=1`. Governs what happens when a module is so densely packed with high-risk obfuscation shapes that the AST tier could not fully analyze it (an *incomplete* scan — the span-exhaustion attack shape). Defaults to **fail-closed** (`quarantine`/`block`): an un-analyzable suspicious module is treated as block-tier, so the bypass is closed by default rather than only for operators who opt in. Set `observe` to opt down to WARN-only telemetry (module still runs) if you prefer availability. Only fires on pathological saturation (>256 rare high-risk spans in one module); ordinary large bundles never reach it. |
|
|
96
|
+
| `FW_ENABLE_CROSSFILE` | `0` | Set to `1` to enable cross-file behavioral correlation within a package (capabilities split across files). Off by default: it false-positives on large legitimate packages that legitimately split credential reads, metadata fetches, and code-gen/spawn across files. Intended for curated dependency sets and the registry batch scanner. |
|
|
97
|
+
| `FW_CACHE_POLICY` | `block` under `FW_MODE=enforce`, else `audit` | How `require.cache`/`Module._load` pre-seeding (a forged cache entry that bypasses `_compile` entirely) is handled: `block` refuses the substitution, `audit` allows it but logs, `allow` disables the check. |
|
|
70
98
|
| `FW_TELEMETRY` | `0` | Set to `1` to start a telemetry worker that POSTs events to `FW_CONTROL_PORT`; with no control plane running it fails open and delivers nothing. |
|
|
71
99
|
| `FW_CONTROL_PORT` | `3000` | Port for the control plane telemetry ingestion endpoint (`fw-control`). Used by the telemetry worker when `FW_TELEMETRY=1`. |
|
|
72
100
|
| `FW_MODE` | `dev` | `enforce` fails closed (`process.exit(1)`) when not preloaded via `--require`; `dev` (default) warns loudly and continues. See the root README's "Enforcement mode vs Development mode" section. |
|
|
@@ -127,7 +155,8 @@ To reproduce, run the 900-module gate from the GitHub repo: `npm run gate`.
|
|
|
127
155
|
|
|
128
156
|
## Known Bypasses
|
|
129
157
|
|
|
130
|
-
This firewall provides defense-in-depth but cannot catch all threats.
|
|
158
|
+
This firewall provides defense-in-depth but cannot catch all threats. "Status" below is split by
|
|
159
|
+
tier: default (signature + behavioral) and `FW_ENABLE_AST=1` (the opt-in AST tier).
|
|
131
160
|
|
|
132
161
|
| Technique | Status |
|
|
133
162
|
|-----------|--------|
|
|
@@ -138,11 +167,14 @@ This firewall provides defense-in-depth but cannot catch all threats. Documented
|
|
|
138
167
|
| `.env`/credential read + network call | **BLOCKED** |
|
|
139
168
|
| `eval` + `child_process.exec` | **BLOCKED** |
|
|
140
169
|
| `curl \| bash` in host project's npm scripts | **BLOCKED** (root scripts only; not dependency install hooks) |
|
|
141
|
-
| Bracket eval: `this["ev"+"al"]` | **BYPASSES**
|
|
142
|
-
| String concat: `global["ev"+"al"]` | **BYPASSES**
|
|
143
|
-
| Variable-alias eval: `const fn = eval; fn("code")` | **BYPASSES**
|
|
144
|
-
| Array join: `["ch","ild"].join("")` | **BYPASSES
|
|
145
|
-
|
|
|
170
|
+
| Bracket eval: `this["ev"+"al"]` | Default: **BYPASSES** · `FW_ENABLE_AST=1`: **BLOCKED** (bracket key folded, resolved eval access) |
|
|
171
|
+
| String concat: `global["ev"+"al"]` | Default: **BYPASSES** · `FW_ENABLE_AST=1`: **BLOCKED** (concat folded) |
|
|
172
|
+
| Variable-alias eval: `const fn = eval; fn("code")` | Default: **BYPASSES** · `FW_ENABLE_AST=1`: **BLOCKED** (alias tracking) |
|
|
173
|
+
| Array join: `["ch","ild"].join("")` | Default: **BYPASSES** · `FW_ENABLE_AST=1`: **BLOCKED** (join folded, re-matched) |
|
|
174
|
+
| Unicode-escape eval: `eval(...)` | Default: **BYPASSES** · `FW_ENABLE_AST=1`: **BLOCKED** (tokenizer decodes escapes) |
|
|
175
|
+
| Prototype chain: `Object.getPrototypeOf(fn).constructor`, `constructor.constructor` | Default: **BYPASSES** · `FW_ENABLE_AST=1`: **BLOCKED** (constructor-chase resolved) |
|
|
176
|
+
| AST span-exhaustion: real payload hidden behind >40 decoy spans | `FW_ENABLE_AST=1`: **BLOCKED** (highest-risk-first span scheduling; incomplete scans governed by `FW_AST_INCOMPLETE_POLICY`) |
|
|
177
|
+
| WASM payloads, env-sourced config, network+process-exec taint chains, low-and-slow C2 | **BYPASSES even with `FW_ENABLE_AST=1`** — architectural limits of static/AST analysis; need runtime/dataflow instrumentation |
|
|
146
178
|
|
|
147
179
|
See the monorepo's `docs/THREAT-COVERAGE.md` for the full, test-backed protection/bypass matrix.
|
|
148
180
|
|
package/index.js
CHANGED
|
@@ -195,6 +195,11 @@ const fwMode = resolveFwMode();
|
|
|
195
195
|
// in the npm manifest and are security-critical, so they must be covered here — omitting
|
|
196
196
|
// them let a tampered aho-corasick silently defeat detection while self-integrity passed.
|
|
197
197
|
path.join(__dirname, 'src', 'aho-corasick.js'),
|
|
198
|
+
// ast-scan.js (Phase 3) is required by detector.js and feeds signal positions directly into
|
|
199
|
+
// detector.js's block-tier decisions — equally security-critical, same reasoning as
|
|
200
|
+
// aho-corasick.js above. This list is duplicated in three other places that must stay in
|
|
201
|
+
// lockstep — see the self-integrity-lockstep test in .agent/scripts/__tests__/.
|
|
202
|
+
path.join(__dirname, 'src', 'ast-scan.js'),
|
|
198
203
|
path.join(__dirname, 'sync-worker.js'),
|
|
199
204
|
];
|
|
200
205
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aletheia-firewall",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Zero-dependency runtime
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Zero-dependency, opt-in runtime enforcement for supported Node.js module-loading paths (require()/file:// ESM): signature, behavioral, and opt-in AST obfuscation detection with signed-policy enforcement. Defense-in-depth, not a comprehensive malware blocker.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Aletheia contributors",
|