rterm-backend 2.7.0 → 2.7.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/bin/gybackend.js +194 -4
- package/package.json +1 -1
package/bin/gybackend.js
CHANGED
|
@@ -254727,7 +254727,7 @@ var require_websocket = __commonJS({
|
|
|
254727
254727
|
var http2 = __require("http");
|
|
254728
254728
|
var net2 = __require("net");
|
|
254729
254729
|
var tls = __require("tls");
|
|
254730
|
-
var { randomBytes: randomBytes2, createHash } = __require("crypto");
|
|
254730
|
+
var { randomBytes: randomBytes2, createHash: createHash3 } = __require("crypto");
|
|
254731
254731
|
var { Duplex, Readable } = __require("stream");
|
|
254732
254732
|
var { URL: URL5 } = __require("url");
|
|
254733
254733
|
var PerMessageDeflate = require_permessage_deflate();
|
|
@@ -255387,7 +255387,7 @@ var require_websocket = __commonJS({
|
|
|
255387
255387
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
255388
255388
|
return;
|
|
255389
255389
|
}
|
|
255390
|
-
const digest =
|
|
255390
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
255391
255391
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
255392
255392
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
255393
255393
|
return;
|
|
@@ -255754,7 +255754,7 @@ var require_websocket_server = __commonJS({
|
|
|
255754
255754
|
var EventEmitter4 = __require("events");
|
|
255755
255755
|
var http2 = __require("http");
|
|
255756
255756
|
var { Duplex } = __require("stream");
|
|
255757
|
-
var { createHash } = __require("crypto");
|
|
255757
|
+
var { createHash: createHash3 } = __require("crypto");
|
|
255758
255758
|
var extension = require_extension();
|
|
255759
255759
|
var PerMessageDeflate = require_permessage_deflate();
|
|
255760
255760
|
var subprotocol = require_subprotocol();
|
|
@@ -256055,7 +256055,7 @@ var require_websocket_server = __commonJS({
|
|
|
256055
256055
|
);
|
|
256056
256056
|
}
|
|
256057
256057
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
256058
|
-
const digest =
|
|
256058
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
256059
256059
|
const headers = [
|
|
256060
256060
|
"HTTP/1.1 101 Switching Protocols",
|
|
256061
256061
|
"Upgrade: websocket",
|
|
@@ -382011,6 +382011,193 @@ function aperfSummaryToMetricPoint(result) {
|
|
|
382011
382011
|
};
|
|
382012
382012
|
}
|
|
382013
382013
|
|
|
382014
|
+
// ../../packages/backend/src/services/audit/auditLedger.ts
|
|
382015
|
+
import { createHash, randomUUID as randomUUID21 } from "crypto";
|
|
382016
|
+
var GENESIS_HASH = "0".repeat(64);
|
|
382017
|
+
function computeRecordHash(kind, actor, target, summary, detail, at, seq2) {
|
|
382018
|
+
const payload = JSON.stringify({ kind, actor, target, summary, detail, at, seq: seq2 });
|
|
382019
|
+
return createHash("sha256").update(payload).digest("hex");
|
|
382020
|
+
}
|
|
382021
|
+
var AuditLedger = class _AuditLedger {
|
|
382022
|
+
records = [];
|
|
382023
|
+
hashFn;
|
|
382024
|
+
now;
|
|
382025
|
+
constructor(deps = {}) {
|
|
382026
|
+
this.hashFn = deps.hashFn ?? ((data) => createHash("sha256").update(data).digest("hex"));
|
|
382027
|
+
this.now = deps.now ?? (() => Date.now());
|
|
382028
|
+
}
|
|
382029
|
+
/** Append an event to the audit ledger. Returns the record with hash + chain. */
|
|
382030
|
+
append(event) {
|
|
382031
|
+
const seq2 = this.records.length + 1;
|
|
382032
|
+
const at = event.at ?? this.now();
|
|
382033
|
+
const prevHash = this.records.length > 0 ? this.records[this.records.length - 1].hash : GENESIS_HASH;
|
|
382034
|
+
const contentHash = computeRecordHash(
|
|
382035
|
+
event.kind,
|
|
382036
|
+
event.actor,
|
|
382037
|
+
event.target,
|
|
382038
|
+
event.summary,
|
|
382039
|
+
event.detail,
|
|
382040
|
+
at,
|
|
382041
|
+
seq2
|
|
382042
|
+
);
|
|
382043
|
+
const hash2 = createHash("sha256").update(contentHash + prevHash).digest("hex");
|
|
382044
|
+
const record2 = {
|
|
382045
|
+
id: `audit-${randomUUID21().slice(0, 12)}`,
|
|
382046
|
+
kind: event.kind,
|
|
382047
|
+
actor: event.actor,
|
|
382048
|
+
target: event.target,
|
|
382049
|
+
summary: event.summary,
|
|
382050
|
+
...event.detail ? { detail: event.detail } : {},
|
|
382051
|
+
at,
|
|
382052
|
+
seq: seq2,
|
|
382053
|
+
hash: hash2,
|
|
382054
|
+
prevHash
|
|
382055
|
+
};
|
|
382056
|
+
this.records.push(record2);
|
|
382057
|
+
return record2;
|
|
382058
|
+
}
|
|
382059
|
+
/** Get all records (ordered by seq). */
|
|
382060
|
+
list() {
|
|
382061
|
+
return [...this.records];
|
|
382062
|
+
}
|
|
382063
|
+
/** Get records by kind. */
|
|
382064
|
+
listByKind(kind) {
|
|
382065
|
+
return this.records.filter((r) => r.kind === kind);
|
|
382066
|
+
}
|
|
382067
|
+
/** Get records for a specific target. */
|
|
382068
|
+
listByTarget(target) {
|
|
382069
|
+
return this.records.filter((r) => r.target === target);
|
|
382070
|
+
}
|
|
382071
|
+
/** Get records for a specific actor. */
|
|
382072
|
+
listByActor(actor) {
|
|
382073
|
+
return this.records.filter((r) => r.actor === actor);
|
|
382074
|
+
}
|
|
382075
|
+
/** Get records in a time range. */
|
|
382076
|
+
listInRange(fromAt, toAt) {
|
|
382077
|
+
return this.records.filter((r) => r.at >= fromAt && r.at <= toAt);
|
|
382078
|
+
}
|
|
382079
|
+
/** Verify the hash chain integrity. Returns { valid: true } or { valid: false, brokenAt: seq }. */
|
|
382080
|
+
verify() {
|
|
382081
|
+
if (this.records.length === 0) return { valid: true };
|
|
382082
|
+
for (let i = 0; i < this.records.length; i++) {
|
|
382083
|
+
const record2 = this.records[i];
|
|
382084
|
+
if (record2.seq !== i + 1) {
|
|
382085
|
+
return { valid: false, brokenAt: record2.seq, detail: `seq mismatch: expected ${i + 1}, got ${record2.seq}` };
|
|
382086
|
+
}
|
|
382087
|
+
const expectedContentHash = computeRecordHash(
|
|
382088
|
+
record2.kind,
|
|
382089
|
+
record2.actor,
|
|
382090
|
+
record2.target,
|
|
382091
|
+
record2.summary,
|
|
382092
|
+
record2.detail,
|
|
382093
|
+
record2.at,
|
|
382094
|
+
record2.seq
|
|
382095
|
+
);
|
|
382096
|
+
const expectedHash = createHash("sha256").update(expectedContentHash + record2.prevHash).digest("hex");
|
|
382097
|
+
if (record2.hash !== expectedHash) {
|
|
382098
|
+
return { valid: false, brokenAt: record2.seq, detail: `hash mismatch at seq ${record2.seq}` };
|
|
382099
|
+
}
|
|
382100
|
+
const expectedPrevHash = i === 0 ? GENESIS_HASH : this.records[i - 1].hash;
|
|
382101
|
+
if (record2.prevHash !== expectedPrevHash) {
|
|
382102
|
+
return { valid: false, brokenAt: record2.seq, detail: `prevHash mismatch at seq ${record2.seq}` };
|
|
382103
|
+
}
|
|
382104
|
+
}
|
|
382105
|
+
return { valid: true };
|
|
382106
|
+
}
|
|
382107
|
+
/** Get the current chain tip (the latest record's hash). */
|
|
382108
|
+
tip() {
|
|
382109
|
+
if (this.records.length === 0) return GENESIS_HASH;
|
|
382110
|
+
return this.records[this.records.length - 1].hash;
|
|
382111
|
+
}
|
|
382112
|
+
/** Get the total number of records. */
|
|
382113
|
+
size() {
|
|
382114
|
+
return this.records.length;
|
|
382115
|
+
}
|
|
382116
|
+
/** Export all records as JSON (for persistence). */
|
|
382117
|
+
export() {
|
|
382118
|
+
return JSON.stringify(this.records, null, 2);
|
|
382119
|
+
}
|
|
382120
|
+
/** Import records from JSON (for recovery). Verifies the chain on import. */
|
|
382121
|
+
import(json3) {
|
|
382122
|
+
const parsed = JSON.parse(json3);
|
|
382123
|
+
if (!Array.isArray(parsed)) {
|
|
382124
|
+
return { imported: 0, valid: false, detail: "not an array" };
|
|
382125
|
+
}
|
|
382126
|
+
const tempLedger = new _AuditLedger({ hashFn: this.hashFn, now: this.now });
|
|
382127
|
+
tempLedger.records = parsed;
|
|
382128
|
+
const result = tempLedger.verify();
|
|
382129
|
+
if (!result.valid) {
|
|
382130
|
+
return { imported: 0, valid: false, detail: result.detail };
|
|
382131
|
+
}
|
|
382132
|
+
this.records = parsed;
|
|
382133
|
+
return { imported: parsed.length, valid: true };
|
|
382134
|
+
}
|
|
382135
|
+
};
|
|
382136
|
+
|
|
382137
|
+
// ../../packages/backend/src/services/audit/evidenceSealer.ts
|
|
382138
|
+
import { createHash as createHash2 } from "crypto";
|
|
382139
|
+
function computeMerkleRoot(hashes) {
|
|
382140
|
+
if (hashes.length === 0) return "0".repeat(64);
|
|
382141
|
+
if (hashes.length === 1) return hashes[0];
|
|
382142
|
+
const nextLevel = [];
|
|
382143
|
+
for (let i = 0; i < hashes.length; i += 2) {
|
|
382144
|
+
const left = hashes[i];
|
|
382145
|
+
const right = i + 1 < hashes.length ? hashes[i + 1] : left;
|
|
382146
|
+
nextLevel.push(createHash2("sha256").update(left + right).digest("hex"));
|
|
382147
|
+
}
|
|
382148
|
+
return computeMerkleRoot(nextLevel);
|
|
382149
|
+
}
|
|
382150
|
+
function verifyMerkleRoot(hashes, expectedRoot) {
|
|
382151
|
+
return computeMerkleRoot(hashes) === expectedRoot;
|
|
382152
|
+
}
|
|
382153
|
+
var EvidenceSealer = class {
|
|
382154
|
+
bundles = [];
|
|
382155
|
+
now;
|
|
382156
|
+
constructor(deps = {}) {
|
|
382157
|
+
this.now = deps.now ?? (() => Date.now());
|
|
382158
|
+
}
|
|
382159
|
+
/** Seal the current state of the audit ledger into an evidence bundle. */
|
|
382160
|
+
seal(ledger, sealId) {
|
|
382161
|
+
const records = ledger.list();
|
|
382162
|
+
const recordHashes = records.map((r) => r.hash);
|
|
382163
|
+
const merkleRoot = computeMerkleRoot(recordHashes);
|
|
382164
|
+
const bundle = {
|
|
382165
|
+
merkleRoot,
|
|
382166
|
+
recordCount: records.length,
|
|
382167
|
+
fromAt: records.length > 0 ? records[0].at : 0,
|
|
382168
|
+
toAt: records.length > 0 ? records[records.length - 1].at : 0,
|
|
382169
|
+
tipHash: ledger.tip(),
|
|
382170
|
+
recordHashes,
|
|
382171
|
+
sealedAt: this.now(),
|
|
382172
|
+
sealId: sealId ?? `seal-${Date.now().toString(36)}`
|
|
382173
|
+
};
|
|
382174
|
+
this.bundles.push(bundle);
|
|
382175
|
+
return bundle;
|
|
382176
|
+
}
|
|
382177
|
+
/** Verify a sealed bundle against a set of records. */
|
|
382178
|
+
verify(bundle, records) {
|
|
382179
|
+
if (records.length !== bundle.recordCount) {
|
|
382180
|
+
return { valid: false, detail: `record count mismatch: expected ${bundle.recordCount}, got ${records.length}` };
|
|
382181
|
+
}
|
|
382182
|
+
if (records.length > 0 && records[records.length - 1].hash !== bundle.tipHash) {
|
|
382183
|
+
return { valid: false, detail: `tip hash mismatch` };
|
|
382184
|
+
}
|
|
382185
|
+
const hashes = records.map((r) => r.hash);
|
|
382186
|
+
if (!verifyMerkleRoot(hashes, bundle.merkleRoot)) {
|
|
382187
|
+
return { valid: false, detail: `Merkle root mismatch` };
|
|
382188
|
+
}
|
|
382189
|
+
return { valid: true };
|
|
382190
|
+
}
|
|
382191
|
+
/** Get all sealed bundles. */
|
|
382192
|
+
listBundles() {
|
|
382193
|
+
return [...this.bundles];
|
|
382194
|
+
}
|
|
382195
|
+
/** Get the latest sealed bundle. */
|
|
382196
|
+
latest() {
|
|
382197
|
+
return this.bundles.length > 0 ? this.bundles[this.bundles.length - 1] : void 0;
|
|
382198
|
+
}
|
|
382199
|
+
};
|
|
382200
|
+
|
|
382014
382201
|
// ../../packages/backend/src/services/observability.ts
|
|
382015
382202
|
function createObservability(deps) {
|
|
382016
382203
|
const log = deps.onLog ?? (() => {
|
|
@@ -382087,6 +382274,8 @@ function createObservability(deps) {
|
|
|
382087
382274
|
const aperfService = new AperfService({
|
|
382088
382275
|
execSsh: async () => ""
|
|
382089
382276
|
});
|
|
382277
|
+
const auditLedger = new AuditLedger({});
|
|
382278
|
+
const evidenceSealer = new EvidenceSealer({});
|
|
382090
382279
|
const pluginScanRoot = (process.env.GYBACKEND_DATA_DIR ?? "./.gybackend-data") + "/plugins";
|
|
382091
382280
|
const pluginRegistry = new PluginRegistry({
|
|
382092
382281
|
scanRoots: [pluginScanRoot, "./plugins"],
|
|
@@ -382139,6 +382328,7 @@ function createObservability(deps) {
|
|
|
382139
382328
|
dagu: { parseDaguYaml, parseDaguWorkflow, daguExecutionPlan },
|
|
382140
382329
|
notify: { slackChannel, teamsChannel, smtpChannel, telegramChannel },
|
|
382141
382330
|
aperf: { service: aperfService, toMetricPoint: aperfSummaryToMetricPoint },
|
|
382331
|
+
audit: { ledger: auditLedger, sealer: evidenceSealer },
|
|
382142
382332
|
pluginRegistry
|
|
382143
382333
|
};
|
|
382144
382334
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rterm-backend",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.1",
|
|
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",
|