rterm-backend 2.7.6 → 2.7.7
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.js +136 -0
- package/package.json +1 -1
package/bin/gybackend.js
CHANGED
|
@@ -382290,6 +382290,107 @@ var MonitorStatusService = class {
|
|
|
382290
382290
|
}
|
|
382291
382291
|
};
|
|
382292
382292
|
|
|
382293
|
+
// ../../packages/backend/src/services/governance/agtPolicyEngine.ts
|
|
382294
|
+
var AgtPolicyEngine = class {
|
|
382295
|
+
constructor(deps) {
|
|
382296
|
+
this.deps = deps;
|
|
382297
|
+
this.agentIdentity = deps.agentIdentity ?? "rterm-agent";
|
|
382298
|
+
this.sponsoringPrincipal = deps.sponsoringPrincipal ?? "unknown";
|
|
382299
|
+
}
|
|
382300
|
+
policy = null;
|
|
382301
|
+
agentIdentity;
|
|
382302
|
+
sponsoringPrincipal;
|
|
382303
|
+
/** Load the policy document. Must be called before evaluate(). */
|
|
382304
|
+
async load() {
|
|
382305
|
+
this.policy = await this.deps.loadPolicy();
|
|
382306
|
+
}
|
|
382307
|
+
/** Evaluate an action against the policy. Returns the decision. */
|
|
382308
|
+
evaluate(action, target) {
|
|
382309
|
+
if (!this.policy) {
|
|
382310
|
+
throw new Error("Policy not loaded. Call load() first.");
|
|
382311
|
+
}
|
|
382312
|
+
const actionLower = action.toLowerCase();
|
|
382313
|
+
const targetLower = (target ?? "").toLowerCase();
|
|
382314
|
+
for (const rule of this.policy.rules) {
|
|
382315
|
+
const actionMatch = this.matchPattern(actionLower, rule.actionPattern.toLowerCase());
|
|
382316
|
+
const targetMatch = !rule.targetPattern || this.matchPattern(targetLower, rule.targetPattern.toLowerCase());
|
|
382317
|
+
if (actionMatch && targetMatch) {
|
|
382318
|
+
return {
|
|
382319
|
+
decision: rule.decision,
|
|
382320
|
+
rule: rule.name,
|
|
382321
|
+
reason: rule.reason ?? `matched rule '${rule.name}'`,
|
|
382322
|
+
policyVersion: this.policy.version,
|
|
382323
|
+
action,
|
|
382324
|
+
target
|
|
382325
|
+
};
|
|
382326
|
+
}
|
|
382327
|
+
}
|
|
382328
|
+
return {
|
|
382329
|
+
decision: this.policy.defaultDecision,
|
|
382330
|
+
rule: "default",
|
|
382331
|
+
reason: `no rule matched, using default '${this.policy.defaultDecision}'`,
|
|
382332
|
+
policyVersion: this.policy.version,
|
|
382333
|
+
action,
|
|
382334
|
+
target
|
|
382335
|
+
};
|
|
382336
|
+
}
|
|
382337
|
+
/** Get the current policy document. */
|
|
382338
|
+
getPolicy() {
|
|
382339
|
+
return this.policy;
|
|
382340
|
+
}
|
|
382341
|
+
/** Get the agent identity. */
|
|
382342
|
+
getAgentIdentity() {
|
|
382343
|
+
return this.agentIdentity;
|
|
382344
|
+
}
|
|
382345
|
+
/** Get the sponsoring principal. */
|
|
382346
|
+
getSponsoringPrincipal() {
|
|
382347
|
+
return this.sponsoringPrincipal;
|
|
382348
|
+
}
|
|
382349
|
+
/** Simple glob-style pattern matching: * matches any suffix. The value matches
|
|
382350
|
+
* if it equals the pattern or starts with the pattern (for action patterns
|
|
382351
|
+
* like "read" to match "read /etc/passwd"). */
|
|
382352
|
+
matchPattern(value, pattern) {
|
|
382353
|
+
if (pattern === "*") return true;
|
|
382354
|
+
if (pattern.endsWith("*")) {
|
|
382355
|
+
return value.startsWith(pattern.slice(0, -1));
|
|
382356
|
+
}
|
|
382357
|
+
return value === pattern || value.startsWith(pattern + " ");
|
|
382358
|
+
}
|
|
382359
|
+
};
|
|
382360
|
+
function parsePolicyYaml(yaml) {
|
|
382361
|
+
const lines = yaml.split(/\r?\n/);
|
|
382362
|
+
const doc = { name: "", version: "1.0", defaultDecision: "deny", rules: [] };
|
|
382363
|
+
let currentRule = null;
|
|
382364
|
+
for (const line of lines) {
|
|
382365
|
+
const l = line.trim();
|
|
382366
|
+
if (!l || l.startsWith("#")) continue;
|
|
382367
|
+
if (l.startsWith("name:")) {
|
|
382368
|
+
if (currentRule?.name) {
|
|
382369
|
+
doc.rules.push(currentRule);
|
|
382370
|
+
currentRule = null;
|
|
382371
|
+
}
|
|
382372
|
+
doc.name = l.split(":")[1].trim();
|
|
382373
|
+
} else if (l.startsWith("version:")) {
|
|
382374
|
+
doc.version = l.split(":")[1].trim();
|
|
382375
|
+
} else if (l.startsWith("defaultDecision:")) {
|
|
382376
|
+
doc.defaultDecision = l.split(":")[1].trim();
|
|
382377
|
+
} else if (l.startsWith("- name:")) {
|
|
382378
|
+
if (currentRule?.name) doc.rules.push(currentRule);
|
|
382379
|
+
currentRule = { name: l.split(":")[1].trim() };
|
|
382380
|
+
} else if (l.startsWith("actionPattern:") && currentRule) {
|
|
382381
|
+
currentRule.actionPattern = l.split(":")[1].trim();
|
|
382382
|
+
} else if (l.startsWith("targetPattern:") && currentRule) {
|
|
382383
|
+
currentRule.targetPattern = l.split(":")[1].trim();
|
|
382384
|
+
} else if (l.startsWith("decision:") && currentRule) {
|
|
382385
|
+
currentRule.decision = l.split(":")[1].trim();
|
|
382386
|
+
} else if (l.startsWith("reason:") && currentRule) {
|
|
382387
|
+
currentRule.reason = l.split(":").slice(1).join(":").trim();
|
|
382388
|
+
}
|
|
382389
|
+
}
|
|
382390
|
+
if (currentRule?.name) doc.rules.push(currentRule);
|
|
382391
|
+
return doc;
|
|
382392
|
+
}
|
|
382393
|
+
|
|
382293
382394
|
// ../../packages/backend/src/services/observability.ts
|
|
382294
382395
|
function createObservability(deps) {
|
|
382295
382396
|
const log = deps.onLog ?? (() => {
|
|
@@ -382369,6 +382470,40 @@ function createObservability(deps) {
|
|
|
382369
382470
|
const auditLedger = new AuditLedger({});
|
|
382370
382471
|
const evidenceSealer = new EvidenceSealer({});
|
|
382371
382472
|
const monitorStatus = new MonitorStatusService(deps.resourceMonitorService, deps.terminalService);
|
|
382473
|
+
const policyEngine = new AgtPolicyEngine({
|
|
382474
|
+
loadPolicy: async () => {
|
|
382475
|
+
const req = createRequire2(import.meta.url);
|
|
382476
|
+
const fs22 = req("node:fs");
|
|
382477
|
+
const path31 = req("node:path");
|
|
382478
|
+
const policyPath = path31.join(".", "policy.yaml");
|
|
382479
|
+
if (fs22.existsSync(policyPath)) {
|
|
382480
|
+
return parsePolicyYaml(fs22.readFileSync(policyPath, "utf8"));
|
|
382481
|
+
}
|
|
382482
|
+
return {
|
|
382483
|
+
name: "rterm-default",
|
|
382484
|
+
version: "1.0",
|
|
382485
|
+
defaultDecision: "deny",
|
|
382486
|
+
rules: [
|
|
382487
|
+
{ name: "allow-read", actionPattern: "read", decision: "allow" },
|
|
382488
|
+
{ name: "allow-status", actionPattern: "status", decision: "allow" },
|
|
382489
|
+
{ name: "allow-list", actionPattern: "list", decision: "allow" },
|
|
382490
|
+
{ name: "deny-delete", actionPattern: "delete", decision: "deny" },
|
|
382491
|
+
{ name: "deny-drop", actionPattern: "drop", decision: "deny" },
|
|
382492
|
+
{ name: "deny-format", actionPattern: "format", decision: "deny" },
|
|
382493
|
+
{ name: "escalate-restart-prod", actionPattern: "restart", targetPattern: "prod-*", decision: "escalate" },
|
|
382494
|
+
{ name: "escalate-patch-prod", actionPattern: "patch", targetPattern: "prod-*", decision: "escalate" },
|
|
382495
|
+
{ name: "escalate-deploy-prod", actionPattern: "deploy", targetPattern: "prod-*", decision: "escalate" },
|
|
382496
|
+
{ name: "allow-restart", actionPattern: "restart", decision: "allow" },
|
|
382497
|
+
{ name: "allow-patch", actionPattern: "patch", decision: "allow" },
|
|
382498
|
+
{ name: "allow-deploy", actionPattern: "deploy", decision: "allow" }
|
|
382499
|
+
]
|
|
382500
|
+
};
|
|
382501
|
+
},
|
|
382502
|
+
agentIdentity: `rterm-agent-v${process.env.GYBACKEND_VERSION ?? "2.7.7"}`,
|
|
382503
|
+
sponsoringPrincipal: process.env.USER ?? "unknown"
|
|
382504
|
+
});
|
|
382505
|
+
void policyEngine.load().catch(() => {
|
|
382506
|
+
});
|
|
382372
382507
|
const pluginScanRoot = (process.env.GYBACKEND_DATA_DIR ?? "./.gybackend-data") + "/plugins";
|
|
382373
382508
|
const bundlePluginRoot = new URL("../../plugins/", import.meta.url).pathname;
|
|
382374
382509
|
const scanRoots = [pluginScanRoot, "./plugins"];
|
|
@@ -382442,6 +382577,7 @@ function createObservability(deps) {
|
|
|
382442
382577
|
aperf: { service: aperfService, toMetricPoint: aperfSummaryToMetricPoint },
|
|
382443
382578
|
audit: { ledger: auditLedger, sealer: evidenceSealer },
|
|
382444
382579
|
monitorStatus,
|
|
382580
|
+
governance: { policyEngine },
|
|
382445
382581
|
pluginRegistry
|
|
382446
382582
|
};
|
|
382447
382583
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rterm-backend",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.7",
|
|
4
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) and drive it over a WebSocket JSON-RPC gateway. No desktop UI required.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|