rterm-backend 2.8.8 → 2.9.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/bin/gybackend.cjs +2676 -1386
- package/bin/gybackend.js +2648 -1379
- package/package.json +18 -31
- package/plugins/fraudops/index.d.mts +1 -1
- package/plugins/fraudops/index.mjs +5 -4
- package/plugins/iam-connector/index.d.mts +3 -3
- package/plugins/iam-connector/index.mjs +13 -9
- package/plugins/patch-manager/index.d.mts +2 -2
- package/plugins/patch-manager/index.mjs +3 -3
- package/plugins/request-router/index.d.mts +1 -1
- package/plugins/request-router/index.mjs +20 -16
- package/plugins/sop-assistant/index.mjs +2 -2
package/package.json
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rterm-backend",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "RTerm headless backend — run RTerm-as-a-service (AI agent, SSH/WinRM/Serial/local terminals, fleet orchestration, advanced automation with event-driven triggers + NATS event mesh, scheduled automation, SRE observability, Netdata integration, AWS APerf performance deep-dive, plugin system
|
|
5
|
-
"
|
|
6
|
-
"type": "module",
|
|
3
|
+
"version": "2.9.0",
|
|
4
|
+
"description": "RTerm headless backend — run RTerm-as-a-service (AI agent, SSH/WinRM/Serial/local terminals, fleet orchestration, advanced automation with event-driven triggers + NATS event mesh, scheduled automation, SRE observability, Netdata integration, AWS APerf performance deep-dive, plugin system, governance/audit, Prometheus/OTel metrics export, secrets vault, on-call paging, AI cost budgets, GitOps, cloud inventory).",
|
|
5
|
+
"main": "bin/gybackend.js",
|
|
7
6
|
"bin": {
|
|
8
7
|
"gybackend": "bin/gybackend.cjs"
|
|
9
8
|
},
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"plugins/",
|
|
14
|
-
"README.md",
|
|
15
|
-
"LICENSE.md"
|
|
16
|
-
],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/gybackend.cjs"
|
|
11
|
+
},
|
|
17
12
|
"engines": {
|
|
18
13
|
"node": ">=18"
|
|
19
14
|
},
|
|
@@ -22,17 +17,10 @@
|
|
|
22
17
|
"linux",
|
|
23
18
|
"win32"
|
|
24
19
|
],
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"node-pty": "^1.2.0-beta.3",
|
|
30
|
-
"ssh2": "^1.17.0",
|
|
31
|
-
"tree-sitter-bash": "^0.25.1",
|
|
32
|
-
"web-tree-sitter": "^0.26.3"
|
|
33
|
-
},
|
|
34
|
-
"optionalDependencies": {
|
|
35
|
-
"serialport": "^13.0.0"
|
|
20
|
+
"license": "Apache-2.0",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/DrOlu/RTerm.git"
|
|
36
24
|
},
|
|
37
25
|
"keywords": [
|
|
38
26
|
"rterm",
|
|
@@ -58,13 +46,12 @@
|
|
|
58
46
|
"observability",
|
|
59
47
|
"netdata",
|
|
60
48
|
"aperf",
|
|
61
|
-
"monitoring"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
"bugs": "https://github.com/DrOlu/RTerm/issues"
|
|
49
|
+
"monitoring",
|
|
50
|
+
"prometheus",
|
|
51
|
+
"opentelemetry",
|
|
52
|
+
"secrets",
|
|
53
|
+
"on-call",
|
|
54
|
+
"gitops",
|
|
55
|
+
"cloud-inventory"
|
|
56
|
+
]
|
|
70
57
|
}
|
|
@@ -5,5 +5,5 @@ export function buildNatsStatusCommand(): string
|
|
|
5
5
|
export function buildKafkaLagCommand(): string
|
|
6
6
|
export function parsePipelineHealth(output: string): { status: string; jobs: any[]; running: number; failed: number }
|
|
7
7
|
export function buildStrCase(txnId: string, decision: string, indicators?: string[], assignedTo?: string): any
|
|
8
|
-
export function buildDecisionSummary(decisions: Array<{ decision: string }>): { total: number; blocks: number; reviews: number; approves: number; blockRate: number; reviewRate: number; approveRate: number }
|
|
8
|
+
export function buildDecisionSummary(decisions: Array<{ decision: string }> | null | undefined): { total: number; blocks: number; reviews: number; approves: number; blockRate: number; reviewRate: number; approveRate: number }
|
|
9
9
|
export default any
|
|
@@ -53,10 +53,11 @@ export function buildStrCase(txnId, decision, indicators, assignedTo) {
|
|
|
53
53
|
|
|
54
54
|
// --- Pure: build a decision summary ---
|
|
55
55
|
export function buildDecisionSummary(decisions) {
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
56
|
+
const list = Array.isArray(decisions) ? decisions : []
|
|
57
|
+
const total = list.length
|
|
58
|
+
const blocks = list.filter((d) => d.decision === 'BLOCK').length
|
|
59
|
+
const reviews = list.filter((d) => d.decision === 'REVIEW').length
|
|
60
|
+
const approves = list.filter((d) => d.decision === 'APPROVE').length
|
|
60
61
|
return {
|
|
61
62
|
total,
|
|
62
63
|
blocks,
|
|
@@ -4,7 +4,7 @@ export function buildUserInfoCommand(username: string, os?: string): string
|
|
|
4
4
|
export function buildUserGroupsCommand(username: string, os?: string): string
|
|
5
5
|
export function buildDisableUserCommand(username: string, os?: string): string
|
|
6
6
|
export function buildAccessReviewCommand(os?: string): string
|
|
7
|
-
export function parseUserInfo(output: string, os?: string): { username: string; groups: string[]; enabled: boolean; locked: boolean }
|
|
8
|
-
export function parseAccessReview(output: string, os?: string): Array<{ username: string; groups?: string[]; enabled?: boolean }>
|
|
9
|
-
export function isPrivileged(userInfo: { groups: string[] }, privilegedGroups?: string[]): boolean
|
|
7
|
+
export function parseUserInfo(output: string | null | undefined, os?: string | null | undefined): { username: string; groups: string[]; enabled: boolean; locked: boolean }
|
|
8
|
+
export function parseAccessReview(output: string | null | undefined, os?: string | null | undefined): Array<{ username: string; groups?: string[]; enabled?: boolean }>
|
|
9
|
+
export function isPrivileged(userInfo: { groups: string[] } | null | undefined, privilegedGroups?: string[] | null | undefined): boolean
|
|
10
10
|
export default any
|
|
@@ -46,13 +46,14 @@ export function buildAccessReviewCommand(os = 'linux') {
|
|
|
46
46
|
|
|
47
47
|
// --- Pure: parse user info output ---
|
|
48
48
|
export function parseUserInfo(output, os = 'linux') {
|
|
49
|
-
const o = String(os).toLowerCase()
|
|
49
|
+
const o = String(os ?? 'linux').toLowerCase()
|
|
50
|
+
const text = String(output ?? '')
|
|
50
51
|
const info = { username: '', groups: [], enabled: true, locked: false }
|
|
51
52
|
|
|
52
53
|
if (o === 'windows' || o === 'win32') {
|
|
53
|
-
const nameMatch =
|
|
54
|
-
const enabledMatch =
|
|
55
|
-
const lockedMatch =
|
|
54
|
+
const nameMatch = text.match(/Name\s+:\s+(\S+)/)
|
|
55
|
+
const enabledMatch = text.match(/Enabled\s+:\s+(True|False)/i)
|
|
56
|
+
const lockedMatch = text.match(/LockedOut\s+:\s+(True|False)/i)
|
|
56
57
|
if (nameMatch) info.username = nameMatch[1]
|
|
57
58
|
if (enabledMatch) info.enabled = enabledMatch[1].toLowerCase() === 'true'
|
|
58
59
|
if (lockedMatch) info.locked = lockedMatch[1].toLowerCase() === 'true'
|
|
@@ -60,7 +61,7 @@ export function parseUserInfo(output, os = 'linux') {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
// Linux: parse id + groups + passwd -S output
|
|
63
|
-
const lines =
|
|
64
|
+
const lines = text.split(/\r?\n/)
|
|
64
65
|
for (const line of lines) {
|
|
65
66
|
const l = line.trim()
|
|
66
67
|
const idMatch = l.match(/uid=\d+\((\S+)\)/)
|
|
@@ -80,11 +81,12 @@ export function parseUserInfo(output, os = 'linux') {
|
|
|
80
81
|
|
|
81
82
|
// --- Pure: parse access review output into a user list ---
|
|
82
83
|
export function parseAccessReview(output, os = 'linux') {
|
|
83
|
-
const o = String(os).toLowerCase()
|
|
84
|
+
const o = String(os ?? 'linux').toLowerCase()
|
|
85
|
+
const text = String(output ?? '')
|
|
84
86
|
const users = []
|
|
85
87
|
|
|
86
88
|
if (o === 'windows' || o === 'win32') {
|
|
87
|
-
const lines =
|
|
89
|
+
const lines = text.split(/\r?\n/)
|
|
88
90
|
for (const line of lines) {
|
|
89
91
|
const l = line.trim()
|
|
90
92
|
const match = l.match(/^(\S+)\s+(True|False)\s+/)
|
|
@@ -96,7 +98,7 @@ export function parseAccessReview(output, os = 'linux') {
|
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
// Linux: parse "user: group1 group2" lines
|
|
99
|
-
const lines =
|
|
101
|
+
const lines = text.split(/\r?\n/)
|
|
100
102
|
for (const line of lines) {
|
|
101
103
|
const l = line.trim()
|
|
102
104
|
const match = l.match(/^(\S+):\s*(.*)/)
|
|
@@ -109,7 +111,9 @@ export function parseAccessReview(output, os = 'linux') {
|
|
|
109
111
|
|
|
110
112
|
// --- Pure: check if a user has privileged access ---
|
|
111
113
|
export function isPrivileged(userInfo, privilegedGroups = ['sudo', 'wheel', 'admin', 'root', 'Administrators']) {
|
|
112
|
-
|
|
114
|
+
const groups = Array.isArray(userInfo?.groups) ? userInfo.groups : []
|
|
115
|
+
const privs = Array.isArray(privilegedGroups) ? privilegedGroups : []
|
|
116
|
+
return groups.some((g) => privs.some((pg) => String(g).toLowerCase().includes(String(pg).toLowerCase())))
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
// --- Plugin entry ---
|
|
@@ -4,7 +4,7 @@ export function buildPatchStatusCommand(os: string): string
|
|
|
4
4
|
export function buildPatchApplyCommand(os: string, opts?: { severity?: string; dryRun?: boolean }): string
|
|
5
5
|
export function buildPrePatchCheckCommand(os: string): string
|
|
6
6
|
export function buildPostPatchCheckCommand(os: string): string
|
|
7
|
-
export function parsePatchStatus(output: string, os: string): { patches: Array<{ id: string; title: string; severity: string; os: string }>; summary: { total: number; critical: number; security: number; recommended: number } }
|
|
7
|
+
export function parsePatchStatus(output: string | null | undefined, os: string | null | undefined): { patches: Array<{ id: string; title: string; severity: string; os: string }>; summary: { total: number; critical: number; security: number; recommended: number } }
|
|
8
8
|
export function buildPatchPlan(host: string, os: string, patchStatus: any, opts?: { severity?: string }): any
|
|
9
|
-
export function buildComplianceReport(hostStatuses: Record<string, any>): any
|
|
9
|
+
export function buildComplianceReport(hostStatuses: Record<string, any> | null | undefined): any
|
|
10
10
|
export default any
|
|
@@ -70,8 +70,8 @@ export function buildPostPatchCheckCommand(os) {
|
|
|
70
70
|
|
|
71
71
|
// --- Pure: parse patch status output into structured results ---
|
|
72
72
|
export function parsePatchStatus(output, os) {
|
|
73
|
-
const o = String(os).toLowerCase()
|
|
74
|
-
const lines = output.split(/\r?\n/).filter((l) => l.trim().length > 0)
|
|
73
|
+
const o = String(os ?? '').toLowerCase()
|
|
74
|
+
const lines = String(output ?? '').split(/\r?\n/).filter((l) => l.trim().length > 0)
|
|
75
75
|
const patches = []
|
|
76
76
|
let summary = { total: 0, critical: 0, security: 0, recommended: 0 }
|
|
77
77
|
|
|
@@ -151,7 +151,7 @@ export function buildPatchPlan(host, os, patchStatus, opts = {}) {
|
|
|
151
151
|
|
|
152
152
|
// --- Pure: build a compliance report for a fleet ---
|
|
153
153
|
export function buildComplianceReport(hostStatuses) {
|
|
154
|
-
const hosts = Object.entries(hostStatuses).map(([host, status]) => ({
|
|
154
|
+
const hosts = Object.entries(hostStatuses ?? {}).map(([host, status]) => ({
|
|
155
155
|
host,
|
|
156
156
|
os: status.os ?? 'unknown',
|
|
157
157
|
totalPatches: status.summary?.total ?? 0,
|
|
@@ -5,5 +5,5 @@ export function routeRequest(request: any): { route: string; risk: string; reaso
|
|
|
5
5
|
export function buildRequestId(): string
|
|
6
6
|
export function buildApprovalRecord(requestId: string, approvedBy: string, rationale: string, decision: string): any
|
|
7
7
|
export function buildQueueEntry(request: any, requestId: string): any
|
|
8
|
-
export function filterQueue(queue: any[], filter?: any): any[]
|
|
8
|
+
export function filterQueue(queue: any[] | null | undefined, filter?: any): any[]
|
|
9
9
|
export default any
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
// --- Pure: classify a request by risk ---
|
|
11
11
|
export function classifyRequest(request) {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
12
|
+
const req = request ?? {}
|
|
13
|
+
const type = String(req.type ?? '').toLowerCase()
|
|
14
|
+
const urgency = String(req.urgency ?? 'low').toLowerCase()
|
|
15
|
+
const target = String(req.target ?? '').toLowerCase()
|
|
15
16
|
|
|
16
17
|
// Destructive operations = high risk
|
|
17
18
|
if (['delete', 'drop', 'purge', 'format', 'destroy'].some((w) => type.includes(w))) return 'high'
|
|
@@ -32,8 +33,9 @@ export function classifyRequest(request) {
|
|
|
32
33
|
|
|
33
34
|
// --- Pure: route a request based on risk ---
|
|
34
35
|
export function routeRequest(request) {
|
|
35
|
-
const
|
|
36
|
-
const
|
|
36
|
+
const req = request ?? {}
|
|
37
|
+
const risk = classifyRequest(req)
|
|
38
|
+
const urgency = String(req.urgency ?? 'low').toLowerCase()
|
|
37
39
|
|
|
38
40
|
if (risk === 'low') return { route: 'auto_approve', risk, reason: 'low-risk read-only operation' }
|
|
39
41
|
if (risk === 'medium') {
|
|
@@ -63,18 +65,19 @@ export function buildApprovalRecord(requestId, approvedBy, rationale, decision)
|
|
|
63
65
|
|
|
64
66
|
// --- Pure: build a request queue entry ---
|
|
65
67
|
export function buildQueueEntry(request, requestId) {
|
|
66
|
-
const
|
|
68
|
+
const req = request ?? {}
|
|
69
|
+
const { route, risk, reason } = routeRequest(req)
|
|
67
70
|
return {
|
|
68
71
|
id: requestId,
|
|
69
|
-
type:
|
|
70
|
-
target:
|
|
71
|
-
justification:
|
|
72
|
-
urgency:
|
|
72
|
+
type: req.type,
|
|
73
|
+
target: req.target,
|
|
74
|
+
justification: req.justification ?? '',
|
|
75
|
+
urgency: req.urgency ?? 'low',
|
|
73
76
|
risk,
|
|
74
77
|
route,
|
|
75
78
|
routeReason: reason,
|
|
76
79
|
status: route === 'auto_approve' ? 'auto_approved' : 'pending',
|
|
77
|
-
submittedBy:
|
|
80
|
+
submittedBy: req.submittedBy ?? 'unknown',
|
|
78
81
|
submittedAt: Date.now(),
|
|
79
82
|
...(route === 'auto_approve' ? { approvedAt: Date.now(), approvedBy: 'system' } : {}),
|
|
80
83
|
}
|
|
@@ -82,11 +85,12 @@ export function buildQueueEntry(request, requestId) {
|
|
|
82
85
|
|
|
83
86
|
// --- Pure: filter the request queue ---
|
|
84
87
|
export function filterQueue(queue, filter = {}) {
|
|
85
|
-
let out = [...queue]
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
if (
|
|
89
|
-
if (
|
|
88
|
+
let out = Array.isArray(queue) ? [...queue] : []
|
|
89
|
+
const f = filter ?? {}
|
|
90
|
+
if (f.status) out = out.filter((r) => r.status === f.status)
|
|
91
|
+
if (f.risk) out = out.filter((r) => r.risk === f.risk)
|
|
92
|
+
if (f.urgency) out = out.filter((r) => r.urgency === f.urgency)
|
|
93
|
+
if (f.target) out = out.filter((r) => String(r.target ?? '').includes(f.target))
|
|
90
94
|
return out
|
|
91
95
|
}
|
|
92
96
|
|
|
@@ -214,8 +214,8 @@ export function searchIamPolicies(query, policies = IAM_POLICIES) {
|
|
|
214
214
|
|
|
215
215
|
// --- Pure: build the command for a SOP step (with variable substitution) ---
|
|
216
216
|
export function buildStepCommand(step, vars = {}) {
|
|
217
|
-
let cmd = step
|
|
218
|
-
for (const [key, value] of Object.entries(vars)) {
|
|
217
|
+
let cmd = String(step?.command ?? '')
|
|
218
|
+
for (const [key, value] of Object.entries(vars ?? {})) {
|
|
219
219
|
cmd = cmd.replace(new RegExp(`\\{${key}\\}`, 'g'), String(value))
|
|
220
220
|
}
|
|
221
221
|
return cmd
|