@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
|
}
|
|
@@ -1205,19 +1387,19 @@ async function fireHooks(payload, tags) {
|
|
|
1205
1387
|
}
|
|
1206
1388
|
|
|
1207
1389
|
// src/core/approval.ts
|
|
1208
|
-
import { existsSync as
|
|
1209
|
-
import { join as
|
|
1210
|
-
import { homedir as
|
|
1211
|
-
import { createHmac as createHmac2, randomBytes as
|
|
1390
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
|
|
1391
|
+
import { join as join8 } from "path";
|
|
1392
|
+
import { homedir as homedir6 } from "os";
|
|
1393
|
+
import { createHmac as createHmac2, randomBytes as randomBytes3, timingSafeEqual } from "crypto";
|
|
1212
1394
|
function getHmacSecret() {
|
|
1213
|
-
const dir =
|
|
1214
|
-
const secretPath =
|
|
1215
|
-
if (!
|
|
1216
|
-
if (
|
|
1217
|
-
return
|
|
1218
|
-
}
|
|
1219
|
-
const secret =
|
|
1220
|
-
|
|
1395
|
+
const dir = join8(homedir6(), ".config", "q-ring");
|
|
1396
|
+
const secretPath = join8(dir, ".approval-key");
|
|
1397
|
+
if (!existsSync6(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
1398
|
+
if (existsSync6(secretPath)) {
|
|
1399
|
+
return readFileSync7(secretPath, "utf8").trim();
|
|
1400
|
+
}
|
|
1401
|
+
const secret = randomBytes3(32).toString("hex");
|
|
1402
|
+
writeFileSync5(secretPath, secret, { mode: 384 });
|
|
1221
1403
|
return secret;
|
|
1222
1404
|
}
|
|
1223
1405
|
function computeHmac(entry) {
|
|
@@ -1247,17 +1429,17 @@ function verifyHmac(entry) {
|
|
|
1247
1429
|
}
|
|
1248
1430
|
}
|
|
1249
1431
|
function getRegistryPath3() {
|
|
1250
|
-
const dir =
|
|
1251
|
-
if (!
|
|
1252
|
-
|
|
1432
|
+
const dir = join8(homedir6(), ".config", "q-ring");
|
|
1433
|
+
if (!existsSync6(dir)) {
|
|
1434
|
+
mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
1253
1435
|
}
|
|
1254
|
-
return
|
|
1436
|
+
return join8(dir, "approvals.json");
|
|
1255
1437
|
}
|
|
1256
1438
|
function loadRegistry3() {
|
|
1257
1439
|
return loadJsonRegistry(getRegistryPath3(), { approvals: [] });
|
|
1258
1440
|
}
|
|
1259
1441
|
function saveRegistry3(registry2) {
|
|
1260
|
-
|
|
1442
|
+
writeFileSync5(getRegistryPath3(), JSON.stringify(registry2, null, 2), { mode: 384 });
|
|
1261
1443
|
}
|
|
1262
1444
|
function cleanup(registry2) {
|
|
1263
1445
|
const now = Date.now();
|
|
@@ -1268,7 +1450,7 @@ function cleanup(registry2) {
|
|
|
1268
1450
|
function grantApproval(key, scope, service, ttlSeconds = 3600, grantOpts = {}) {
|
|
1269
1451
|
const registry2 = loadRegistry3();
|
|
1270
1452
|
cleanup(registry2);
|
|
1271
|
-
const id =
|
|
1453
|
+
const id = randomBytes3(8).toString("hex");
|
|
1272
1454
|
const grantedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1273
1455
|
const expiresAt = new Date(Date.now() + ttlSeconds * 1e3).toISOString();
|
|
1274
1456
|
const partial = {
|
|
@@ -1329,7 +1511,7 @@ function listApprovals() {
|
|
|
1329
1511
|
|
|
1330
1512
|
// src/core/policy.ts
|
|
1331
1513
|
import { statSync as statSync4 } from "fs";
|
|
1332
|
-
import { join as
|
|
1514
|
+
import { join as join9 } from "path";
|
|
1333
1515
|
import { z as z2 } from "zod";
|
|
1334
1516
|
var stringArray = z2.array(z2.string());
|
|
1335
1517
|
var mcpPolicySchema = z2.object({
|
|
@@ -1371,7 +1553,7 @@ function resolvePolicyPath(projectPath) {
|
|
|
1371
1553
|
}
|
|
1372
1554
|
function configMtime(pp) {
|
|
1373
1555
|
try {
|
|
1374
|
-
return statSync4(
|
|
1556
|
+
return statSync4(join9(pp, ".q-ring.json")).mtimeMs;
|
|
1375
1557
|
} catch {
|
|
1376
1558
|
return 0;
|
|
1377
1559
|
}
|
|
@@ -1394,7 +1576,7 @@ function loadPolicy(projectPath) {
|
|
|
1394
1576
|
if (!parsed.success) {
|
|
1395
1577
|
const issues = parsed.error.issues.map((i) => `policy${i.path.length ? "." + i.path.join(".") : ""}: ${i.message}`).join("; ");
|
|
1396
1578
|
const error = new PolicyConfigError(
|
|
1397
|
-
`Invalid policy in ${
|
|
1579
|
+
`Invalid policy in ${join9(pp, ".q-ring.json")} \u2014 refusing to run under an unparseable security policy (fail closed). Fix these and retry: ${issues}`
|
|
1398
1580
|
);
|
|
1399
1581
|
console.error(`q-ring: ${error.message}`);
|
|
1400
1582
|
cachedPolicy = { path: pp, mtimeMs, error };
|
|
@@ -1510,9 +1692,6 @@ function getPolicySummary(projectPath) {
|
|
|
1510
1692
|
};
|
|
1511
1693
|
}
|
|
1512
1694
|
|
|
1513
|
-
// src/core/keyring.ts
|
|
1514
|
-
import { Entry as Entry2, findCredentials } from "@napi-rs/keyring";
|
|
1515
|
-
|
|
1516
1695
|
// src/utils/hash.ts
|
|
1517
1696
|
import { createHash as createHash2 } from "crypto";
|
|
1518
1697
|
function hashProjectPath(projectPath) {
|
|
@@ -1568,6 +1747,75 @@ function serviceForScope(scope, opts = {}) {
|
|
|
1568
1747
|
return resolveScope({ ...opts, scope })[0].service;
|
|
1569
1748
|
}
|
|
1570
1749
|
|
|
1750
|
+
// src/core/notify.ts
|
|
1751
|
+
import { spawn as spawn2 } from "child_process";
|
|
1752
|
+
var DISABLE_ENV = "QRING_NOTIFY";
|
|
1753
|
+
var THROTTLE_MS = 5 * 60 * 1e3;
|
|
1754
|
+
var lastNotified = /* @__PURE__ */ new Map();
|
|
1755
|
+
function notificationsEnabled() {
|
|
1756
|
+
const value = process.env[DISABLE_ENV];
|
|
1757
|
+
return value !== "off" && value !== "0" && value !== "false";
|
|
1758
|
+
}
|
|
1759
|
+
function notifyUser(title, body) {
|
|
1760
|
+
try {
|
|
1761
|
+
let command;
|
|
1762
|
+
let args;
|
|
1763
|
+
if (process.platform === "linux") {
|
|
1764
|
+
command = "notify-send";
|
|
1765
|
+
args = ["--app-name=q-ring", "--urgency=critical", title, body];
|
|
1766
|
+
} else if (process.platform === "darwin") {
|
|
1767
|
+
command = "osascript";
|
|
1768
|
+
const clean = (s) => s.replace(/["\\]/g, "");
|
|
1769
|
+
args = ["-e", `display notification "${clean(body)}" with title "${clean(title)}"`];
|
|
1770
|
+
} else {
|
|
1771
|
+
return false;
|
|
1772
|
+
}
|
|
1773
|
+
const child = spawn2(command, args, { detached: true, stdio: "ignore" });
|
|
1774
|
+
child.on("error", () => {
|
|
1775
|
+
});
|
|
1776
|
+
child.unref();
|
|
1777
|
+
return true;
|
|
1778
|
+
} catch {
|
|
1779
|
+
return false;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
function notifyApprovalRequested(key, source) {
|
|
1783
|
+
if (!notificationsEnabled()) return;
|
|
1784
|
+
const now = Date.now();
|
|
1785
|
+
const last = lastNotified.get(key);
|
|
1786
|
+
if (last !== void 0 && now - last < THROTTLE_MS) return;
|
|
1787
|
+
lastNotified.set(key, now);
|
|
1788
|
+
notifyUser(
|
|
1789
|
+
"q-ring: approval requested",
|
|
1790
|
+
`An ${source} agent wants to read "${key}". Allow with: qring approve ${key}`
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
// src/core/canary-alert.ts
|
|
1795
|
+
var TRIP_THROTTLE_MS = 30 * 1e3;
|
|
1796
|
+
var lastAlerted = /* @__PURE__ */ new Map();
|
|
1797
|
+
function recordCanaryTrip(trip) {
|
|
1798
|
+
const agent = getAuditAgentLabel();
|
|
1799
|
+
logAudit({
|
|
1800
|
+
action: "canary",
|
|
1801
|
+
key: trip.key,
|
|
1802
|
+
scope: trip.scope,
|
|
1803
|
+
env: trip.env,
|
|
1804
|
+
source: trip.source,
|
|
1805
|
+
detail: `CANARY TRIPPED: honeytoken read via ${trip.source}`
|
|
1806
|
+
});
|
|
1807
|
+
if (!notificationsEnabled()) return;
|
|
1808
|
+
const now = Date.now();
|
|
1809
|
+
const last = lastAlerted.get(trip.key);
|
|
1810
|
+
if (last !== void 0 && now - last < TRIP_THROTTLE_MS) return;
|
|
1811
|
+
lastAlerted.set(trip.key, now);
|
|
1812
|
+
const who = agent ? `${trip.source} (${agent})` : trip.source;
|
|
1813
|
+
notifyUser(
|
|
1814
|
+
"q-ring: CANARY TRIPPED",
|
|
1815
|
+
`Honeytoken "${trip.key}" was read by ${who}. This credential is fake \u2014 but something reached for it. Investigate: qring audit --action canary`
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1571
1819
|
// src/core/provision.ts
|
|
1572
1820
|
import { execFileSync, spawnSync } from "child_process";
|
|
1573
1821
|
import { z as z3 } from "zod";
|
|
@@ -1720,14 +1968,14 @@ function withJitEnvelopeLock(service, key, fn) {
|
|
|
1720
1968
|
return withFileLock(`${service}\0${key}`, fn, { dir: "jit-locks" });
|
|
1721
1969
|
}
|
|
1722
1970
|
function readEnvelope(service, key) {
|
|
1723
|
-
const entry = new
|
|
1971
|
+
const entry = new Entry(service, key);
|
|
1724
1972
|
const raw = entry.getPassword();
|
|
1725
1973
|
if (raw === null) return null;
|
|
1726
1974
|
const envelope = parseEnvelope(raw);
|
|
1727
1975
|
return envelope ?? wrapLegacy(raw);
|
|
1728
1976
|
}
|
|
1729
1977
|
function writeEnvelope(service, key, envelope) {
|
|
1730
|
-
const entry = new
|
|
1978
|
+
const entry = new Entry(service, key);
|
|
1731
1979
|
entry.setPassword(serializeEnvelope(envelope));
|
|
1732
1980
|
}
|
|
1733
1981
|
function resolveEnv(opts) {
|
|
@@ -1804,6 +2052,7 @@ function getSecret(key, opts = {}) {
|
|
|
1804
2052
|
source,
|
|
1805
2053
|
detail: "blocked: requires user approval"
|
|
1806
2054
|
});
|
|
2055
|
+
notifyApprovalRequested(key, source);
|
|
1807
2056
|
}
|
|
1808
2057
|
throw new Error(`Access Denied: This secret requires user approval. Please ask the user to run 'qring approve ${key}'`);
|
|
1809
2058
|
}
|
|
@@ -1848,6 +2097,9 @@ function getSecret(key, opts = {}) {
|
|
|
1848
2097
|
const latest = readEnvelope(service, key) ?? envelope;
|
|
1849
2098
|
writeEnvelope(service, key, recordAccess(latest));
|
|
1850
2099
|
logAudit({ action: "read", key, scope, env, source });
|
|
2100
|
+
if (envelope.meta.canary) {
|
|
2101
|
+
recordCanaryTrip({ key, scope, env, source });
|
|
2102
|
+
}
|
|
1851
2103
|
}
|
|
1852
2104
|
return value;
|
|
1853
2105
|
}
|
|
@@ -1880,6 +2132,8 @@ function setSecret(key, value, opts = {}) {
|
|
|
1880
2132
|
const prov = opts.provider ?? existing?.meta.provider;
|
|
1881
2133
|
const reqApp = opts.requiresApproval ?? existing?.meta.requiresApproval;
|
|
1882
2134
|
const jitProv = opts.jitProvider ?? existing?.meta.jitProvider;
|
|
2135
|
+
const canaryFlag = opts.canary ?? existing?.meta.canary;
|
|
2136
|
+
const canaryFmt = opts.canaryFormat ?? existing?.meta.canaryFormat;
|
|
1883
2137
|
const mergedTags = opts.tags ?? existing?.meta.tags;
|
|
1884
2138
|
const ttlForPolicy = opts.ttlSeconds ?? existing?.meta.ttlSeconds;
|
|
1885
2139
|
const life = checkSecretLifecyclePolicy(
|
|
@@ -1907,7 +2161,9 @@ function setSecret(key, value, opts = {}) {
|
|
|
1907
2161
|
rotationPrefix: rotPfx,
|
|
1908
2162
|
provider: prov,
|
|
1909
2163
|
requiresApproval: reqApp,
|
|
1910
|
-
jitProvider: jitProv
|
|
2164
|
+
jitProvider: jitProv,
|
|
2165
|
+
canary: canaryFlag,
|
|
2166
|
+
canaryFormat: canaryFmt
|
|
1911
2167
|
});
|
|
1912
2168
|
} else {
|
|
1913
2169
|
envelope = createEnvelope(value, {
|
|
@@ -1920,7 +2176,9 @@ function setSecret(key, value, opts = {}) {
|
|
|
1920
2176
|
rotationPrefix: rotPfx,
|
|
1921
2177
|
provider: prov,
|
|
1922
2178
|
requiresApproval: reqApp,
|
|
1923
|
-
jitProvider: jitProv
|
|
2179
|
+
jitProvider: jitProv,
|
|
2180
|
+
canary: canaryFlag,
|
|
2181
|
+
canaryFormat: canaryFmt
|
|
1924
2182
|
});
|
|
1925
2183
|
}
|
|
1926
2184
|
if (existing) {
|
|
@@ -1985,7 +2243,7 @@ function deleteSecret(key, opts = {}) {
|
|
|
1985
2243
|
}
|
|
1986
2244
|
let deleted = false;
|
|
1987
2245
|
for (const { service, scope } of scopes) {
|
|
1988
|
-
const entry = new
|
|
2246
|
+
const entry = new Entry(service, key);
|
|
1989
2247
|
try {
|
|
1990
2248
|
if (entry.deleteCredential()) {
|
|
1991
2249
|
deleted = true;
|
|
@@ -2165,7 +2423,7 @@ function disentangleSecrets(sourceKey, sourceOpts, targetKey, targetOpts) {
|
|
|
2165
2423
|
}
|
|
2166
2424
|
|
|
2167
2425
|
// src/core/tunnel.ts
|
|
2168
|
-
import { randomBytes as
|
|
2426
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
2169
2427
|
var tunnelStore = /* @__PURE__ */ new Map();
|
|
2170
2428
|
var cleanupInterval = null;
|
|
2171
2429
|
function ensureCleanup() {
|
|
@@ -2187,7 +2445,7 @@ function ensureCleanup() {
|
|
|
2187
2445
|
}
|
|
2188
2446
|
}
|
|
2189
2447
|
function tunnelCreate(value, opts = {}) {
|
|
2190
|
-
const id = `tun_${Date.now().toString(36)}_${
|
|
2448
|
+
const id = `tun_${Date.now().toString(36)}_${randomBytes4(6).toString("base64url")}`;
|
|
2191
2449
|
const now = Date.now();
|
|
2192
2450
|
tunnelStore.set(id, {
|
|
2193
2451
|
value,
|
|
@@ -2237,40 +2495,39 @@ function tunnelList() {
|
|
|
2237
2495
|
}
|
|
2238
2496
|
|
|
2239
2497
|
// src/core/memory.ts
|
|
2240
|
-
import { existsSync as
|
|
2241
|
-
import { join as
|
|
2242
|
-
import { homedir as
|
|
2498
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, chmodSync as chmodSync3 } from "fs";
|
|
2499
|
+
import { join as join10 } from "path";
|
|
2500
|
+
import { homedir as homedir7, hostname, userInfo } from "os";
|
|
2243
2501
|
import {
|
|
2244
|
-
createCipheriv,
|
|
2245
|
-
createDecipheriv,
|
|
2502
|
+
createCipheriv as createCipheriv2,
|
|
2503
|
+
createDecipheriv as createDecipheriv2,
|
|
2246
2504
|
createHash as createHash3,
|
|
2247
|
-
randomBytes as
|
|
2248
|
-
pbkdf2Sync
|
|
2505
|
+
randomBytes as randomBytes5,
|
|
2506
|
+
pbkdf2Sync as pbkdf2Sync2
|
|
2249
2507
|
} from "crypto";
|
|
2250
|
-
import { Entry as Entry3 } from "@napi-rs/keyring";
|
|
2251
2508
|
var MEMORY_FILE = "agent-memory.enc";
|
|
2252
2509
|
var KEYRING_SERVICE = "qring-memory-key";
|
|
2253
2510
|
var KEYRING_ACCOUNT = "encryption-key";
|
|
2254
2511
|
function getMemoryDir() {
|
|
2255
|
-
const dir =
|
|
2256
|
-
if (!
|
|
2257
|
-
|
|
2512
|
+
const dir = join10(homedir7(), ".config", "q-ring");
|
|
2513
|
+
if (!existsSync7(dir)) {
|
|
2514
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
2258
2515
|
}
|
|
2259
2516
|
return dir;
|
|
2260
2517
|
}
|
|
2261
2518
|
function writeMemoryFile(path, data) {
|
|
2262
|
-
|
|
2519
|
+
writeFileSync6(path, data, { mode: 384 });
|
|
2263
2520
|
try {
|
|
2264
|
-
|
|
2521
|
+
chmodSync3(path, 384);
|
|
2265
2522
|
} catch {
|
|
2266
2523
|
}
|
|
2267
2524
|
}
|
|
2268
2525
|
function getMemoryPath() {
|
|
2269
|
-
return
|
|
2526
|
+
return join10(getMemoryDir(), MEMORY_FILE);
|
|
2270
2527
|
}
|
|
2271
|
-
var
|
|
2272
|
-
var
|
|
2273
|
-
var
|
|
2528
|
+
var PBKDF2_ITERATIONS2 = 21e4;
|
|
2529
|
+
var KEY_LENGTH2 = 32;
|
|
2530
|
+
var PASSPHRASE_ENV2 = "QRING_MEMORY_PASSPHRASE";
|
|
2274
2531
|
var V2_PREFIX = "qmem2";
|
|
2275
2532
|
var MemoryKeyUnavailableError = class extends Error {
|
|
2276
2533
|
constructor(message) {
|
|
@@ -2282,19 +2539,19 @@ function deriveLegacyKey() {
|
|
|
2282
2539
|
const fingerprint = `qring-memory:${hostname()}:${userInfo().username}`;
|
|
2283
2540
|
return createHash3("sha256").update(fingerprint).digest();
|
|
2284
2541
|
}
|
|
2285
|
-
function
|
|
2286
|
-
const p = process.env[
|
|
2542
|
+
function passphrase2() {
|
|
2543
|
+
const p = process.env[PASSPHRASE_ENV2];
|
|
2287
2544
|
return p && p.length > 0 ? p : void 0;
|
|
2288
2545
|
}
|
|
2289
2546
|
function derivePassphraseKey(salt) {
|
|
2290
|
-
return
|
|
2547
|
+
return pbkdf2Sync2(passphrase2(), salt, PBKDF2_ITERATIONS2, KEY_LENGTH2, "sha512");
|
|
2291
2548
|
}
|
|
2292
2549
|
function keyringKey() {
|
|
2293
2550
|
try {
|
|
2294
|
-
const entry = new
|
|
2551
|
+
const entry = new Entry(KEYRING_SERVICE, KEYRING_ACCOUNT);
|
|
2295
2552
|
const stored = entry.getPassword();
|
|
2296
2553
|
if (stored) return Buffer.from(stored, "base64");
|
|
2297
|
-
const key =
|
|
2554
|
+
const key = randomBytes5(KEY_LENGTH2);
|
|
2298
2555
|
entry.setPassword(key.toString("base64"));
|
|
2299
2556
|
return key;
|
|
2300
2557
|
} catch {
|
|
@@ -2302,8 +2559,8 @@ function keyringKey() {
|
|
|
2302
2559
|
}
|
|
2303
2560
|
}
|
|
2304
2561
|
function encryptWith(data, key) {
|
|
2305
|
-
const iv =
|
|
2306
|
-
const cipher =
|
|
2562
|
+
const iv = randomBytes5(12);
|
|
2563
|
+
const cipher = createCipheriv2("aes-256-gcm", key, iv);
|
|
2307
2564
|
const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
2308
2565
|
const tag = cipher.getAuthTag();
|
|
2309
2566
|
return `${iv.toString("base64")}:${tag.toString("base64")}:${encrypted.toString("base64")}`;
|
|
@@ -2314,27 +2571,27 @@ function decryptWith(blob, key) {
|
|
|
2314
2571
|
const iv = Buffer.from(parts[0], "base64");
|
|
2315
2572
|
const tag = Buffer.from(parts[1], "base64");
|
|
2316
2573
|
const encrypted = Buffer.from(parts[2], "base64");
|
|
2317
|
-
const decipher =
|
|
2574
|
+
const decipher = createDecipheriv2("aes-256-gcm", key, iv);
|
|
2318
2575
|
decipher.setAuthTag(tag);
|
|
2319
2576
|
return decipher.update(encrypted) + decipher.final("utf8");
|
|
2320
2577
|
}
|
|
2321
2578
|
function encrypt(data) {
|
|
2322
2579
|
const kk = keyringKey();
|
|
2323
2580
|
if (kk) return encryptWith(data, kk);
|
|
2324
|
-
if (
|
|
2325
|
-
const salt =
|
|
2581
|
+
if (passphrase2()) {
|
|
2582
|
+
const salt = randomBytes5(16);
|
|
2326
2583
|
const key = derivePassphraseKey(salt);
|
|
2327
2584
|
return `${V2_PREFIX}:${salt.toString("base64")}:${encryptWith(data, key)}`;
|
|
2328
2585
|
}
|
|
2329
2586
|
throw new MemoryKeyUnavailableError(
|
|
2330
|
-
`Cannot persist agent memory: the OS keyring is unavailable and ${
|
|
2587
|
+
`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.`
|
|
2331
2588
|
);
|
|
2332
2589
|
}
|
|
2333
2590
|
function decrypt(blob) {
|
|
2334
2591
|
if (blob.startsWith(`${V2_PREFIX}:`)) {
|
|
2335
|
-
if (!
|
|
2592
|
+
if (!passphrase2()) {
|
|
2336
2593
|
throw new MemoryKeyUnavailableError(
|
|
2337
|
-
`Agent memory was encrypted with ${
|
|
2594
|
+
`Agent memory was encrypted with ${PASSPHRASE_ENV2} but it is not set \u2014 cannot decrypt.`
|
|
2338
2595
|
);
|
|
2339
2596
|
}
|
|
2340
2597
|
const rest = blob.slice(V2_PREFIX.length + 1);
|
|
@@ -2358,13 +2615,13 @@ function decrypt(blob) {
|
|
|
2358
2615
|
}
|
|
2359
2616
|
return plain;
|
|
2360
2617
|
}
|
|
2361
|
-
function
|
|
2618
|
+
function loadStore2() {
|
|
2362
2619
|
const path = getMemoryPath();
|
|
2363
|
-
if (!
|
|
2620
|
+
if (!existsSync7(path)) {
|
|
2364
2621
|
return { entries: {} };
|
|
2365
2622
|
}
|
|
2366
2623
|
try {
|
|
2367
|
-
const raw =
|
|
2624
|
+
const raw = readFileSync8(path, "utf8");
|
|
2368
2625
|
const decrypted = decrypt(raw);
|
|
2369
2626
|
return JSON.parse(decrypted);
|
|
2370
2627
|
} catch (err) {
|
|
@@ -2374,41 +2631,41 @@ function loadStore() {
|
|
|
2374
2631
|
return { entries: {} };
|
|
2375
2632
|
}
|
|
2376
2633
|
}
|
|
2377
|
-
function
|
|
2634
|
+
function saveStore2(store) {
|
|
2378
2635
|
const json = JSON.stringify(store);
|
|
2379
2636
|
const encrypted = encrypt(json);
|
|
2380
2637
|
writeMemoryFile(getMemoryPath(), encrypted);
|
|
2381
2638
|
}
|
|
2382
2639
|
function remember(key, value) {
|
|
2383
|
-
const store =
|
|
2640
|
+
const store = loadStore2();
|
|
2384
2641
|
store.entries[key] = {
|
|
2385
2642
|
value,
|
|
2386
2643
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2387
2644
|
};
|
|
2388
|
-
|
|
2645
|
+
saveStore2(store);
|
|
2389
2646
|
}
|
|
2390
2647
|
function recall(key) {
|
|
2391
|
-
const store =
|
|
2648
|
+
const store = loadStore2();
|
|
2392
2649
|
return store.entries[key]?.value ?? null;
|
|
2393
2650
|
}
|
|
2394
2651
|
function listMemory() {
|
|
2395
|
-
const store =
|
|
2652
|
+
const store = loadStore2();
|
|
2396
2653
|
return Object.entries(store.entries).map(([key, entry]) => ({
|
|
2397
2654
|
key,
|
|
2398
2655
|
updatedAt: entry.updatedAt
|
|
2399
2656
|
}));
|
|
2400
2657
|
}
|
|
2401
2658
|
function forget(key) {
|
|
2402
|
-
const store =
|
|
2659
|
+
const store = loadStore2();
|
|
2403
2660
|
if (key in store.entries) {
|
|
2404
2661
|
delete store.entries[key];
|
|
2405
|
-
|
|
2662
|
+
saveStore2(store);
|
|
2406
2663
|
return true;
|
|
2407
2664
|
}
|
|
2408
2665
|
return false;
|
|
2409
2666
|
}
|
|
2410
2667
|
function clearMemory() {
|
|
2411
|
-
|
|
2668
|
+
saveStore2({ entries: {} });
|
|
2412
2669
|
}
|
|
2413
2670
|
|
|
2414
2671
|
export {
|
|
@@ -2417,6 +2674,7 @@ export {
|
|
|
2417
2674
|
checkDecay,
|
|
2418
2675
|
readProjectConfig,
|
|
2419
2676
|
collapseEnvironment,
|
|
2677
|
+
setAuditAgentLabel,
|
|
2420
2678
|
logAudit,
|
|
2421
2679
|
queryAudit,
|
|
2422
2680
|
verifyAuditChain,
|
|
@@ -2458,4 +2716,4 @@ export {
|
|
|
2458
2716
|
forget,
|
|
2459
2717
|
clearMemory
|
|
2460
2718
|
};
|
|
2461
|
-
//# sourceMappingURL=chunk-
|
|
2719
|
+
//# sourceMappingURL=chunk-5LFKBZ3Q.js.map
|