trooth 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 +39 -0
- package/bin/trooth.mjs +134 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# trooth
|
|
2
|
+
|
|
3
|
+
Compliance, automated — from your terminal. Scan a Terraform plan against SOC 2, ISO 27001,
|
|
4
|
+
GDPR, HIPAA, NIST AI RMF, and the EU AI Act, and get a signed Compliance Delta back.
|
|
5
|
+
|
|
6
|
+
**Advisory and report-only. Trooth never applies changes to your infrastructure.**
|
|
7
|
+
|
|
8
|
+
## Install / run
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# No install needed:
|
|
12
|
+
npx trooth scan ./plan.tfplan
|
|
13
|
+
|
|
14
|
+
# or generate JSON yourself first:
|
|
15
|
+
terraform show -json plan.tfplan > plan.json
|
|
16
|
+
npx trooth scan plan.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
| Command | What it does |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `trooth scan <plan>` | Posts your `terraform show -json` plan to Trooth Pre-Flight (`POST https://api.trooth.co/v1/preflight`) and prints the verdict, score, and findings. Accepts a JSON file or a binary `.tfplan` (it runs `terraform show -json` for you). |
|
|
24
|
+
| `trooth lint [path]` | Local, read-only IaC drift check. Never transmits your code. |
|
|
25
|
+
| `trooth --help` / `--version` | Help / version. |
|
|
26
|
+
|
|
27
|
+
## Flags
|
|
28
|
+
|
|
29
|
+
- `--strict` — exit non-zero if there are findings (default: exit 0, advisory).
|
|
30
|
+
- `--json` — print the raw signed Compliance Delta.
|
|
31
|
+
|
|
32
|
+
## Notes
|
|
33
|
+
|
|
34
|
+
- Only your **declared plan** is sent to the Pre-Flight API; nothing is written back, and
|
|
35
|
+
Trooth never touches live infrastructure.
|
|
36
|
+
- Set `TROOTH_API` to point at a different base URL (defaults to `https://api.trooth.co`).
|
|
37
|
+
- Requires Node 18+ (uses built-in `fetch`).
|
|
38
|
+
|
|
39
|
+
Trooth automates. Trooth never signs for you.
|
package/bin/trooth.mjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Trooth CLI — `trooth`
|
|
3
|
+
// Copyright (c) 2026 Trooth, LLC. All rights reserved.
|
|
4
|
+
//
|
|
5
|
+
// Commands:
|
|
6
|
+
// trooth scan <plan> Send a Terraform plan to Trooth Pre-Flight (advisory, report-only).
|
|
7
|
+
// <plan> may be a `terraform show -json` JSON file, OR a binary
|
|
8
|
+
// .tfplan (the CLI will run `terraform show -json` for you).
|
|
9
|
+
// trooth lint [path] Local, read-only IaC drift check (never transmits your code).
|
|
10
|
+
// trooth --help Show help. trooth --version Show version.
|
|
11
|
+
//
|
|
12
|
+
// Pre-Flight is ADVISORY and REPORT-ONLY. Trooth never applies changes to your
|
|
13
|
+
// infrastructure. The scan posts only your declared plan to the Pre-Flight API and
|
|
14
|
+
// returns a signed Compliance Delta. Trooth automates. Trooth never signs for you.
|
|
15
|
+
|
|
16
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
17
|
+
import { execFileSync } from 'node:child_process';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
|
|
20
|
+
const API = process.env.TROOTH_API || 'https://api.trooth.co';
|
|
21
|
+
const J='\x1b[32m', D='\x1b[2m', B='\x1b[1m', R='\x1b[31m', A='\x1b[33m', X='\x1b[0m';
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
let VERSION = '0.1.0';
|
|
24
|
+
try { VERSION = require('../package.json').version; } catch {}
|
|
25
|
+
|
|
26
|
+
const argv = process.argv.slice(2);
|
|
27
|
+
const cmd = argv[0];
|
|
28
|
+
|
|
29
|
+
function help() {
|
|
30
|
+
console.log(`
|
|
31
|
+
${J}${B}trooth${X} ${D}v${VERSION} — compliance, automated (advisory, report-only)${X}
|
|
32
|
+
|
|
33
|
+
${B}Usage${X}
|
|
34
|
+
trooth scan <plan> Scan a Terraform plan via Trooth Pre-Flight
|
|
35
|
+
trooth lint [path] Local read-only IaC drift check
|
|
36
|
+
trooth --help | --version
|
|
37
|
+
|
|
38
|
+
${B}Examples${X}
|
|
39
|
+
terraform show -json plan.tfplan > plan.json && trooth scan plan.json
|
|
40
|
+
trooth scan ./plan.tfplan ${D}# runs \`terraform show -json\` for you${X}
|
|
41
|
+
|
|
42
|
+
${B}Flags${X}
|
|
43
|
+
--strict Exit non-zero if findings exist (default: exit 0, advisory)
|
|
44
|
+
--json Print raw JSON response
|
|
45
|
+
|
|
46
|
+
${D}Pre-Flight is advisory and report-only — Trooth never applies changes.
|
|
47
|
+
Trooth automates. Trooth never signs for you.${X}
|
|
48
|
+
`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function loadPlan(file) {
|
|
52
|
+
if (!file) { console.error(`${R}error${X} missing <plan> argument. Try: trooth scan ./plan.json`); process.exit(2); }
|
|
53
|
+
if (!existsSync(file)) { console.error(`${R}error${X} file not found: ${file}`); process.exit(2); }
|
|
54
|
+
// Try to read as JSON (a `terraform show -json` document).
|
|
55
|
+
let raw = '';
|
|
56
|
+
try { raw = readFileSync(file, 'utf8'); } catch (e) { console.error(`${R}error${X} cannot read ${file}: ${e.message}`); process.exit(2); }
|
|
57
|
+
try { return JSON.parse(raw); } catch { /* not JSON — likely a binary .tfplan */ }
|
|
58
|
+
// Fall back: ask Terraform to render the plan as JSON.
|
|
59
|
+
try {
|
|
60
|
+
const out = execFileSync('terraform', ['show', '-json', file], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
|
|
61
|
+
return JSON.parse(out);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error(`${R}error${X} ${file} is not JSON, and \`terraform show -json\` failed.`);
|
|
64
|
+
console.error(`${D} Generate JSON first: terraform show -json ${file} > plan.json → trooth scan plan.json${X}`);
|
|
65
|
+
process.exit(2);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function scan() {
|
|
70
|
+
const file = argv.find((a, i) => i > 0 && !a.startsWith('--'));
|
|
71
|
+
const strict = argv.includes('--strict');
|
|
72
|
+
const asJson = argv.includes('--json');
|
|
73
|
+
const plan = loadPlan(file);
|
|
74
|
+
|
|
75
|
+
let res;
|
|
76
|
+
try {
|
|
77
|
+
res = await fetch(`${API}/v1/preflight`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: { 'content-type': 'application/json', 'user-agent': `trooth-cli/${VERSION}` },
|
|
80
|
+
body: JSON.stringify({ plan }),
|
|
81
|
+
});
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error(`${R}error${X} could not reach Trooth Pre-Flight at ${API}: ${e.message}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
if (!res.ok) {
|
|
87
|
+
const body = await res.text().catch(() => '');
|
|
88
|
+
console.error(`${R}error${X} Pre-Flight returned HTTP ${res.status}. ${body.slice(0, 300)}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
const delta = await res.json();
|
|
92
|
+
|
|
93
|
+
if (asJson) { console.log(JSON.stringify(delta, null, 2)); process.exit(strict && (delta.summary?.fail > 0) ? 1 : 0); }
|
|
94
|
+
|
|
95
|
+
const score = delta.score ?? '–';
|
|
96
|
+
const verdict = (delta.verdict || 'reviewed').toUpperCase();
|
|
97
|
+
const s = delta.summary || {};
|
|
98
|
+
console.log(`\n${J}${B}Trooth Pre-Flight${X} ${D}// advisory · report-only //${X}`);
|
|
99
|
+
console.log(`Verdict: ${B}${verdict}${X} Score: ${B}${score}${typeof score === 'number' ? '/100' : ''}${X} ` +
|
|
100
|
+
`${J}pass ${s.pass ?? 0}${X} · ${A}fail ${s.fail ?? 0}${X} · ${D}n/a ${s.notApplicable ?? 0}${X}`);
|
|
101
|
+
const findings = delta.findings || [];
|
|
102
|
+
if (findings.length) {
|
|
103
|
+
console.log(`\n${B}${findings.length} finding(s):${X}\n`);
|
|
104
|
+
for (const f of findings.slice(0, 50)) {
|
|
105
|
+
const sev = (f.severity || '').toLowerCase();
|
|
106
|
+
const tag = sev === 'critical' || sev === 'high' ? `${R}● ${sev.toUpperCase()}${X}`
|
|
107
|
+
: sev === 'medium' ? `${A}● MEDIUM${X}` : `${D}● ${(sev||'LOW').toUpperCase()}${X}`;
|
|
108
|
+
console.log(` ${tag} ${B}${f.title || f.message || f.check || 'finding'}${X}`);
|
|
109
|
+
if (f.control || f.frameworks) console.log(` ${J}↳${X} ${f.control || (Array.isArray(f.frameworks) ? f.frameworks.join(', ') : f.frameworks)}`);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
console.log(`\n${J}✓ No findings in the declared plan.${X}`);
|
|
113
|
+
}
|
|
114
|
+
console.log(`\n${D}${delta.disclaimer || 'Advisory. Analyzes declared infrastructure intent, not live production state. Trooth never applies changes.'}${X}\n`);
|
|
115
|
+
|
|
116
|
+
process.exit(strict && (s.fail > 0) ? 1 : 0); // advisory by default: exit 0
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function lint() {
|
|
120
|
+
// Local, read-only .tf drift check. Reuses the published @trooth/os linter if present,
|
|
121
|
+
// otherwise runs a built-in minimal ruleset. Never transmits your code.
|
|
122
|
+
const path = argv[1] && !argv[1].startsWith('--') ? argv[1] : '.';
|
|
123
|
+
const { spawnSync } = await import('node:child_process');
|
|
124
|
+
const r = spawnSync('npx', ['--yes', '@trooth/os', path], { stdio: 'inherit' });
|
|
125
|
+
process.exit(r.status ?? 0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
(async () => {
|
|
129
|
+
if (cmd === '--version' || cmd === '-v') { console.log(VERSION); return; }
|
|
130
|
+
if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') { help(); return; }
|
|
131
|
+
if (cmd === 'scan') return scan();
|
|
132
|
+
if (cmd === 'lint') return lint();
|
|
133
|
+
console.error(`${R}error${X} unknown command: ${cmd}\n`); help(); process.exit(2);
|
|
134
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "trooth",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Trooth CLI — scan your Terraform plan against SOC 2 / ISO 27001 / GDPR / HIPAA / NIST AI RMF / EU AI Act. Advisory and report-only. Trooth never applies changes.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"trooth": "./bin/trooth.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"scan": "node ./bin/trooth.mjs scan"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "Trooth, LLC",
|
|
21
|
+
"homepage": "https://trooth.co",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/trooth/trooth-cli.git"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"compliance",
|
|
28
|
+
"soc2",
|
|
29
|
+
"iso27001",
|
|
30
|
+
"gdpr",
|
|
31
|
+
"hipaa",
|
|
32
|
+
"nist-ai-rmf",
|
|
33
|
+
"eu-ai-act",
|
|
34
|
+
"terraform",
|
|
35
|
+
"iac",
|
|
36
|
+
"trooth"
|
|
37
|
+
]
|
|
38
|
+
}
|