@greenarmor/ges 1.3.0 → 1.4.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/dist/cli.js +2 -0
- package/dist/commands/audit.js +28 -19
- package/dist/commands/doctor.js +48 -7
- package/dist/commands/governance.d.ts +2 -0
- package/dist/commands/governance.js +726 -0
- package/dist/commands/init.js +35 -27
- package/dist/commands/policy.js +14 -10
- package/dist/commands/score.js +6 -2
- package/dist/utils/next-steps.js +11 -6
- package/dist/utils/prompts.d.ts +8 -0
- package/dist/utils/prompts.js +62 -11
- package/dist/utils/ui.d.ts +37 -0
- package/dist/utils/ui.js +115 -0
- package/package.json +18 -14
package/dist/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ import { controlCommand } from "./commands/control.js";
|
|
|
17
17
|
import { fixCommand } from "./commands/fix.js";
|
|
18
18
|
import { hooksCommand } from "./commands/hooks.js";
|
|
19
19
|
import { dashboardCommand } from "./commands/dashboard.js";
|
|
20
|
+
import { governanceCommand } from "./commands/governance.js";
|
|
20
21
|
import { CLI_VERSION } from "./utils/version.js";
|
|
21
22
|
const program = new Command();
|
|
22
23
|
program
|
|
@@ -40,4 +41,5 @@ program.addCommand(controlCommand);
|
|
|
40
41
|
program.addCommand(fixCommand);
|
|
41
42
|
program.addCommand(hooksCommand);
|
|
42
43
|
program.addCommand(dashboardCommand);
|
|
44
|
+
program.addCommand(governanceCommand);
|
|
43
45
|
program.parse();
|
package/dist/commands/audit.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getPacksForProjectType, getAllPacks } from "@greenarmor/ges-policy-engi
|
|
|
5
5
|
import { generateScoreFile, formatScoreOutput } from "@greenarmor/ges-scoring-engine";
|
|
6
6
|
import { runAudit, runAuditIncremental, deduplicateFindings } from "@greenarmor/ges-audit-engine";
|
|
7
7
|
import { showNextStepsMenu } from "../utils/next-steps.js";
|
|
8
|
+
import { banner, divider, blank, success, warn, info, severityBadge, BOLD, DIM, CYAN, RED, YELLOW, GRAY } from "../utils/ui.js";
|
|
8
9
|
import * as fs from "node:fs";
|
|
9
10
|
import * as path from "node:path";
|
|
10
11
|
export const auditCommand = new Command("audit")
|
|
@@ -15,9 +16,10 @@ export const auditCommand = new Command("audit")
|
|
|
15
16
|
.action(async (options) => {
|
|
16
17
|
const root = ensureGESInitialized();
|
|
17
18
|
const config = readJsonFile(path.join(root, ".ges", "config.json"));
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
if (!options.json) {
|
|
20
|
+
banner("GESF Compliance Audit", options.incremental ? "Incremental scan" : "Full project scan");
|
|
21
|
+
info("Scanning project files...");
|
|
22
|
+
}
|
|
21
23
|
let rawFindings;
|
|
22
24
|
let scannedFiles;
|
|
23
25
|
if (options.incremental) {
|
|
@@ -27,16 +29,19 @@ export const auditCommand = new Command("audit")
|
|
|
27
29
|
writeJsonFile(cachePath, result.newCache);
|
|
28
30
|
rawFindings = result.findings;
|
|
29
31
|
scannedFiles = result.scannedFiles;
|
|
30
|
-
|
|
32
|
+
if (!options.json)
|
|
33
|
+
success("Scan complete", `${scannedFiles} files (${result.changedFiles} changed)`);
|
|
31
34
|
}
|
|
32
35
|
else {
|
|
33
36
|
const result = runAudit(root);
|
|
34
37
|
rawFindings = result.findings;
|
|
35
38
|
scannedFiles = result.scannedFiles;
|
|
36
|
-
|
|
39
|
+
if (!options.json)
|
|
40
|
+
success("Scan complete", `${scannedFiles} files`);
|
|
37
41
|
}
|
|
38
42
|
const findings = deduplicateFindings(rawFindings);
|
|
39
|
-
|
|
43
|
+
if (!options.json)
|
|
44
|
+
blank();
|
|
40
45
|
const configFrameworks = (config?.frameworks || ["GDPR", "OWASP"]);
|
|
41
46
|
const projectPacks = getPacksForProjectType(config?.project_type || "generic-web-application");
|
|
42
47
|
const packIds = new Set(projectPacks.map(p => p.id));
|
|
@@ -76,7 +81,8 @@ export const auditCommand = new Command("audit")
|
|
|
76
81
|
if (overrides.length > 0) {
|
|
77
82
|
const naCount = overrides.filter(o => o.status === "not-applicable").length;
|
|
78
83
|
const passCount = overrides.filter(o => o.status === "pass").length;
|
|
79
|
-
|
|
84
|
+
if (!options.json)
|
|
85
|
+
info("Control overrides", `${naCount} not-applicable, ${passCount} pre-verified`);
|
|
80
86
|
}
|
|
81
87
|
if (options.json) {
|
|
82
88
|
console.log(JSON.stringify({ findings, score: scoreData }, null, 2));
|
|
@@ -84,34 +90,37 @@ export const auditCommand = new Command("audit")
|
|
|
84
90
|
process.exit(1);
|
|
85
91
|
return;
|
|
86
92
|
}
|
|
87
|
-
console.log("
|
|
88
|
-
|
|
89
|
-
console.log(`
|
|
93
|
+
console.log(` ${BOLD("Findings")}`);
|
|
94
|
+
divider(40);
|
|
95
|
+
console.log(` ${DIM("Total")} ${findings.length}`);
|
|
96
|
+
console.log(` ${RED(`Critical ${critical.length}`)} ${RED(`High ${high.length}`)} ${YELLOW(`Medium ${medium.length}`)} ${CYAN(`Low ${low.length}`)}\n`);
|
|
90
97
|
if (findings.length > 0) {
|
|
91
98
|
const grouped = groupByCategory(findings);
|
|
92
99
|
for (const [category, categoryFindings] of Object.entries(grouped)) {
|
|
93
|
-
console.log(`
|
|
100
|
+
console.log(` ${BOLD(category.toUpperCase())}`);
|
|
94
101
|
for (const f of categoryFindings.slice(0, 10)) {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
console.log(` [${sev}] ${f.title}${loc}`);
|
|
102
|
+
const loc = f.file !== "project" ? ` ${DIM(`(${f.file}${f.line ? ":" + f.line : ""})`)}` : "";
|
|
103
|
+
console.log(` ${severityBadge(f.severity).padEnd(10)} ${f.title}${loc}`);
|
|
98
104
|
if (f.evidence && f.file !== "project") {
|
|
99
|
-
console.log(` ${f.evidence.slice(0, 100)}`);
|
|
105
|
+
console.log(` ${GRAY(f.evidence.slice(0, 100))}`);
|
|
100
106
|
}
|
|
101
107
|
}
|
|
102
108
|
if (categoryFindings.length > 10) {
|
|
103
|
-
console.log(`
|
|
109
|
+
console.log(` ${DIM(`... and ${categoryFindings.length - 10} more`)}`);
|
|
104
110
|
}
|
|
105
111
|
console.log("");
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
else {
|
|
109
|
-
|
|
115
|
+
success("No security or compliance issues found in source code.");
|
|
116
|
+
blank();
|
|
110
117
|
}
|
|
111
|
-
console.log("
|
|
118
|
+
console.log(` ${BOLD("Compliance Score")}`);
|
|
119
|
+
divider(40);
|
|
112
120
|
console.log(formatScoreOutput(scoreData));
|
|
113
121
|
if (critical.length > 0) {
|
|
114
|
-
|
|
122
|
+
warn("Critical issues must be resolved before deployment.");
|
|
123
|
+
blank();
|
|
115
124
|
}
|
|
116
125
|
recordActivity(root, {
|
|
117
126
|
source: "cli",
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { findProjectRoot, readJsonFile } from "../utils/project.js";
|
|
3
3
|
import { CLI_VERSION } from "../utils/version.js";
|
|
4
|
-
import { GES_DIR } from "@greenarmor/ges-core";
|
|
4
|
+
import { GES_DIR, loadGovernanceRecords, verifyGovernanceRecord } from "@greenarmor/ges-core";
|
|
5
5
|
import { showNextStepsMenu } from "../utils/next-steps.js";
|
|
6
|
+
import { banner, success, warn, error, blank, progressBar, BOLD, DIM, GREEN, RED, YELLOW } from "../utils/ui.js";
|
|
6
7
|
import * as fs from "node:fs";
|
|
7
8
|
import * as path from "node:path";
|
|
8
9
|
export const doctorCommand = new Command("doctor")
|
|
9
10
|
.description("Diagnose GESF configuration and health")
|
|
10
11
|
.action(async () => {
|
|
11
|
-
|
|
12
|
-
console.log(" ─────────────────────────────\n");
|
|
12
|
+
banner("GESF Doctor", "Diagnostic health check");
|
|
13
13
|
const checks = [];
|
|
14
14
|
const root = findProjectRoot();
|
|
15
15
|
if (root) {
|
|
@@ -51,13 +51,54 @@ export const doctorCommand = new Command("doctor")
|
|
|
51
51
|
checks.push({ name: "Project", status: "OK", detail: `${config.project_name} (${config.project_type})` });
|
|
52
52
|
checks.push({ name: "Frameworks", status: "OK", detail: config.frameworks.join(", ") });
|
|
53
53
|
}
|
|
54
|
+
const govRecords = loadGovernanceRecords(root);
|
|
55
|
+
if (govRecords.length > 0) {
|
|
56
|
+
let approved = 0;
|
|
57
|
+
let blockingIssues = 0;
|
|
58
|
+
let expiredApprovals = 0;
|
|
59
|
+
let missingReviewCycles = 0;
|
|
60
|
+
for (const record of govRecords) {
|
|
61
|
+
const verification = verifyGovernanceRecord(record);
|
|
62
|
+
if (verification.completeness.has_approval && record.approval && record.approval.decision === "approved")
|
|
63
|
+
approved++;
|
|
64
|
+
if (verification.issues.length > 0)
|
|
65
|
+
blockingIssues++;
|
|
66
|
+
if (verification.approval_status === "expired")
|
|
67
|
+
expiredApprovals++;
|
|
68
|
+
if (!record.review_cycle)
|
|
69
|
+
missingReviewCycles++;
|
|
70
|
+
}
|
|
71
|
+
checks.push({
|
|
72
|
+
name: "Governance records",
|
|
73
|
+
status: blockingIssues > 0 || expiredApprovals > 0 ? "WARN" : "OK",
|
|
74
|
+
detail: `${govRecords.length} record(s), ${approved} approved, ${blockingIssues} with blocking issues`,
|
|
75
|
+
});
|
|
76
|
+
if (expiredApprovals > 0) {
|
|
77
|
+
checks.push({ name: "Governance approvals", status: "WARN", detail: `${expiredApprovals} expired approval(s)` });
|
|
78
|
+
}
|
|
79
|
+
if (missingReviewCycles > 0) {
|
|
80
|
+
checks.push({ name: "Governance review cycles", status: "WARN", detail: `${missingReviewCycles} record(s) without review cycle` });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
54
83
|
}
|
|
55
84
|
checks.push({ name: "GESF Version", status: "OK", detail: CLI_VERSION });
|
|
85
|
+
const okCount = checks.filter(c => c.status === "OK").length;
|
|
86
|
+
const warnCount = checks.filter(c => c.status === "WARN" || c.status === "MISSING").length;
|
|
87
|
+
const failCount = checks.filter(c => c.status === "FAIL").length;
|
|
88
|
+
console.log(` ${BOLD("Health Score")} ${progressBar(okCount, checks.length, 24)}`);
|
|
89
|
+
console.log(` ${DIM("Checks")} ${GREEN(`${okCount} ok`)} ${YELLOW(`${warnCount} warn`)} ${RED(`${failCount} fail`)}`);
|
|
90
|
+
blank();
|
|
56
91
|
for (const check of checks) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
92
|
+
if (check.status === "OK") {
|
|
93
|
+
success(check.name, check.detail);
|
|
94
|
+
}
|
|
95
|
+
else if (check.status === "WARN" || check.status === "MISSING") {
|
|
96
|
+
warn(check.name, check.detail);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
error(check.name, check.detail);
|
|
100
|
+
}
|
|
60
101
|
}
|
|
61
|
-
|
|
102
|
+
blank();
|
|
62
103
|
await showNextStepsMenu("doctor");
|
|
63
104
|
});
|