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.
Files changed (2) hide show
  1. package/bin/gybackend.js +194 -4
  2. 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 = createHash("sha1").update(key + GUID).digest("base64");
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 = createHash("sha1").update(key + GUID).digest("base64");
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.0",
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",