@i4ctime/q-ring 0.14.1 → 0.16.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/README.md +114 -2
- package/dist/{chunk-C2TFJ2EH.js → chunk-5LFKBZ3Q.js} +383 -125
- package/dist/chunk-5LFKBZ3Q.js.map +1 -0
- package/dist/{chunk-NNIEXAW5.js → chunk-NCM5GHNW.js} +380 -122
- package/dist/chunk-NCM5GHNW.js.map +1 -0
- package/dist/{dashboard-KAUEPLXO.js → dashboard-A3GJQCJX.js} +2 -2
- package/dist/{dashboard-PYYAED45.js → dashboard-R3FWTFFW.js} +2 -2
- package/dist/index.js +930 -19
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +110 -8
- package/dist/mcp.js.map +1 -1
- package/package.json +6 -4
- package/dist/chunk-C2TFJ2EH.js.map +0 -1
- package/dist/chunk-NNIEXAW5.js.map +0 -1
- /package/dist/{dashboard-KAUEPLXO.js.map → dashboard-A3GJQCJX.js.map} +0 -0
- /package/dist/{dashboard-PYYAED45.js.map → dashboard-R3FWTFFW.js.map} +0 -0
|
@@ -40,7 +40,9 @@ var SecretMetadataSchema = z.object({
|
|
|
40
40
|
validationUrl: z.string().optional(),
|
|
41
41
|
requiresApproval: z.boolean().optional(),
|
|
42
42
|
jitProvider: z.string().optional(),
|
|
43
|
-
jitExpiresAt: z.string().optional()
|
|
43
|
+
jitExpiresAt: z.string().optional(),
|
|
44
|
+
canary: z.boolean().optional(),
|
|
45
|
+
canaryFormat: z.string().optional()
|
|
44
46
|
});
|
|
45
47
|
var QuantumEnvelopeSchema = z.object({
|
|
46
48
|
v: z.literal(1),
|
|
@@ -73,7 +75,9 @@ function createEnvelope(value, opts) {
|
|
|
73
75
|
rotationPrefix: opts?.rotationPrefix,
|
|
74
76
|
provider: opts?.provider,
|
|
75
77
|
requiresApproval: opts?.requiresApproval,
|
|
76
|
-
jitProvider: opts?.jitProvider
|
|
78
|
+
jitProvider: opts?.jitProvider,
|
|
79
|
+
canary: opts?.canary,
|
|
80
|
+
canaryFormat: opts?.canaryFormat
|
|
77
81
|
}
|
|
78
82
|
};
|
|
79
83
|
}
|
|
@@ -287,21 +291,27 @@ function mapEnvName(raw) {
|
|
|
287
291
|
|
|
288
292
|
// src/core/observer.ts
|
|
289
293
|
import {
|
|
290
|
-
existsSync,
|
|
291
|
-
mkdirSync as
|
|
294
|
+
existsSync as existsSync2,
|
|
295
|
+
mkdirSync as mkdirSync3,
|
|
292
296
|
appendFileSync,
|
|
293
|
-
chmodSync,
|
|
294
|
-
readFileSync as
|
|
297
|
+
chmodSync as chmodSync2,
|
|
298
|
+
readFileSync as readFileSync5,
|
|
295
299
|
openSync,
|
|
296
300
|
fstatSync,
|
|
297
301
|
readSync,
|
|
298
302
|
closeSync,
|
|
299
303
|
statSync as statSync3
|
|
300
304
|
} from "fs";
|
|
301
|
-
import { join as
|
|
305
|
+
import { join as join5 } from "path";
|
|
306
|
+
import { homedir as homedir3 } from "os";
|
|
307
|
+
import { createHash, createHmac, randomBytes as randomBytes2 } from "crypto";
|
|
308
|
+
|
|
309
|
+
// src/core/backend.ts
|
|
310
|
+
import { Entry as NapiEntry, findCredentials as napiFindCredentials } from "@napi-rs/keyring";
|
|
311
|
+
import { existsSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, chmodSync } from "fs";
|
|
312
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
302
313
|
import { homedir as homedir2 } from "os";
|
|
303
|
-
import {
|
|
304
|
-
import { Entry } from "@napi-rs/keyring";
|
|
314
|
+
import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync } from "crypto";
|
|
305
315
|
|
|
306
316
|
// src/utils/file-lock.ts
|
|
307
317
|
import {
|
|
@@ -332,17 +342,11 @@ function withFileLock(name, fn, opts = {}) {
|
|
|
332
342
|
const deadline = Date.now() + (opts.timeoutMs ?? 8e3);
|
|
333
343
|
const staleMs = opts.staleMs ?? 3e4;
|
|
334
344
|
while (Date.now() < deadline) {
|
|
345
|
+
let acquired = false;
|
|
335
346
|
try {
|
|
336
347
|
writeFileSync(lockPath, `${process.pid}
|
|
337
348
|
`, { flag: "wx", mode: 384 });
|
|
338
|
-
|
|
339
|
-
return fn();
|
|
340
|
-
} finally {
|
|
341
|
-
try {
|
|
342
|
-
unlinkSync(lockPath);
|
|
343
|
-
} catch {
|
|
344
|
-
}
|
|
345
|
-
}
|
|
349
|
+
acquired = true;
|
|
346
350
|
} catch {
|
|
347
351
|
try {
|
|
348
352
|
const holderPid = parseInt(readFileSync3(lockPath, "utf8").trim(), 10);
|
|
@@ -356,10 +360,172 @@ function withFileLock(name, fn, opts = {}) {
|
|
|
356
360
|
}
|
|
357
361
|
sleepSync(15);
|
|
358
362
|
}
|
|
363
|
+
if (acquired) {
|
|
364
|
+
try {
|
|
365
|
+
return fn();
|
|
366
|
+
} finally {
|
|
367
|
+
try {
|
|
368
|
+
unlinkSync(lockPath);
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
359
373
|
}
|
|
360
374
|
throw new Error(`Could not acquire lock "${name}" (timeout)`);
|
|
361
375
|
}
|
|
362
376
|
|
|
377
|
+
// src/core/backend.ts
|
|
378
|
+
var PASSPHRASE_ENV = "QRING_FILE_PASSPHRASE";
|
|
379
|
+
var BACKEND_ENV = "QRING_BACKEND";
|
|
380
|
+
var PATH_ENV = "QRING_FILE_BACKEND_PATH";
|
|
381
|
+
var PBKDF2_ITERATIONS = 21e4;
|
|
382
|
+
var KEY_LENGTH = 32;
|
|
383
|
+
var FILE_PREFIX = "qfile1";
|
|
384
|
+
var BackendUnavailableError = class extends Error {
|
|
385
|
+
constructor(message) {
|
|
386
|
+
super(message);
|
|
387
|
+
this.name = "BackendUnavailableError";
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
function activeBackend() {
|
|
391
|
+
const requested = process.env[BACKEND_ENV];
|
|
392
|
+
if (requested === "file") return "file";
|
|
393
|
+
if (requested && requested !== "keyring") {
|
|
394
|
+
throw new BackendUnavailableError(
|
|
395
|
+
`Unknown ${BACKEND_ENV} "${requested}" \u2014 expected "keyring" or "file".`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
return "keyring";
|
|
399
|
+
}
|
|
400
|
+
function storePath() {
|
|
401
|
+
return process.env[PATH_ENV] ?? join4(homedir2(), ".config", "q-ring", "file-backend.enc");
|
|
402
|
+
}
|
|
403
|
+
function passphrase() {
|
|
404
|
+
const p = process.env[PASSPHRASE_ENV];
|
|
405
|
+
if (!p) {
|
|
406
|
+
throw new BackendUnavailableError(
|
|
407
|
+
`${BACKEND_ENV}=file requires ${PASSPHRASE_ENV} to be set. q-ring refuses to store secrets under a machine-derivable key \u2014 set a strong passphrase, or use the OS keyring backend.`
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
return p;
|
|
411
|
+
}
|
|
412
|
+
var keyCache = null;
|
|
413
|
+
function deriveKey(saltB64) {
|
|
414
|
+
const pass = passphrase();
|
|
415
|
+
if (keyCache && keyCache.salt === saltB64 && keyCache.pass === pass) {
|
|
416
|
+
return keyCache.key;
|
|
417
|
+
}
|
|
418
|
+
const key = pbkdf2Sync(pass, Buffer.from(saltB64, "base64"), PBKDF2_ITERATIONS, KEY_LENGTH, "sha512");
|
|
419
|
+
keyCache = { salt: saltB64, pass, key };
|
|
420
|
+
return key;
|
|
421
|
+
}
|
|
422
|
+
function loadStore() {
|
|
423
|
+
const path = storePath();
|
|
424
|
+
if (!existsSync(path)) {
|
|
425
|
+
return { map: {}, salt: randomBytes(16).toString("base64") };
|
|
426
|
+
}
|
|
427
|
+
const blob = readFileSync4(path, "utf8").trim();
|
|
428
|
+
const parts = blob.split(":");
|
|
429
|
+
if (parts.length !== 5 || parts[0] !== FILE_PREFIX) {
|
|
430
|
+
throw new BackendUnavailableError(
|
|
431
|
+
`${path} is not a valid q-ring file-backend store (expected ${FILE_PREFIX}:...).`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
const [, salt, ivB64, tagB64, ctB64] = parts;
|
|
435
|
+
const decipher = createDecipheriv("aes-256-gcm", deriveKey(salt), Buffer.from(ivB64, "base64"));
|
|
436
|
+
decipher.setAuthTag(Buffer.from(tagB64, "base64"));
|
|
437
|
+
let plaintext;
|
|
438
|
+
try {
|
|
439
|
+
plaintext = decipher.update(Buffer.from(ctB64, "base64")) + decipher.final("utf8");
|
|
440
|
+
} catch {
|
|
441
|
+
throw new BackendUnavailableError(
|
|
442
|
+
`Cannot decrypt ${path} \u2014 wrong ${PASSPHRASE_ENV}, or the store was tampered with.`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
return { map: JSON.parse(plaintext), salt };
|
|
446
|
+
}
|
|
447
|
+
function saveStore(map, salt) {
|
|
448
|
+
const path = storePath();
|
|
449
|
+
mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
|
|
450
|
+
const iv = randomBytes(12);
|
|
451
|
+
const cipher = createCipheriv("aes-256-gcm", deriveKey(salt), iv);
|
|
452
|
+
const ct = Buffer.concat([cipher.update(JSON.stringify(map), "utf8"), cipher.final()]);
|
|
453
|
+
const blob = [
|
|
454
|
+
FILE_PREFIX,
|
|
455
|
+
salt,
|
|
456
|
+
iv.toString("base64"),
|
|
457
|
+
cipher.getAuthTag().toString("base64"),
|
|
458
|
+
ct.toString("base64")
|
|
459
|
+
].join(":");
|
|
460
|
+
writeFileSync2(path, blob + "\n", { mode: 384 });
|
|
461
|
+
try {
|
|
462
|
+
chmodSync(path, 384);
|
|
463
|
+
} catch {
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
var SEP = "\0";
|
|
467
|
+
function fileMutate(fn) {
|
|
468
|
+
return withFileLock("file-backend", () => {
|
|
469
|
+
const { map, salt } = loadStore();
|
|
470
|
+
const result = fn(map);
|
|
471
|
+
saveStore(map, salt);
|
|
472
|
+
return result;
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
var Entry = class {
|
|
476
|
+
constructor(service, account) {
|
|
477
|
+
this.service = service;
|
|
478
|
+
this.account = account;
|
|
479
|
+
}
|
|
480
|
+
service;
|
|
481
|
+
account;
|
|
482
|
+
napi;
|
|
483
|
+
delegate() {
|
|
484
|
+
this.napi ??= new NapiEntry(this.service, this.account);
|
|
485
|
+
return this.napi;
|
|
486
|
+
}
|
|
487
|
+
storeKey() {
|
|
488
|
+
return `${this.service}${SEP}${this.account}`;
|
|
489
|
+
}
|
|
490
|
+
getPassword() {
|
|
491
|
+
if (activeBackend() === "file") {
|
|
492
|
+
return loadStore().map[this.storeKey()] ?? null;
|
|
493
|
+
}
|
|
494
|
+
return this.delegate().getPassword();
|
|
495
|
+
}
|
|
496
|
+
setPassword(password) {
|
|
497
|
+
if (activeBackend() === "file") {
|
|
498
|
+
fileMutate((map) => {
|
|
499
|
+
map[this.storeKey()] = password;
|
|
500
|
+
});
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
this.delegate().setPassword(password);
|
|
504
|
+
}
|
|
505
|
+
deleteCredential() {
|
|
506
|
+
if (activeBackend() === "file") {
|
|
507
|
+
return fileMutate((map) => {
|
|
508
|
+
const existed = this.storeKey() in map;
|
|
509
|
+
delete map[this.storeKey()];
|
|
510
|
+
return existed;
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
return this.delegate().deleteCredential();
|
|
514
|
+
}
|
|
515
|
+
// @napi-rs/keyring's other delete alias, used by `qring doctor`.
|
|
516
|
+
deletePassword() {
|
|
517
|
+
return this.deleteCredential();
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
function findCredentials(service) {
|
|
521
|
+
if (activeBackend() === "file") {
|
|
522
|
+
const { map } = loadStore();
|
|
523
|
+
const prefix = `${service}${SEP}`;
|
|
524
|
+
return Object.entries(map).filter(([k]) => k.startsWith(prefix)).map(([k, password]) => ({ account: k.slice(prefix.length), password }));
|
|
525
|
+
}
|
|
526
|
+
return napiFindCredentials(service);
|
|
527
|
+
}
|
|
528
|
+
|
|
363
529
|
// src/core/observer.ts
|
|
364
530
|
var AUDIT_KEYRING_SERVICE = "qring-audit-chain";
|
|
365
531
|
var AUDIT_KEY_ACCOUNT = "hmac-key";
|
|
@@ -370,7 +536,7 @@ function getAuditKey() {
|
|
|
370
536
|
const entry = new Entry(AUDIT_KEYRING_SERVICE, AUDIT_KEY_ACCOUNT);
|
|
371
537
|
const stored = entry.getPassword();
|
|
372
538
|
if (stored) return Buffer.from(stored, "base64");
|
|
373
|
-
const key =
|
|
539
|
+
const key = randomBytes2(32);
|
|
374
540
|
entry.setPassword(key.toString("base64"));
|
|
375
541
|
return key;
|
|
376
542
|
} catch {
|
|
@@ -399,25 +565,37 @@ function writeStoredAnchor(hash) {
|
|
|
399
565
|
} catch {
|
|
400
566
|
}
|
|
401
567
|
}
|
|
568
|
+
var auditAgentLabel = null;
|
|
569
|
+
function setAuditAgentLabel(label) {
|
|
570
|
+
if (label === null) {
|
|
571
|
+
auditAgentLabel = null;
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const cleaned = label.replace(/[^\x20-\x7e]/g, "").trim().slice(0, 128);
|
|
575
|
+
auditAgentLabel = cleaned.length > 0 ? cleaned : null;
|
|
576
|
+
}
|
|
577
|
+
function getAuditAgentLabel() {
|
|
578
|
+
return auditAgentLabel;
|
|
579
|
+
}
|
|
402
580
|
function getAuditDir() {
|
|
403
581
|
if (process.env.QRING_AUDIT_DIR) {
|
|
404
|
-
if (!
|
|
405
|
-
|
|
582
|
+
if (!existsSync2(process.env.QRING_AUDIT_DIR)) {
|
|
583
|
+
mkdirSync3(process.env.QRING_AUDIT_DIR, { recursive: true, mode: 448 });
|
|
406
584
|
}
|
|
407
585
|
return process.env.QRING_AUDIT_DIR;
|
|
408
586
|
}
|
|
409
|
-
const dir =
|
|
410
|
-
if (!
|
|
411
|
-
|
|
587
|
+
const dir = join5(homedir3(), ".config", "q-ring");
|
|
588
|
+
if (!existsSync2(dir)) {
|
|
589
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
412
590
|
}
|
|
413
591
|
return dir;
|
|
414
592
|
}
|
|
415
593
|
function getAuditPath() {
|
|
416
|
-
return
|
|
594
|
+
return join5(getAuditDir(), "audit.jsonl");
|
|
417
595
|
}
|
|
418
596
|
function getLastLineHash() {
|
|
419
597
|
const path = getAuditPath();
|
|
420
|
-
if (!
|
|
598
|
+
if (!existsSync2(path)) return void 0;
|
|
421
599
|
try {
|
|
422
600
|
const fd = openSync(path, "r");
|
|
423
601
|
const stat = fstatSync(fd);
|
|
@@ -450,11 +628,14 @@ function logAudit(event) {
|
|
|
450
628
|
pid: process.pid,
|
|
451
629
|
prevHash
|
|
452
630
|
};
|
|
631
|
+
if (full.agent === void 0 && auditAgentLabel) {
|
|
632
|
+
full.agent = auditAgentLabel;
|
|
633
|
+
}
|
|
453
634
|
const line = JSON.stringify(full);
|
|
454
635
|
const path = getAuditPath();
|
|
455
636
|
appendFileSync(path, line + "\n", { mode: 384 });
|
|
456
637
|
try {
|
|
457
|
-
|
|
638
|
+
chmodSync2(path, 384);
|
|
458
639
|
} catch {
|
|
459
640
|
}
|
|
460
641
|
const key = getAuditKey();
|
|
@@ -468,7 +649,7 @@ function logAudit(event) {
|
|
|
468
649
|
var MAX_AUDIT_BYTES = 12 * 1024 * 1024;
|
|
469
650
|
function queryAudit(query = {}) {
|
|
470
651
|
const path = getAuditPath();
|
|
471
|
-
if (!
|
|
652
|
+
if (!existsSync2(path)) return [];
|
|
472
653
|
try {
|
|
473
654
|
const st = statSync3(path);
|
|
474
655
|
const readStart = st.size > MAX_AUDIT_BYTES ? st.size - MAX_AUDIT_BYTES : 0;
|
|
@@ -494,6 +675,7 @@ function queryAudit(query = {}) {
|
|
|
494
675
|
if (query.action) events = events.filter((e) => e.action === query.action);
|
|
495
676
|
if (query.source) events = events.filter((e) => e.source === query.source);
|
|
496
677
|
if (query.correlationId) events = events.filter((e) => e.correlationId === query.correlationId);
|
|
678
|
+
if (query.agent) events = events.filter((e) => e.agent === query.agent);
|
|
497
679
|
if (query.since) {
|
|
498
680
|
const since = new Date(query.since).getTime();
|
|
499
681
|
events = events.filter((e) => new Date(e.timestamp).getTime() >= since);
|
|
@@ -509,10 +691,10 @@ function queryAudit(query = {}) {
|
|
|
509
691
|
}
|
|
510
692
|
function verifyAuditChain() {
|
|
511
693
|
const path = getAuditPath();
|
|
512
|
-
if (!
|
|
694
|
+
if (!existsSync2(path)) {
|
|
513
695
|
return { totalEvents: 0, validEvents: 0, intact: true };
|
|
514
696
|
}
|
|
515
|
-
const lines =
|
|
697
|
+
const lines = readFileSync5(path, "utf8").split("\n").filter((l) => l.trim());
|
|
516
698
|
if (lines.length === 0) {
|
|
517
699
|
return { totalEvents: 0, validEvents: 0, intact: true };
|
|
518
700
|
}
|
|
@@ -563,8 +745,8 @@ function verifyAuditChain() {
|
|
|
563
745
|
}
|
|
564
746
|
function exportAudit(opts = {}) {
|
|
565
747
|
const path = getAuditPath();
|
|
566
|
-
if (!
|
|
567
|
-
const lines =
|
|
748
|
+
if (!existsSync2(path)) return opts.format === "json" ? "[]" : "";
|
|
749
|
+
const lines = readFileSync5(path, "utf8").split("\n").filter((l) => l.trim());
|
|
568
750
|
let events = lines.map((l) => {
|
|
569
751
|
try {
|
|
570
752
|
return JSON.parse(l);
|
|
@@ -584,9 +766,9 @@ function exportAudit(opts = {}) {
|
|
|
584
766
|
return JSON.stringify(events, null, 2);
|
|
585
767
|
}
|
|
586
768
|
if (opts.format === "csv") {
|
|
587
|
-
const header = "timestamp,action,key,scope,env,source,pid,correlationId,detail";
|
|
769
|
+
const header = "timestamp,action,key,scope,env,source,agent,pid,correlationId,detail";
|
|
588
770
|
const rows = events.map(
|
|
589
|
-
(e) => `${e.timestamp},${e.action},${e.key ?? ""},${e.scope ?? ""},${e.env ?? ""},${e.source},${e.pid},${e.correlationId ?? ""},${(e.detail ?? "").replace(/,/g, ";")}`
|
|
771
|
+
(e) => `${e.timestamp},${e.action},${e.key ?? ""},${e.scope ?? ""},${e.env ?? ""},${e.source},${(e.agent ?? "").replace(/,/g, ";")},${e.pid},${e.correlationId ?? ""},${(e.detail ?? "").replace(/,/g, ";")}`
|
|
590
772
|
);
|
|
591
773
|
return [header, ...rows].join("\n");
|
|
592
774
|
}
|
|
@@ -629,15 +811,15 @@ function detectAnomalies(key) {
|
|
|
629
811
|
}
|
|
630
812
|
|
|
631
813
|
// src/core/entanglement.ts
|
|
632
|
-
import { existsSync as
|
|
633
|
-
import { join as
|
|
634
|
-
import { homedir as
|
|
814
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
815
|
+
import { join as join6 } from "path";
|
|
816
|
+
import { homedir as homedir4 } from "os";
|
|
635
817
|
|
|
636
818
|
// src/utils/registry.ts
|
|
637
|
-
import { existsSync as
|
|
819
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6, renameSync } from "fs";
|
|
638
820
|
function loadJsonRegistry(path, empty) {
|
|
639
|
-
if (!
|
|
640
|
-
const raw =
|
|
821
|
+
if (!existsSync3(path)) return empty;
|
|
822
|
+
const raw = readFileSync6(path, "utf8");
|
|
641
823
|
try {
|
|
642
824
|
return JSON.parse(raw);
|
|
643
825
|
} catch (err) {
|
|
@@ -661,18 +843,18 @@ function loadJsonRegistry(path, empty) {
|
|
|
661
843
|
// src/core/entanglement.ts
|
|
662
844
|
var REGISTRY_VERSION = 1;
|
|
663
845
|
function getRegistryPath() {
|
|
664
|
-
const dir =
|
|
665
|
-
if (!
|
|
666
|
-
|
|
846
|
+
const dir = join6(homedir4(), ".config", "q-ring");
|
|
847
|
+
if (!existsSync4(dir)) {
|
|
848
|
+
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
667
849
|
}
|
|
668
|
-
return
|
|
850
|
+
return join6(dir, "entanglement.json");
|
|
669
851
|
}
|
|
670
852
|
function loadRegistry() {
|
|
671
853
|
return loadJsonRegistry(getRegistryPath(), { pairs: [] });
|
|
672
854
|
}
|
|
673
855
|
function saveRegistry(registry2) {
|
|
674
856
|
registry2.version = REGISTRY_VERSION;
|
|
675
|
-
|
|
857
|
+
writeFileSync3(getRegistryPath(), JSON.stringify(registry2, null, 2), {
|
|
676
858
|
mode: 384
|
|
677
859
|
});
|
|
678
860
|
}
|
|
@@ -706,9 +888,9 @@ function listEntanglements() {
|
|
|
706
888
|
}
|
|
707
889
|
|
|
708
890
|
// src/core/hooks.ts
|
|
709
|
-
import { existsSync as
|
|
710
|
-
import { join as
|
|
711
|
-
import { homedir as
|
|
891
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5 } from "fs";
|
|
892
|
+
import { join as join7 } from "path";
|
|
893
|
+
import { homedir as homedir5 } from "os";
|
|
712
894
|
import { execFile, spawn } from "child_process";
|
|
713
895
|
import { randomUUID } from "crypto";
|
|
714
896
|
|
|
@@ -932,17 +1114,17 @@ function httpRequest(opts) {
|
|
|
932
1114
|
|
|
933
1115
|
// src/core/hooks.ts
|
|
934
1116
|
function getRegistryPath2() {
|
|
935
|
-
const dir =
|
|
936
|
-
if (!
|
|
937
|
-
|
|
1117
|
+
const dir = join7(homedir5(), ".config", "q-ring");
|
|
1118
|
+
if (!existsSync5(dir)) {
|
|
1119
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
938
1120
|
}
|
|
939
|
-
return
|
|
1121
|
+
return join7(dir, "hooks.json");
|
|
940
1122
|
}
|
|
941
1123
|
function loadRegistry2() {
|
|
942
1124
|
return loadJsonRegistry(getRegistryPath2(), { hooks: [] });
|
|
943
1125
|
}
|
|
944
1126
|
function saveRegistry2(registry2) {
|
|
945
|
-
|
|
1127
|
+
writeFileSync4(getRegistryPath2(), JSON.stringify(registry2, null, 2), {
|
|
946
1128
|
mode: 384
|
|
947
1129
|
});
|
|
948
1130
|
}
|
|
@@ -1189,19 +1371,19 @@ async function fireHooks(payload, tags) {
|
|
|
1189
1371
|
}
|
|
1190
1372
|
|
|
1191
1373
|
// src/core/approval.ts
|
|
1192
|
-
import { existsSync as
|
|
1193
|
-
import { join as
|
|
1194
|
-
import { homedir as
|
|
1195
|
-
import { createHmac as createHmac2, randomBytes as
|
|
1374
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
|
|
1375
|
+
import { join as join8 } from "path";
|
|
1376
|
+
import { homedir as homedir6 } from "os";
|
|
1377
|
+
import { createHmac as createHmac2, randomBytes as randomBytes3, timingSafeEqual } from "crypto";
|
|
1196
1378
|
function getHmacSecret() {
|
|
1197
|
-
const dir =
|
|
1198
|
-
const secretPath =
|
|
1199
|
-
if (!
|
|
1200
|
-
if (
|
|
1201
|
-
return
|
|
1202
|
-
}
|
|
1203
|
-
const secret =
|
|
1204
|
-
|
|
1379
|
+
const dir = join8(homedir6(), ".config", "q-ring");
|
|
1380
|
+
const secretPath = join8(dir, ".approval-key");
|
|
1381
|
+
if (!existsSync6(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
1382
|
+
if (existsSync6(secretPath)) {
|
|
1383
|
+
return readFileSync7(secretPath, "utf8").trim();
|
|
1384
|
+
}
|
|
1385
|
+
const secret = randomBytes3(32).toString("hex");
|
|
1386
|
+
writeFileSync5(secretPath, secret, { mode: 384 });
|
|
1205
1387
|
return secret;
|
|
1206
1388
|
}
|
|
1207
1389
|
function computeHmac(entry) {
|
|
@@ -1231,11 +1413,11 @@ function verifyHmac(entry) {
|
|
|
1231
1413
|
}
|
|
1232
1414
|
}
|
|
1233
1415
|
function getRegistryPath3() {
|
|
1234
|
-
const dir =
|
|
1235
|
-
if (!
|
|
1236
|
-
|
|
1416
|
+
const dir = join8(homedir6(), ".config", "q-ring");
|
|
1417
|
+
if (!existsSync6(dir)) {
|
|
1418
|
+
mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
1237
1419
|
}
|
|
1238
|
-
return
|
|
1420
|
+
return join8(dir, "approvals.json");
|
|
1239
1421
|
}
|
|
1240
1422
|
function loadRegistry3() {
|
|
1241
1423
|
return loadJsonRegistry(getRegistryPath3(), { approvals: [] });
|
|
@@ -1262,7 +1444,7 @@ function listApprovals() {
|
|
|
1262
1444
|
|
|
1263
1445
|
// src/core/policy.ts
|
|
1264
1446
|
import { statSync as statSync4 } from "fs";
|
|
1265
|
-
import { join as
|
|
1447
|
+
import { join as join9 } from "path";
|
|
1266
1448
|
import { z as z2 } from "zod";
|
|
1267
1449
|
var stringArray = z2.array(z2.string());
|
|
1268
1450
|
var mcpPolicySchema = z2.object({
|
|
@@ -1308,7 +1490,7 @@ function resolvePolicyPath(projectPath) {
|
|
|
1308
1490
|
}
|
|
1309
1491
|
function configMtime(pp) {
|
|
1310
1492
|
try {
|
|
1311
|
-
return statSync4(
|
|
1493
|
+
return statSync4(join9(pp, ".q-ring.json")).mtimeMs;
|
|
1312
1494
|
} catch {
|
|
1313
1495
|
return 0;
|
|
1314
1496
|
}
|
|
@@ -1331,7 +1513,7 @@ function loadPolicy(projectPath) {
|
|
|
1331
1513
|
if (!parsed.success) {
|
|
1332
1514
|
const issues = parsed.error.issues.map((i) => `policy${i.path.length ? "." + i.path.join(".") : ""}: ${i.message}`).join("; ");
|
|
1333
1515
|
const error = new PolicyConfigError(
|
|
1334
|
-
`Invalid policy in ${
|
|
1516
|
+
`Invalid policy in ${join9(pp, ".q-ring.json")} \u2014 refusing to run under an unparseable security policy (fail closed). Fix these and retry: ${issues}`
|
|
1335
1517
|
);
|
|
1336
1518
|
console.error(`q-ring: ${error.message}`);
|
|
1337
1519
|
cachedPolicy = { path: pp, mtimeMs, error };
|
|
@@ -1466,9 +1648,6 @@ function getPolicySummary(projectPath) {
|
|
|
1466
1648
|
};
|
|
1467
1649
|
}
|
|
1468
1650
|
|
|
1469
|
-
// src/core/keyring.ts
|
|
1470
|
-
import { Entry as Entry2, findCredentials } from "@napi-rs/keyring";
|
|
1471
|
-
|
|
1472
1651
|
// src/utils/hash.ts
|
|
1473
1652
|
import { createHash as createHash2 } from "crypto";
|
|
1474
1653
|
function hashProjectPath(projectPath) {
|
|
@@ -1524,6 +1703,75 @@ function serviceForScope(scope, opts = {}) {
|
|
|
1524
1703
|
return resolveScope({ ...opts, scope })[0].service;
|
|
1525
1704
|
}
|
|
1526
1705
|
|
|
1706
|
+
// src/core/notify.ts
|
|
1707
|
+
import { spawn as spawn2 } from "child_process";
|
|
1708
|
+
var DISABLE_ENV = "QRING_NOTIFY";
|
|
1709
|
+
var THROTTLE_MS = 5 * 60 * 1e3;
|
|
1710
|
+
var lastNotified = /* @__PURE__ */ new Map();
|
|
1711
|
+
function notificationsEnabled() {
|
|
1712
|
+
const value = process.env[DISABLE_ENV];
|
|
1713
|
+
return value !== "off" && value !== "0" && value !== "false";
|
|
1714
|
+
}
|
|
1715
|
+
function notifyUser(title, body) {
|
|
1716
|
+
try {
|
|
1717
|
+
let command;
|
|
1718
|
+
let args;
|
|
1719
|
+
if (process.platform === "linux") {
|
|
1720
|
+
command = "notify-send";
|
|
1721
|
+
args = ["--app-name=q-ring", "--urgency=critical", title, body];
|
|
1722
|
+
} else if (process.platform === "darwin") {
|
|
1723
|
+
command = "osascript";
|
|
1724
|
+
const clean = (s) => s.replace(/["\\]/g, "");
|
|
1725
|
+
args = ["-e", `display notification "${clean(body)}" with title "${clean(title)}"`];
|
|
1726
|
+
} else {
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
const child = spawn2(command, args, { detached: true, stdio: "ignore" });
|
|
1730
|
+
child.on("error", () => {
|
|
1731
|
+
});
|
|
1732
|
+
child.unref();
|
|
1733
|
+
return true;
|
|
1734
|
+
} catch {
|
|
1735
|
+
return false;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
function notifyApprovalRequested(key, source) {
|
|
1739
|
+
if (!notificationsEnabled()) return;
|
|
1740
|
+
const now = Date.now();
|
|
1741
|
+
const last = lastNotified.get(key);
|
|
1742
|
+
if (last !== void 0 && now - last < THROTTLE_MS) return;
|
|
1743
|
+
lastNotified.set(key, now);
|
|
1744
|
+
notifyUser(
|
|
1745
|
+
"q-ring: approval requested",
|
|
1746
|
+
`An ${source} agent wants to read "${key}". Allow with: qring approve ${key}`
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// src/core/canary-alert.ts
|
|
1751
|
+
var TRIP_THROTTLE_MS = 30 * 1e3;
|
|
1752
|
+
var lastAlerted = /* @__PURE__ */ new Map();
|
|
1753
|
+
function recordCanaryTrip(trip) {
|
|
1754
|
+
const agent = getAuditAgentLabel();
|
|
1755
|
+
logAudit({
|
|
1756
|
+
action: "canary",
|
|
1757
|
+
key: trip.key,
|
|
1758
|
+
scope: trip.scope,
|
|
1759
|
+
env: trip.env,
|
|
1760
|
+
source: trip.source,
|
|
1761
|
+
detail: `CANARY TRIPPED: honeytoken read via ${trip.source}`
|
|
1762
|
+
});
|
|
1763
|
+
if (!notificationsEnabled()) return;
|
|
1764
|
+
const now = Date.now();
|
|
1765
|
+
const last = lastAlerted.get(trip.key);
|
|
1766
|
+
if (last !== void 0 && now - last < TRIP_THROTTLE_MS) return;
|
|
1767
|
+
lastAlerted.set(trip.key, now);
|
|
1768
|
+
const who = agent ? `${trip.source} (${agent})` : trip.source;
|
|
1769
|
+
notifyUser(
|
|
1770
|
+
"q-ring: CANARY TRIPPED",
|
|
1771
|
+
`Honeytoken "${trip.key}" was read by ${who}. This credential is fake \u2014 but something reached for it. Investigate: qring audit --action canary`
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1527
1775
|
// src/core/provision.ts
|
|
1528
1776
|
import { execFileSync, spawnSync } from "child_process";
|
|
1529
1777
|
import { z as z3 } from "zod";
|
|
@@ -1676,14 +1924,14 @@ function withJitEnvelopeLock(service, key, fn) {
|
|
|
1676
1924
|
return withFileLock(`${service}\0${key}`, fn, { dir: "jit-locks" });
|
|
1677
1925
|
}
|
|
1678
1926
|
function readEnvelope(service, key) {
|
|
1679
|
-
const entry = new
|
|
1927
|
+
const entry = new Entry(service, key);
|
|
1680
1928
|
const raw = entry.getPassword();
|
|
1681
1929
|
if (raw === null) return null;
|
|
1682
1930
|
const envelope = parseEnvelope(raw);
|
|
1683
1931
|
return envelope ?? wrapLegacy(raw);
|
|
1684
1932
|
}
|
|
1685
1933
|
function writeEnvelope(service, key, envelope) {
|
|
1686
|
-
const entry = new
|
|
1934
|
+
const entry = new Entry(service, key);
|
|
1687
1935
|
entry.setPassword(serializeEnvelope(envelope));
|
|
1688
1936
|
}
|
|
1689
1937
|
function resolveEnv(opts) {
|
|
@@ -1760,6 +2008,7 @@ function getSecret(key, opts = {}) {
|
|
|
1760
2008
|
source,
|
|
1761
2009
|
detail: "blocked: requires user approval"
|
|
1762
2010
|
});
|
|
2011
|
+
notifyApprovalRequested(key, source);
|
|
1763
2012
|
}
|
|
1764
2013
|
throw new Error(`Access Denied: This secret requires user approval. Please ask the user to run 'qring approve ${key}'`);
|
|
1765
2014
|
}
|
|
@@ -1804,6 +2053,9 @@ function getSecret(key, opts = {}) {
|
|
|
1804
2053
|
const latest = readEnvelope(service, key) ?? envelope;
|
|
1805
2054
|
writeEnvelope(service, key, recordAccess(latest));
|
|
1806
2055
|
logAudit({ action: "read", key, scope, env, source });
|
|
2056
|
+
if (envelope.meta.canary) {
|
|
2057
|
+
recordCanaryTrip({ key, scope, env, source });
|
|
2058
|
+
}
|
|
1807
2059
|
}
|
|
1808
2060
|
return value;
|
|
1809
2061
|
}
|
|
@@ -1836,6 +2088,8 @@ function setSecret(key, value, opts = {}) {
|
|
|
1836
2088
|
const prov = opts.provider ?? existing?.meta.provider;
|
|
1837
2089
|
const reqApp = opts.requiresApproval ?? existing?.meta.requiresApproval;
|
|
1838
2090
|
const jitProv = opts.jitProvider ?? existing?.meta.jitProvider;
|
|
2091
|
+
const canaryFlag = opts.canary ?? existing?.meta.canary;
|
|
2092
|
+
const canaryFmt = opts.canaryFormat ?? existing?.meta.canaryFormat;
|
|
1839
2093
|
const mergedTags = opts.tags ?? existing?.meta.tags;
|
|
1840
2094
|
const ttlForPolicy = opts.ttlSeconds ?? existing?.meta.ttlSeconds;
|
|
1841
2095
|
const life = checkSecretLifecyclePolicy(
|
|
@@ -1863,7 +2117,9 @@ function setSecret(key, value, opts = {}) {
|
|
|
1863
2117
|
rotationPrefix: rotPfx,
|
|
1864
2118
|
provider: prov,
|
|
1865
2119
|
requiresApproval: reqApp,
|
|
1866
|
-
jitProvider: jitProv
|
|
2120
|
+
jitProvider: jitProv,
|
|
2121
|
+
canary: canaryFlag,
|
|
2122
|
+
canaryFormat: canaryFmt
|
|
1867
2123
|
});
|
|
1868
2124
|
} else {
|
|
1869
2125
|
envelope = createEnvelope(value, {
|
|
@@ -1876,7 +2132,9 @@ function setSecret(key, value, opts = {}) {
|
|
|
1876
2132
|
rotationPrefix: rotPfx,
|
|
1877
2133
|
provider: prov,
|
|
1878
2134
|
requiresApproval: reqApp,
|
|
1879
|
-
jitProvider: jitProv
|
|
2135
|
+
jitProvider: jitProv,
|
|
2136
|
+
canary: canaryFlag,
|
|
2137
|
+
canaryFormat: canaryFmt
|
|
1880
2138
|
});
|
|
1881
2139
|
}
|
|
1882
2140
|
if (existing) {
|
|
@@ -1941,7 +2199,7 @@ function deleteSecret(key, opts = {}) {
|
|
|
1941
2199
|
}
|
|
1942
2200
|
let deleted = false;
|
|
1943
2201
|
for (const { service, scope } of scopes) {
|
|
1944
|
-
const entry = new
|
|
2202
|
+
const entry = new Entry(service, key);
|
|
1945
2203
|
try {
|
|
1946
2204
|
if (entry.deleteCredential()) {
|
|
1947
2205
|
deleted = true;
|
|
@@ -2121,7 +2379,7 @@ function disentangleSecrets(sourceKey, sourceOpts, targetKey, targetOpts) {
|
|
|
2121
2379
|
}
|
|
2122
2380
|
|
|
2123
2381
|
// src/core/tunnel.ts
|
|
2124
|
-
import { randomBytes as
|
|
2382
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
2125
2383
|
var tunnelStore = /* @__PURE__ */ new Map();
|
|
2126
2384
|
var cleanupInterval = null;
|
|
2127
2385
|
function ensureCleanup() {
|
|
@@ -2143,7 +2401,7 @@ function ensureCleanup() {
|
|
|
2143
2401
|
}
|
|
2144
2402
|
}
|
|
2145
2403
|
function tunnelCreate(value, opts = {}) {
|
|
2146
|
-
const id = `tun_${Date.now().toString(36)}_${
|
|
2404
|
+
const id = `tun_${Date.now().toString(36)}_${randomBytes4(6).toString("base64url")}`;
|
|
2147
2405
|
const now = Date.now();
|
|
2148
2406
|
tunnelStore.set(id, {
|
|
2149
2407
|
value,
|
|
@@ -2193,40 +2451,39 @@ function tunnelList() {
|
|
|
2193
2451
|
}
|
|
2194
2452
|
|
|
2195
2453
|
// src/core/memory.ts
|
|
2196
|
-
import { existsSync as
|
|
2197
|
-
import { join as
|
|
2198
|
-
import { homedir as
|
|
2454
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, chmodSync as chmodSync3 } from "fs";
|
|
2455
|
+
import { join as join10 } from "path";
|
|
2456
|
+
import { homedir as homedir7, hostname, userInfo } from "os";
|
|
2199
2457
|
import {
|
|
2200
|
-
createCipheriv,
|
|
2201
|
-
createDecipheriv,
|
|
2458
|
+
createCipheriv as createCipheriv2,
|
|
2459
|
+
createDecipheriv as createDecipheriv2,
|
|
2202
2460
|
createHash as createHash3,
|
|
2203
|
-
randomBytes as
|
|
2204
|
-
pbkdf2Sync
|
|
2461
|
+
randomBytes as randomBytes5,
|
|
2462
|
+
pbkdf2Sync as pbkdf2Sync2
|
|
2205
2463
|
} from "crypto";
|
|
2206
|
-
import { Entry as Entry3 } from "@napi-rs/keyring";
|
|
2207
2464
|
var MEMORY_FILE = "agent-memory.enc";
|
|
2208
2465
|
var KEYRING_SERVICE = "qring-memory-key";
|
|
2209
2466
|
var KEYRING_ACCOUNT = "encryption-key";
|
|
2210
2467
|
function getMemoryDir() {
|
|
2211
|
-
const dir =
|
|
2212
|
-
if (!
|
|
2213
|
-
|
|
2468
|
+
const dir = join10(homedir7(), ".config", "q-ring");
|
|
2469
|
+
if (!existsSync7(dir)) {
|
|
2470
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
2214
2471
|
}
|
|
2215
2472
|
return dir;
|
|
2216
2473
|
}
|
|
2217
2474
|
function writeMemoryFile(path, data) {
|
|
2218
|
-
|
|
2475
|
+
writeFileSync6(path, data, { mode: 384 });
|
|
2219
2476
|
try {
|
|
2220
|
-
|
|
2477
|
+
chmodSync3(path, 384);
|
|
2221
2478
|
} catch {
|
|
2222
2479
|
}
|
|
2223
2480
|
}
|
|
2224
2481
|
function getMemoryPath() {
|
|
2225
|
-
return
|
|
2482
|
+
return join10(getMemoryDir(), MEMORY_FILE);
|
|
2226
2483
|
}
|
|
2227
|
-
var
|
|
2228
|
-
var
|
|
2229
|
-
var
|
|
2484
|
+
var PBKDF2_ITERATIONS2 = 21e4;
|
|
2485
|
+
var KEY_LENGTH2 = 32;
|
|
2486
|
+
var PASSPHRASE_ENV2 = "QRING_MEMORY_PASSPHRASE";
|
|
2230
2487
|
var V2_PREFIX = "qmem2";
|
|
2231
2488
|
var MemoryKeyUnavailableError = class extends Error {
|
|
2232
2489
|
constructor(message) {
|
|
@@ -2238,19 +2495,19 @@ function deriveLegacyKey() {
|
|
|
2238
2495
|
const fingerprint = `qring-memory:${hostname()}:${userInfo().username}`;
|
|
2239
2496
|
return createHash3("sha256").update(fingerprint).digest();
|
|
2240
2497
|
}
|
|
2241
|
-
function
|
|
2242
|
-
const p = process.env[
|
|
2498
|
+
function passphrase2() {
|
|
2499
|
+
const p = process.env[PASSPHRASE_ENV2];
|
|
2243
2500
|
return p && p.length > 0 ? p : void 0;
|
|
2244
2501
|
}
|
|
2245
2502
|
function derivePassphraseKey(salt) {
|
|
2246
|
-
return
|
|
2503
|
+
return pbkdf2Sync2(passphrase2(), salt, PBKDF2_ITERATIONS2, KEY_LENGTH2, "sha512");
|
|
2247
2504
|
}
|
|
2248
2505
|
function keyringKey() {
|
|
2249
2506
|
try {
|
|
2250
|
-
const entry = new
|
|
2507
|
+
const entry = new Entry(KEYRING_SERVICE, KEYRING_ACCOUNT);
|
|
2251
2508
|
const stored = entry.getPassword();
|
|
2252
2509
|
if (stored) return Buffer.from(stored, "base64");
|
|
2253
|
-
const key =
|
|
2510
|
+
const key = randomBytes5(KEY_LENGTH2);
|
|
2254
2511
|
entry.setPassword(key.toString("base64"));
|
|
2255
2512
|
return key;
|
|
2256
2513
|
} catch {
|
|
@@ -2258,8 +2515,8 @@ function keyringKey() {
|
|
|
2258
2515
|
}
|
|
2259
2516
|
}
|
|
2260
2517
|
function encryptWith(data, key) {
|
|
2261
|
-
const iv =
|
|
2262
|
-
const cipher =
|
|
2518
|
+
const iv = randomBytes5(12);
|
|
2519
|
+
const cipher = createCipheriv2("aes-256-gcm", key, iv);
|
|
2263
2520
|
const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
2264
2521
|
const tag = cipher.getAuthTag();
|
|
2265
2522
|
return `${iv.toString("base64")}:${tag.toString("base64")}:${encrypted.toString("base64")}`;
|
|
@@ -2270,27 +2527,27 @@ function decryptWith(blob, key) {
|
|
|
2270
2527
|
const iv = Buffer.from(parts[0], "base64");
|
|
2271
2528
|
const tag = Buffer.from(parts[1], "base64");
|
|
2272
2529
|
const encrypted = Buffer.from(parts[2], "base64");
|
|
2273
|
-
const decipher =
|
|
2530
|
+
const decipher = createDecipheriv2("aes-256-gcm", key, iv);
|
|
2274
2531
|
decipher.setAuthTag(tag);
|
|
2275
2532
|
return decipher.update(encrypted) + decipher.final("utf8");
|
|
2276
2533
|
}
|
|
2277
2534
|
function encrypt(data) {
|
|
2278
2535
|
const kk = keyringKey();
|
|
2279
2536
|
if (kk) return encryptWith(data, kk);
|
|
2280
|
-
if (
|
|
2281
|
-
const salt =
|
|
2537
|
+
if (passphrase2()) {
|
|
2538
|
+
const salt = randomBytes5(16);
|
|
2282
2539
|
const key = derivePassphraseKey(salt);
|
|
2283
2540
|
return `${V2_PREFIX}:${salt.toString("base64")}:${encryptWith(data, key)}`;
|
|
2284
2541
|
}
|
|
2285
2542
|
throw new MemoryKeyUnavailableError(
|
|
2286
|
-
`Cannot persist agent memory: the OS keyring is unavailable and ${
|
|
2543
|
+
`Cannot persist agent memory: the OS keyring is unavailable and ${PASSPHRASE_ENV2} is not set. Refusing to encrypt with a machine-derivable key (any local process could recompute it and read your memory). Set ${PASSPHRASE_ENV2} to a strong passphrase, or run on a host with an OS keyring.`
|
|
2287
2544
|
);
|
|
2288
2545
|
}
|
|
2289
2546
|
function decrypt(blob) {
|
|
2290
2547
|
if (blob.startsWith(`${V2_PREFIX}:`)) {
|
|
2291
|
-
if (!
|
|
2548
|
+
if (!passphrase2()) {
|
|
2292
2549
|
throw new MemoryKeyUnavailableError(
|
|
2293
|
-
`Agent memory was encrypted with ${
|
|
2550
|
+
`Agent memory was encrypted with ${PASSPHRASE_ENV2} but it is not set \u2014 cannot decrypt.`
|
|
2294
2551
|
);
|
|
2295
2552
|
}
|
|
2296
2553
|
const rest = blob.slice(V2_PREFIX.length + 1);
|
|
@@ -2314,13 +2571,13 @@ function decrypt(blob) {
|
|
|
2314
2571
|
}
|
|
2315
2572
|
return plain;
|
|
2316
2573
|
}
|
|
2317
|
-
function
|
|
2574
|
+
function loadStore2() {
|
|
2318
2575
|
const path = getMemoryPath();
|
|
2319
|
-
if (!
|
|
2576
|
+
if (!existsSync7(path)) {
|
|
2320
2577
|
return { entries: {} };
|
|
2321
2578
|
}
|
|
2322
2579
|
try {
|
|
2323
|
-
const raw =
|
|
2580
|
+
const raw = readFileSync8(path, "utf8");
|
|
2324
2581
|
const decrypted = decrypt(raw);
|
|
2325
2582
|
return JSON.parse(decrypted);
|
|
2326
2583
|
} catch (err) {
|
|
@@ -2330,35 +2587,35 @@ function loadStore() {
|
|
|
2330
2587
|
return { entries: {} };
|
|
2331
2588
|
}
|
|
2332
2589
|
}
|
|
2333
|
-
function
|
|
2590
|
+
function saveStore2(store) {
|
|
2334
2591
|
const json = JSON.stringify(store);
|
|
2335
2592
|
const encrypted = encrypt(json);
|
|
2336
2593
|
writeMemoryFile(getMemoryPath(), encrypted);
|
|
2337
2594
|
}
|
|
2338
2595
|
function remember(key, value) {
|
|
2339
|
-
const store =
|
|
2596
|
+
const store = loadStore2();
|
|
2340
2597
|
store.entries[key] = {
|
|
2341
2598
|
value,
|
|
2342
2599
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2343
2600
|
};
|
|
2344
|
-
|
|
2601
|
+
saveStore2(store);
|
|
2345
2602
|
}
|
|
2346
2603
|
function recall(key) {
|
|
2347
|
-
const store =
|
|
2604
|
+
const store = loadStore2();
|
|
2348
2605
|
return store.entries[key]?.value ?? null;
|
|
2349
2606
|
}
|
|
2350
2607
|
function listMemory() {
|
|
2351
|
-
const store =
|
|
2608
|
+
const store = loadStore2();
|
|
2352
2609
|
return Object.entries(store.entries).map(([key, entry]) => ({
|
|
2353
2610
|
key,
|
|
2354
2611
|
updatedAt: entry.updatedAt
|
|
2355
2612
|
}));
|
|
2356
2613
|
}
|
|
2357
2614
|
function forget(key) {
|
|
2358
|
-
const store =
|
|
2615
|
+
const store = loadStore2();
|
|
2359
2616
|
if (key in store.entries) {
|
|
2360
2617
|
delete store.entries[key];
|
|
2361
|
-
|
|
2618
|
+
saveStore2(store);
|
|
2362
2619
|
return true;
|
|
2363
2620
|
}
|
|
2364
2621
|
return false;
|
|
@@ -2369,6 +2626,7 @@ export {
|
|
|
2369
2626
|
checkDecay,
|
|
2370
2627
|
readProjectConfig,
|
|
2371
2628
|
collapseEnvironment,
|
|
2629
|
+
setAuditAgentLabel,
|
|
2372
2630
|
logAudit,
|
|
2373
2631
|
queryAudit,
|
|
2374
2632
|
verifyAuditChain,
|
|
@@ -2407,4 +2665,4 @@ export {
|
|
|
2407
2665
|
listMemory,
|
|
2408
2666
|
forget
|
|
2409
2667
|
};
|
|
2410
|
-
//# sourceMappingURL=chunk-
|
|
2668
|
+
//# sourceMappingURL=chunk-NCM5GHNW.js.map
|