aegis-rex 1.5.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,151 @@
1
+ # aegis-rex
2
+
3
+ **ÆGIS Security Audit (`rex`)** — a local, offline security auditor you can drive
4
+ by hand *or* by an AI agent. No server, no telemetry, no account.
5
+
6
+ This npm package is a **distribution channel and a cross-platform launcher**. The
7
+ auditor itself is a single dependency-free Python file; there is no JavaScript
8
+ dependency tree to audit, by design. A security tool that pulls transitive
9
+ packages into your `PATH` is a supply-chain liability.
10
+
11
+ ```console
12
+ $ npm i -g aegis-rex
13
+ $ rex --audit
14
+ ```
15
+
16
+ ---
17
+
18
+ ## What it does
19
+
20
+ `rex` runs **16 audit sections** (+ optional ClamAV = 17 checks) against the
21
+ local machine and reports findings with severities:
22
+
23
+ | | |
24
+ |---|---|
25
+ | System | system info, CPU & memory, disk, network, running processes |
26
+ | Exposure | open ports, firewall, listening services |
27
+ | Access | SSH config, sudo privileges, users & groups, SUID/SGID files |
28
+ | Hygiene | world-writable files, sensitive file permissions, environment secrets, scheduled tasks, startup services |
29
+ | Malware | ClamAV virus scan (only if ClamAV is installed) |
30
+
31
+ Then it can ask an LLM for a remediation and apply it — with the commands
32
+ classified for danger first. **Nothing is ever executed without `--apply`.**
33
+
34
+ ## Requirements
35
+
36
+ | | |
37
+ |---|---|
38
+ | Python | **3.10+** (required — `rex.py` uses PEP 604 `X \| None` syntax evaluated at import) |
39
+ | Node.js | 16+, only for this launcher |
40
+ | `psutil` | *optional* — without it, 4 of 16 sections report `status: error` (see below) |
41
+ | PyQt6 | *optional* — desktop GUI only (`rex --gui`); the CLI never needs it |
42
+ | Display | *not* required for any `--` command |
43
+
44
+ The launcher probes `python3`, `python`, and (on Windows) `py -3`, then hands over
45
+ stdio and the exit code untouched. Install Python if it is missing:
46
+
47
+ ```console
48
+ $ sudo apt install python3 # Debian/Ubuntu
49
+ $ sudo dnf install python3 # Fedora/RHEL
50
+ $ brew install python@3.12 # macOS
51
+ $ winget install Python.Python.3.12 # Windows
52
+ ```
53
+
54
+ Optional extras:
55
+
56
+ ```console
57
+ $ pip install psutil # 4 richer audit sections
58
+ $ pip install psutil PyQt6 # + the desktop GUI
59
+ ```
60
+
61
+ ## Use as a human
62
+
63
+ ```console
64
+ $ rex --audit # colour terminal report
65
+ $ rex --audit --json # machine-readable (aegis.rex.audit/1)
66
+ $ rex --list-checks # the 17 stable section ids
67
+ $ rex --fix ssh-config --provider deepseek --json # ask a model for the fix
68
+ $ rex --gui # desktop window (needs PyQt6)
69
+ ```
70
+
71
+ ## Use from an agent
72
+
73
+ This is the part the CLI is built around. Start with `--capabilities`: it prints
74
+ the whole contract as JSON, so an agent can discover the surface instead of
75
+ scraping `--help`.
76
+
77
+ ```console
78
+ $ rex --capabilities # full contract: commands, args, schemas, exit codes
79
+ $ rex --rex-env # launcher introspection: which python, where rex.py is
80
+ ```
81
+
82
+ ```console
83
+ $ rex --audit --json --fail-on warn
84
+ ```
85
+
86
+ ### Exit codes
87
+
88
+ Uniform across commands, so a wrapper can branch on them:
89
+
90
+ | Code | Meaning |
91
+ |---|---|
92
+ | `0` | clean — audit ran, nothing met the `--fail-on` threshold |
93
+ | `1` | audit ran and findings met the **explicit** `--fail-on` threshold |
94
+ | `2` | bad arguments or unreadable input |
95
+ | `3` | AI provider unreachable, unauthorized, or out of budget |
96
+ | `4` | `--apply` ran and at least one command failed |
97
+
98
+ `--fail-on` defaults to `none`, so `--audit` exits `0` unless you explicitly opt
99
+ into gating. **CI must pass `--fail-on warn`** (or `critical`) to fail on findings.
100
+
101
+ ### Schemas
102
+
103
+ | Command | Schema |
104
+ |---|---|
105
+ | `--audit --json` | `aegis.rex.audit/1` |
106
+ | `--fix … --json` | `aegis.rex.fix/1` |
107
+ | `--capabilities` | `aegis.rex.capabilities/1` |
108
+
109
+ `summary.unassessed` lists section ids that could not be evaluated — currently the
110
+ four `psutil` sections on a bare interpreter. They report `status: error`, which
111
+ means *absence of evidence, not a pass*: a low score caused by a missing module
112
+ stays distinguishable from a genuinely unhealthy host.
113
+
114
+ ### Redaction and safety
115
+
116
+ Three sections — **Sudo / Privileges**, **SSH Config**, **Users & Groups** — are
117
+ redacted in saved reports, because they leak usernames and host details. A report
118
+ that mentions them is not consent to send them to a model, so `--fix` **refuses**
119
+ to run from a saved report for those sections (exit `2`) unless you pass `--yes`.
120
+
121
+ Every command a model proposes is classified before it is shown or run. Blocked
122
+ patterns include `rm -rf /`, `dd … of=/dev/*`, `mkfs`, fork bombs,
123
+ `curl … | sh`, and writes to `/etc/passwd`, `/etc/shadow`, `/etc/sudoers` and
124
+ `/etc/hosts`. The same classifier backs both `--apply` and the GUI dialog, so
125
+ they cannot disagree.
126
+
127
+ ### Providers
128
+
129
+ | Provider | Notes |
130
+ |---|---|
131
+ | Ollama | local, private, can auto-pull the model; no API key |
132
+ | Claude | `ANTHROPIC_API_KEY` |
133
+ | DeepSeek | `DEEPSEEK_API_KEY` |
134
+
135
+ Set the key in the environment, or configure it once in the GUI (stored in
136
+ `~/.aegis_config.json`, mode `600`).
137
+
138
+ ## Artifact integrity
139
+
140
+ `vendor/rex.py` is byte-identical to the source of truth published at
141
+ <https://github.com/aegisinfo/rex> and served for direct download at
142
+ <https://aegiscloud.org/rex>. `npm run sync` refreshes it under a version guard
143
+ and refuses to proceed if `package.json` and the artifact disagree.
144
+
145
+ ```console
146
+ $ sha256sum vendor/rex.py
147
+ ```
148
+
149
+ ## License
150
+
151
+ MIT
package/bin/rex.js ADDED
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Cross-platform launcher for rex.py.
6
+ *
7
+ * rex is a Python security auditor. npm is only the distribution channel: this
8
+ * shim finds a suitable interpreter, then hands over stdio and the exit code
9
+ * untouched, so `rex --audit --json` behaves identically whether it is invoked
10
+ * from npm, from a shell alias, or by an AI agent reading --capabilities.
11
+ *
12
+ * No npm dependencies, by design. A security tool that pulls a dependency tree
13
+ * into your PATH is a supply-chain liability, and the launcher is small enough
14
+ * that it does not need one.
15
+ */
16
+
17
+ const { spawnSync } = require('child_process');
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ const REX_PY = path.join(__dirname, '..', 'vendor', 'rex.py');
22
+ const MIN_PYTHON = [3, 10];
23
+
24
+ // GUI needs PyQt6; the headless audit/CLI path needs nothing but the stdlib.
25
+ const GUI_ARGS = new Set(['--gui']);
26
+
27
+ function fail(message, hint) {
28
+ process.stderr.write(`rex: ${message}\n`);
29
+ if (hint) process.stderr.write(`${hint}\n`);
30
+ process.exit(2);
31
+ }
32
+
33
+ /**
34
+ * Interpreters to try, in order of preference for the current platform.
35
+ * `py -3` is the supported launcher on Windows; bare `python` there is often
36
+ * the Microsoft Store stub, which we detect by probing it rather than trusting
37
+ * the name.
38
+ */
39
+ function candidates() {
40
+ if (process.platform === 'win32') {
41
+ return [
42
+ { cmd: 'py', args: ['-3'] },
43
+ { cmd: 'python3', args: [] },
44
+ { cmd: 'python', args: [] },
45
+ ];
46
+ }
47
+ return [
48
+ { cmd: 'python3', args: [] },
49
+ { cmd: 'python', args: [] },
50
+ ];
51
+ }
52
+
53
+ /** Probe one candidate. Returns {ok, version, major, minor} without throwing. */
54
+ function probe(candidate) {
55
+ const res = spawnSync(
56
+ candidate.cmd,
57
+ [...candidate.args, '-c',
58
+ 'import sys;print("%d.%d.%d"%sys.version_info[:3])'],
59
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
60
+ );
61
+ if (res.error || res.status !== 0) return { ok: false };
62
+ const version = String(res.stdout || '').trim();
63
+ const parts = version.split('.').map((n) => parseInt(n, 10));
64
+ if (parts.length < 2 || Number.isNaN(parts[0]) || Number.isNaN(parts[1])) {
65
+ return { ok: false };
66
+ }
67
+ return { ok: true, version, major: parts[0], minor: parts[1] };
68
+ }
69
+
70
+ function tooOld(found) {
71
+ return (
72
+ found.major < MIN_PYTHON[0] ||
73
+ (found.major === MIN_PYTHON[0] && found.minor < MIN_PYTHON[1])
74
+ );
75
+ }
76
+
77
+ /** Resolve a usable interpreter, or exit with an actionable message. */
78
+ function findPython() {
79
+ const tried = [];
80
+ let sawOld = null;
81
+
82
+ for (const candidate of candidates()) {
83
+ const found = probe(candidate);
84
+ if (!found.ok) {
85
+ tried.push(candidate.cmd);
86
+ continue;
87
+ }
88
+ if (tooOld(found)) {
89
+ if (!sawOld) sawOld = { cmd: candidate.cmd, ...found };
90
+ tried.push(`${candidate.cmd} (${found.version})`);
91
+ continue;
92
+ }
93
+ return candidate;
94
+ }
95
+
96
+ if (sawOld) {
97
+ fail(
98
+ `found Python ${sawOld.version} at '${sawOld.cmd}', but rex needs ` +
99
+ `${MIN_PYTHON.join('.')}+ (PEP 604 type syntax is evaluated at import).`,
100
+ installHint()
101
+ );
102
+ }
103
+ fail(
104
+ `no Python ${MIN_PYTHON.join('.')}+ interpreter found (tried: ${tried.join(', ') || 'none'}).`,
105
+ installHint()
106
+ );
107
+ }
108
+
109
+ function installHint() {
110
+ const lines = ['Install Python ' + MIN_PYTHON.join('.') + '+:'];
111
+ if (process.platform === 'win32') {
112
+ lines.push(' winget install Python.Python.3.12');
113
+ lines.push(' (or https://python.org/downloads — tick "Add python.exe to PATH")');
114
+ } else if (process.platform === 'darwin') {
115
+ lines.push(' brew install python@3.12');
116
+ } else {
117
+ lines.push(' sudo apt install python3 # Debian/Ubuntu');
118
+ lines.push(' sudo dnf install python3 # Fedora/RHEL');
119
+ }
120
+ lines.push('');
121
+ lines.push('Optional extras:');
122
+ lines.push(' pip install psutil # 4 richer audit sections');
123
+ lines.push(' pip install psutil PyQt6 # + the desktop GUI');
124
+ return lines.join('\n');
125
+ }
126
+
127
+ function main() {
128
+ const argv = process.argv.slice(2);
129
+
130
+ // Environment introspection, so an agent can tell "rex is broken" apart from
131
+ // "rex has no interpreter" without guessing.
132
+ if (argv.includes('--rex-env')) {
133
+ const python = findPython();
134
+ const found = probe(python);
135
+ process.stdout.write(
136
+ JSON.stringify(
137
+ {
138
+ launcher: 'aegis-rex',
139
+ platform: process.platform,
140
+ arch: process.arch,
141
+ node: process.version,
142
+ python: {
143
+ command: python.cmd,
144
+ args: python.args,
145
+ version: found.version,
146
+ },
147
+ rex_py: REX_PY,
148
+ rex_py_present: fs.existsSync(REX_PY),
149
+ },
150
+ null,
151
+ 2
152
+ ) + '\n'
153
+ );
154
+ return 0;
155
+ }
156
+
157
+ if (!fs.existsSync(REX_PY)) {
158
+ fail(`bundled artifact missing at ${REX_PY} — reinstall: npm i -g aegis-rex`);
159
+ }
160
+
161
+ const python = findPython();
162
+ const wantsGui = argv.some((a) => GUI_ARGS.has(a));
163
+
164
+ const res = spawnSync(
165
+ python.cmd,
166
+ [...python.args, REX_PY, ...argv],
167
+ { stdio: 'inherit' }
168
+ );
169
+
170
+ if (res.error) {
171
+ fail(`failed to start ${python.cmd}: ${res.error.message}`);
172
+ }
173
+ if (res.signal) {
174
+ // Mirror the shell convention so `kill -TERM` on the GUI is observable.
175
+ process.kill(process.pid, res.signal);
176
+ return 128;
177
+ }
178
+
179
+ const code = res.status === null ? 1 : res.status;
180
+
181
+ // rex exit 2 is "usage / missing dependency". When the GUI was requested and
182
+ // PyQt6 is the reason, the raw Python traceback is not the useful message.
183
+ if (code === 2 && wantsGui) {
184
+ process.stderr.write(
185
+ '\nThe GUI needs PyQt6 (the audit and CLI do not):\n' +
186
+ ` ${python.cmd} -m pip install PyQt6\n` +
187
+ 'Or run headless: rex --audit\n'
188
+ );
189
+ }
190
+ return code;
191
+ }
192
+
193
+ process.exit(main());
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "aegis-rex",
3
+ "version": "1.5.1",
4
+ "description": "Headless-drivable security auditor for Linux, macOS and Windows. Runs a 17-check local audit and can be driven by an AI agent (--capabilities / --audit --json / --fix). npm is the distribution channel; the tool itself is dependency-free Python.",
5
+ "bin": {
6
+ "rex": "bin/rex.js"
7
+ },
8
+ "files": [
9
+ "bin/rex.js",
10
+ "vendor/rex.py",
11
+ "vendor/LICENSE",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=16"
16
+ },
17
+ "os": [
18
+ "linux",
19
+ "darwin",
20
+ "win32"
21
+ ],
22
+ "scripts": {
23
+ "test": "node --test",
24
+ "sync": "node scripts/sync-artifact.js"
25
+ },
26
+ "keywords": [
27
+ "security",
28
+ "audit",
29
+ "hardening",
30
+ "cis",
31
+ "ssh",
32
+ "firewall",
33
+ "suid",
34
+ "secrets",
35
+ "ai-agent",
36
+ "llm",
37
+ "mcp",
38
+ "cli",
39
+ "headless",
40
+ "devsecops"
41
+ ],
42
+ "author": "AEGIS",
43
+ "license": "MIT",
44
+ "homepage": "https://aegiscloud.org/rex",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/aegisinfo/rex.git"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/aegisinfo/rex/issues"
51
+ },
52
+ "dependencies": {},
53
+ "publishConfig": {
54
+ "access": "public"
55
+ }
56
+ }
package/vendor/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Niklas Borneklint
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.