@mneme-ai/core 2.52.0 → 2.54.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/dist/agent_manifest.d.ts.map +1 -1
- package/dist/agent_manifest.js +23 -0
- package/dist/agent_manifest.js.map +1 -1
- package/dist/catalog_count.d.ts +47 -0
- package/dist/catalog_count.d.ts.map +1 -0
- package/dist/catalog_count.js +76 -0
- package/dist/catalog_count.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/indispensability.d.ts +31 -0
- package/dist/indispensability.d.ts.map +1 -0
- package/dist/indispensability.js +182 -0
- package/dist/indispensability.js.map +1 -0
- package/dist/nemesis/corpus_augmenter.d.ts +69 -0
- package/dist/nemesis/corpus_augmenter.d.ts.map +1 -0
- package/dist/nemesis/corpus_augmenter.js +164 -0
- package/dist/nemesis/corpus_augmenter.js.map +1 -0
- package/dist/nemesis/eu_ai_act_stamp.d.ts.map +1 -1
- package/dist/nemesis/eu_ai_act_stamp.js +14 -0
- package/dist/nemesis/eu_ai_act_stamp.js.map +1 -1
- package/dist/nemesis/gavel.d.ts +102 -0
- package/dist/nemesis/gavel.d.ts.map +1 -0
- package/dist/nemesis/gavel.js +192 -0
- package/dist/nemesis/gavel.js.map +1 -0
- package/dist/nemesis/index.d.ts +6 -0
- package/dist/nemesis/index.d.ts.map +1 -1
- package/dist/nemesis/index.js +14 -0
- package/dist/nemesis/index.js.map +1 -1
- package/dist/nemesis/janus.d.ts +93 -0
- package/dist/nemesis/janus.d.ts.map +1 -0
- package/dist/nemesis/janus.js +160 -0
- package/dist/nemesis/janus.js.map +1 -0
- package/dist/nemesis/key_setup.d.ts +65 -0
- package/dist/nemesis/key_setup.d.ts.map +1 -0
- package/dist/nemesis/key_setup.js +173 -0
- package/dist/nemesis/key_setup.js.map +1 -0
- package/dist/nemesis/lethe.d.ts +112 -0
- package/dist/nemesis/lethe.d.ts.map +1 -0
- package/dist/nemesis/lethe.js +211 -0
- package/dist/nemesis/lethe.js.map +1 -0
- package/dist/nemesis/nimbus.d.ts +117 -0
- package/dist/nemesis/nimbus.d.ts.map +1 -0
- package/dist/nemesis/nimbus.js +208 -0
- package/dist/nemesis/nimbus.js.map +1 -0
- package/dist/perf_budget.d.ts +48 -0
- package/dist/perf_budget.d.ts.map +1 -0
- package/dist/perf_budget.js +111 -0
- package/dist/perf_budget.js.map +1 -0
- package/dist/release_gate/probe_coverage.d.ts +16 -1
- package/dist/release_gate/probe_coverage.d.ts.map +1 -1
- package/dist/release_gate/probe_coverage.js +13 -4
- package/dist/release_gate/probe_coverage.js.map +1 -1
- package/dist/release_gate/wiring_lag.d.ts +65 -0
- package/dist/release_gate/wiring_lag.d.ts.map +1 -0
- package/dist/release_gate/wiring_lag.js +116 -0
- package/dist/release_gate/wiring_lag.js.map +1 -0
- package/dist/strategy.d.ts +43 -0
- package/dist/strategy.d.ts.map +1 -0
- package/dist/strategy.js +117 -0
- package/dist/strategy.js.map +1 -0
- package/dist/truth_gate/claims.d.ts.map +1 -1
- package/dist/truth_gate/claims.js +41 -0
- package/dist/truth_gate/claims.js.map +1 -1
- package/dist/truth_gate/probes.d.ts.map +1 -1
- package/dist/truth_gate/probes.js +248 -0
- package/dist/truth_gate/probes.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2.53.0 — JANUS organ: cross-vendor cluster-boundary detector.
|
|
3
|
+
*
|
|
4
|
+
* The two-faced Roman god — looks at past + future simultaneously.
|
|
5
|
+
*
|
|
6
|
+
* Closes Eve's identity-swap blind spot from the Million Dollar Secret
|
|
7
|
+
* simulation: MOLT (v2.52) detects INTRA-vendor drift (vendor X drifts
|
|
8
|
+
* over time) but misses CROSS-vendor swaps (vendor X mid-session
|
|
9
|
+
* starts behaving like vendor Y). The classifier might still pick Y as
|
|
10
|
+
* the new winner, but neither MOLT nor verify_identity surfaces the
|
|
11
|
+
* "you crossed a cluster boundary" event.
|
|
12
|
+
*
|
|
13
|
+
* Algorithm:
|
|
14
|
+
* 1. Build cluster centroids from seed corpus (per vendor, per
|
|
15
|
+
* discriminative feature).
|
|
16
|
+
* 2. For an observation O, compute distance d(O, centroid_V) for every
|
|
17
|
+
* known vendor V.
|
|
18
|
+
* 3. Assign O to the nearest centroid → "current basin".
|
|
19
|
+
* 4. Track per-session basin history. If basin changes between
|
|
20
|
+
* observations → cross-cluster transition event.
|
|
21
|
+
* 5. Confidence: distance to old basin / distance to new basin (lower
|
|
22
|
+
* ratio = sharper swap).
|
|
23
|
+
*
|
|
24
|
+
* Different from MOLT because MOLT compares pre/post WINDOWS of the
|
|
25
|
+
* SAME vendor's drift; JANUS detects the MOMENT a vendor's fingerprint
|
|
26
|
+
* crosses into a DIFFERENT vendor's basin.
|
|
27
|
+
*
|
|
28
|
+
* Composes: extractFingerprint + seedStats.
|
|
29
|
+
*
|
|
30
|
+
* Pure deterministic + defensive; never throws.
|
|
31
|
+
*/
|
|
32
|
+
import type { Fingerprint } from "./types.js";
|
|
33
|
+
export interface ClusterDistance {
|
|
34
|
+
vendor: string;
|
|
35
|
+
distance: number;
|
|
36
|
+
}
|
|
37
|
+
export interface JanusBasin {
|
|
38
|
+
/** Nearest vendor centroid. */
|
|
39
|
+
basin: string;
|
|
40
|
+
/** Distance to nearest centroid. */
|
|
41
|
+
basinDistance: number;
|
|
42
|
+
/** Distance to SECOND-nearest centroid (margin signal). */
|
|
43
|
+
secondNearest: ClusterDistance | null;
|
|
44
|
+
/** Margin = secondNearest.distance - basinDistance. Larger = sharper assignment. */
|
|
45
|
+
margin: number;
|
|
46
|
+
/** All centroid distances, sorted ascending. */
|
|
47
|
+
allDistances: ClusterDistance[];
|
|
48
|
+
}
|
|
49
|
+
export interface JanusObservation {
|
|
50
|
+
fingerprint: Fingerprint;
|
|
51
|
+
basin: JanusBasin;
|
|
52
|
+
}
|
|
53
|
+
export interface JanusTransition {
|
|
54
|
+
fromBasin: string;
|
|
55
|
+
toBasin: string;
|
|
56
|
+
fromDistance: number;
|
|
57
|
+
toDistance: number;
|
|
58
|
+
/** Ratio of (distance to OLD basin from CURRENT obs) / (distance to NEW basin from CURRENT obs).
|
|
59
|
+
* > 1 means the obs is clearly closer to NEW than OLD → confident swap. */
|
|
60
|
+
swapConfidence: number;
|
|
61
|
+
/** Plain-English citation. */
|
|
62
|
+
citation: string;
|
|
63
|
+
}
|
|
64
|
+
export interface JanusSessionResult {
|
|
65
|
+
observations: JanusObservation[];
|
|
66
|
+
transitions: JanusTransition[];
|
|
67
|
+
/** Did at least one cross-cluster swap fire? */
|
|
68
|
+
swapDetected: boolean;
|
|
69
|
+
hmac: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Locate which vendor cluster an observation belongs to.
|
|
73
|
+
* Defensive: empty stats → returns basin="unknown" with infinity distance.
|
|
74
|
+
*/
|
|
75
|
+
export declare function locateBasin(fingerprint: Fingerprint): JanusBasin;
|
|
76
|
+
/**
|
|
77
|
+
* Observe a fixture; return {fingerprint, basin}.
|
|
78
|
+
*/
|
|
79
|
+
export declare function observe(fixture: {
|
|
80
|
+
diff: string;
|
|
81
|
+
prDescription: string;
|
|
82
|
+
commitMessages: string[];
|
|
83
|
+
} | Fingerprint): JanusObservation;
|
|
84
|
+
/**
|
|
85
|
+
* Walk a sequence of observations from the SAME session; surface
|
|
86
|
+
* cross-cluster transitions + an HMAC-signed verdict.
|
|
87
|
+
*/
|
|
88
|
+
export declare function detectIdentitySwap(observations: JanusObservation[], opts?: {
|
|
89
|
+
minMargin?: number;
|
|
90
|
+
}): JanusSessionResult;
|
|
91
|
+
/** Verify a session-result's HMAC. */
|
|
92
|
+
export declare function verifyJanusResult(r: JanusSessionResult): boolean;
|
|
93
|
+
//# sourceMappingURL=janus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"janus.d.ts","sourceRoot":"","sources":["../../src/nemesis/janus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAY,MAAM,YAAY,CAAC;AASxD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,aAAa,EAAE,eAAe,GAAG,IAAI,CAAC;IACtC,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,YAAY,EAAE,eAAe,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,WAAW,CAAC;IACzB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB;gFAC4E;IAC5E,cAAc,EAAE,MAAM,CAAC;IACvB,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,gDAAgD;IAChD,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAoBD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,WAAW,GAAG,UAAU,CAyBhE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,WAAW,GAAG,gBAAgB,CAMlI;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,gBAAgB,EAAE,EAChC,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,kBAAkB,CAoCpB;AAWD,sCAAsC;AACtC,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAQhE"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2.53.0 — JANUS organ: cross-vendor cluster-boundary detector.
|
|
3
|
+
*
|
|
4
|
+
* The two-faced Roman god — looks at past + future simultaneously.
|
|
5
|
+
*
|
|
6
|
+
* Closes Eve's identity-swap blind spot from the Million Dollar Secret
|
|
7
|
+
* simulation: MOLT (v2.52) detects INTRA-vendor drift (vendor X drifts
|
|
8
|
+
* over time) but misses CROSS-vendor swaps (vendor X mid-session
|
|
9
|
+
* starts behaving like vendor Y). The classifier might still pick Y as
|
|
10
|
+
* the new winner, but neither MOLT nor verify_identity surfaces the
|
|
11
|
+
* "you crossed a cluster boundary" event.
|
|
12
|
+
*
|
|
13
|
+
* Algorithm:
|
|
14
|
+
* 1. Build cluster centroids from seed corpus (per vendor, per
|
|
15
|
+
* discriminative feature).
|
|
16
|
+
* 2. For an observation O, compute distance d(O, centroid_V) for every
|
|
17
|
+
* known vendor V.
|
|
18
|
+
* 3. Assign O to the nearest centroid → "current basin".
|
|
19
|
+
* 4. Track per-session basin history. If basin changes between
|
|
20
|
+
* observations → cross-cluster transition event.
|
|
21
|
+
* 5. Confidence: distance to old basin / distance to new basin (lower
|
|
22
|
+
* ratio = sharper swap).
|
|
23
|
+
*
|
|
24
|
+
* Different from MOLT because MOLT compares pre/post WINDOWS of the
|
|
25
|
+
* SAME vendor's drift; JANUS detects the MOMENT a vendor's fingerprint
|
|
26
|
+
* crosses into a DIFFERENT vendor's basin.
|
|
27
|
+
*
|
|
28
|
+
* Composes: extractFingerprint + seedStats.
|
|
29
|
+
*
|
|
30
|
+
* Pure deterministic + defensive; never throws.
|
|
31
|
+
*/
|
|
32
|
+
import { createHmac } from "node:crypto";
|
|
33
|
+
import { extractFingerprint } from "./features.js";
|
|
34
|
+
import { seedStats } from "./calibration_corpus.js";
|
|
35
|
+
const KEY_ENV = "MNEME_JANUS_KEY";
|
|
36
|
+
const DEFAULT_KEY = "mneme-janus-v1";
|
|
37
|
+
function keyOf() {
|
|
38
|
+
return process.env[KEY_ENV] ?? DEFAULT_KEY;
|
|
39
|
+
}
|
|
40
|
+
/** Euclidean distance between fingerprint and a vendor's per-feature means. */
|
|
41
|
+
function distanceToCentroid(fp, stats) {
|
|
42
|
+
let sumSq = 0;
|
|
43
|
+
let count = 0;
|
|
44
|
+
for (const k of Object.keys(stats.features)) {
|
|
45
|
+
const v = fp[k];
|
|
46
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
47
|
+
continue;
|
|
48
|
+
const m = stats.features[k].mean;
|
|
49
|
+
const stdev = Math.max(stats.features[k].stdev, Math.abs(m) * 0.1, 1e-3);
|
|
50
|
+
// Normalized squared distance per feature
|
|
51
|
+
const z = (v - m) / stdev;
|
|
52
|
+
sumSq += z * z;
|
|
53
|
+
count++;
|
|
54
|
+
}
|
|
55
|
+
if (count === 0)
|
|
56
|
+
return Number.POSITIVE_INFINITY;
|
|
57
|
+
return Math.sqrt(sumSq / count);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Locate which vendor cluster an observation belongs to.
|
|
61
|
+
* Defensive: empty stats → returns basin="unknown" with infinity distance.
|
|
62
|
+
*/
|
|
63
|
+
export function locateBasin(fingerprint) {
|
|
64
|
+
let stats;
|
|
65
|
+
try {
|
|
66
|
+
stats = seedStats();
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return { basin: "unknown", basinDistance: Number.POSITIVE_INFINITY, secondNearest: null, margin: 0, allDistances: [] };
|
|
70
|
+
}
|
|
71
|
+
const distances = [];
|
|
72
|
+
for (const [vendor, s] of stats) {
|
|
73
|
+
distances.push({ vendor, distance: distanceToCentroid(fingerprint, s) });
|
|
74
|
+
}
|
|
75
|
+
distances.sort((a, b) => a.distance - b.distance);
|
|
76
|
+
if (distances.length === 0) {
|
|
77
|
+
return { basin: "unknown", basinDistance: Number.POSITIVE_INFINITY, secondNearest: null, margin: 0, allDistances: [] };
|
|
78
|
+
}
|
|
79
|
+
const nearest = distances[0];
|
|
80
|
+
const second = distances[1] ?? null;
|
|
81
|
+
const margin = second ? second.distance - nearest.distance : 0;
|
|
82
|
+
return {
|
|
83
|
+
basin: nearest.vendor,
|
|
84
|
+
basinDistance: nearest.distance,
|
|
85
|
+
secondNearest: second,
|
|
86
|
+
margin,
|
|
87
|
+
allDistances: distances,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Observe a fixture; return {fingerprint, basin}.
|
|
92
|
+
*/
|
|
93
|
+
export function observe(fixture) {
|
|
94
|
+
const fp = "multiline_commit_ratio" in fixture
|
|
95
|
+
? fixture
|
|
96
|
+
: extractFingerprint(fixture);
|
|
97
|
+
const basin = locateBasin(fp);
|
|
98
|
+
return { fingerprint: fp, basin };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Walk a sequence of observations from the SAME session; surface
|
|
102
|
+
* cross-cluster transitions + an HMAC-signed verdict.
|
|
103
|
+
*/
|
|
104
|
+
export function detectIdentitySwap(observations, opts = {}) {
|
|
105
|
+
const minMargin = opts.minMargin ?? 0.5;
|
|
106
|
+
const transitions = [];
|
|
107
|
+
if (!Array.isArray(observations) || observations.length < 2) {
|
|
108
|
+
return signed({ observations: observations ?? [], transitions: [], swapDetected: false });
|
|
109
|
+
}
|
|
110
|
+
let prev = observations[0];
|
|
111
|
+
for (let i = 1; i < observations.length; i++) {
|
|
112
|
+
const cur = observations[i];
|
|
113
|
+
if (prev.basin.basin === cur.basin.basin) {
|
|
114
|
+
prev = cur;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// Only surface as a real swap when the new basin is clearly nearer.
|
|
118
|
+
if (cur.basin.margin < minMargin) {
|
|
119
|
+
prev = cur;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// Distance from CURRENT obs to PREVIOUS basin vs current basin
|
|
123
|
+
const prevBasinDistInCur = cur.basin.allDistances.find((d) => d.vendor === prev.basin.basin);
|
|
124
|
+
if (!prevBasinDistInCur || !Number.isFinite(prevBasinDistInCur.distance)) {
|
|
125
|
+
prev = cur;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const swapConfidence = prevBasinDistInCur.distance / Math.max(cur.basin.basinDistance, 1e-6);
|
|
129
|
+
transitions.push({
|
|
130
|
+
fromBasin: prev.basin.basin,
|
|
131
|
+
toBasin: cur.basin.basin,
|
|
132
|
+
fromDistance: prevBasinDistInCur.distance,
|
|
133
|
+
toDistance: cur.basin.basinDistance,
|
|
134
|
+
swapConfidence,
|
|
135
|
+
citation: `JANUS: identity swap detected — fingerprint crossed from ${prev.basin.basin} basin to ${cur.basin.basin} basin (swap confidence ${swapConfidence.toFixed(2)}x).`,
|
|
136
|
+
});
|
|
137
|
+
prev = cur;
|
|
138
|
+
}
|
|
139
|
+
return signed({ observations, transitions, swapDetected: transitions.length > 0 });
|
|
140
|
+
}
|
|
141
|
+
function signed(body) {
|
|
142
|
+
const hmac = createHmac("sha256", keyOf()).update(JSON.stringify({
|
|
143
|
+
transitions: body.transitions,
|
|
144
|
+
swapDetected: body.swapDetected,
|
|
145
|
+
obsCount: body.observations.length,
|
|
146
|
+
})).digest("hex");
|
|
147
|
+
return { ...body, hmac };
|
|
148
|
+
}
|
|
149
|
+
/** Verify a session-result's HMAC. */
|
|
150
|
+
export function verifyJanusResult(r) {
|
|
151
|
+
if (!r || typeof r.hmac !== "string")
|
|
152
|
+
return false;
|
|
153
|
+
const expected = createHmac("sha256", keyOf()).update(JSON.stringify({
|
|
154
|
+
transitions: r.transitions,
|
|
155
|
+
swapDetected: r.swapDetected,
|
|
156
|
+
obsCount: r.observations.length,
|
|
157
|
+
})).digest("hex");
|
|
158
|
+
return expected === r.hmac;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=janus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"janus.js","sourceRoot":"","sources":["../../src/nemesis/janus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,SAAS,EAAoB,MAAM,yBAAyB,CAAC;AAGtE,MAAM,OAAO,GAAG,iBAAiB,CAAC;AAClC,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAErC,SAAS,KAAK;IACZ,OAAO,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,WAAW,CAAC;AAC7C,CAAC;AA6CD,+EAA+E;AAC/E,SAAS,kBAAkB,CAAC,EAAe,EAAE,KAAkB;IAC7D,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5C,MAAM,CAAC,GAAI,EAAwC,CAAC,CAAC,CAAC,CAAC;QACvD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,SAAS;QAC3D,MAAM,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC;QAClC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC,CAAC;QAC1E,0CAA0C;QAC1C,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;QAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,KAAK,EAAE,CAAC;IACV,CAAC;IACD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,iBAAiB,CAAC;IACjD,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,WAAwB;IAClD,IAAI,KAAiC,CAAC;IACtC,IAAI,CAAC;QACH,KAAK,GAAG,SAAS,EAAE,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,CAAC,iBAAiB,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;IACzH,CAAC;IACD,MAAM,SAAS,GAAsB,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;QAChC,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IAClD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,CAAC,iBAAiB,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;IACzH,CAAC;IACD,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAE,CAAC;IAC9B,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IACpC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,MAAM;QACrB,aAAa,EAAE,OAAO,CAAC,QAAQ;QAC/B,aAAa,EAAE,MAAM;QACrB,MAAM;QACN,YAAY,EAAE,SAAS;KACxB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,OAAwF;IAC9G,MAAM,EAAE,GAAG,wBAAwB,IAAK,OAAkB;QACxD,CAAC,CAAE,OAAuB;QAC1B,CAAC,CAAC,kBAAkB,CAAC,OAA4E,CAAC,CAAC;IACrG,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9B,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,YAAgC,EAChC,OAA+B,EAAE;IAEjC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC;IACxC,MAAM,WAAW,GAAsB,EAAE,CAAC;IAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5D,OAAO,MAAM,CAAC,EAAE,YAAY,EAAE,YAAY,IAAI,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,IAAI,IAAI,GAAG,YAAY,CAAC,CAAC,CAAE,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7C,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,CAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,KAAK,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACzC,IAAI,GAAG,GAAG,CAAC;YACX,SAAS;QACX,CAAC;QACD,oEAAoE;QACpE,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;YACjC,IAAI,GAAG,GAAG,CAAC;YACX,SAAS;QACX,CAAC;QACD,+DAA+D;QAC/D,MAAM,kBAAkB,GAAG,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC7F,IAAI,CAAC,kBAAkB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzE,IAAI,GAAG,GAAG,CAAC;YACX,SAAS;QACX,CAAC;QACD,MAAM,cAAc,GAAG,kBAAkB,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QAC7F,WAAW,CAAC,IAAI,CAAC;YACf,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK;YAC3B,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK;YACxB,YAAY,EAAE,kBAAkB,CAAC,QAAQ;YACzC,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,aAAa;YACnC,cAAc;YACd,QAAQ,EAAE,4DAA4D,IAAI,CAAC,KAAK,CAAC,KAAK,aAAa,GAAG,CAAC,KAAK,CAAC,KAAK,2BAA2B,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK;SAC5K,CAAC,CAAC;QACH,IAAI,GAAG,GAAG,CAAC;IACb,CAAC;IACD,OAAO,MAAM,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC;AACrF,CAAC;AAED,SAAS,MAAM,CAAC,IAAsC;IACpD,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;QAC/D,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM;KACnC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAClB,OAAO,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,iBAAiB,CAAC,CAAqB;IACrD,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACnD,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;QACnE,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,QAAQ,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;KAChC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAClB,OAAO,QAAQ,KAAK,CAAC,CAAC,IAAI,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2.53.0 — HMAC KEY SETUP WIZARD + STRICT mode.
|
|
3
|
+
*
|
|
4
|
+
* Closes P0-1 from the v2.52 session audit: pre-v2.53 NEMESIS warned LOUD
|
|
5
|
+
* when falling back to the public default key, but didn't auto-fix it
|
|
6
|
+
* + didn't refuse to operate. Result: every developer who ignored the
|
|
7
|
+
* warning shipped HMAC receipts an attacker can forge.
|
|
8
|
+
*
|
|
9
|
+
* v2.53 protocol:
|
|
10
|
+
* 1. First-run wizard auto-generates a 32-byte random key + writes it
|
|
11
|
+
* to .mneme/nemesis/hmac.key with 0o600 perms (owner-only).
|
|
12
|
+
* 2. MNEME_NEMESIS_STRICT=1 env var → key_management refuses to return
|
|
13
|
+
* the default-insecure key (throws instead). For CI / production /
|
|
14
|
+
* regulated environments.
|
|
15
|
+
* 3. Idempotent: re-running detects existing key + skips generation.
|
|
16
|
+
*
|
|
17
|
+
* Defensive: never throws on filesystem errors (best-effort), only the
|
|
18
|
+
* STRICT-mode refusal is intentional.
|
|
19
|
+
*/
|
|
20
|
+
export interface KeyWizardResult {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
action: "generated-repo" | "generated-user" | "already-present" | "skipped" | "failed";
|
|
23
|
+
path?: string;
|
|
24
|
+
/** Length of the resolved key (or 0 on fail). */
|
|
25
|
+
keyLength: number;
|
|
26
|
+
reason: string;
|
|
27
|
+
}
|
|
28
|
+
export interface KeyWizardOpts {
|
|
29
|
+
repoRoot: string;
|
|
30
|
+
/** "repo" → write to <repoRoot>/.mneme/nemesis/hmac.key (default).
|
|
31
|
+
* "user" → write to ~/.mneme/nemesis/hmac.key (cross-repo). */
|
|
32
|
+
target?: "repo" | "user";
|
|
33
|
+
/** Re-generate even if a key already exists. Default false. */
|
|
34
|
+
force?: boolean;
|
|
35
|
+
/** Don't write — only compute the would-be path + key length. */
|
|
36
|
+
dryRun?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Run the key setup wizard. Idempotent; never overwrites an existing
|
|
40
|
+
* key unless force=true. Returns structured outcome.
|
|
41
|
+
*/
|
|
42
|
+
export declare function runKeyWizard(opts: KeyWizardOpts): KeyWizardResult;
|
|
43
|
+
/**
|
|
44
|
+
* Check key file permissions (Unix only). Returns warning on world-readable
|
|
45
|
+
* files. On Windows, returns ok=true with note since chmod is a no-op.
|
|
46
|
+
*/
|
|
47
|
+
export declare function checkKeyPermissions(repoRoot: string): {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
mode?: number;
|
|
50
|
+
reason: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* STRICT MODE check: throws when the user is operating with a
|
|
54
|
+
* default-insecure HMAC key AND has opted into strict enforcement
|
|
55
|
+
* via MNEME_NEMESIS_STRICT=1. Call this from any code path that
|
|
56
|
+
* issues forensic-grade receipts (EU stamp / cli-activity / SIBYL).
|
|
57
|
+
*
|
|
58
|
+
* Non-strict mode: returns { ok: false, message } so caller can decide.
|
|
59
|
+
*/
|
|
60
|
+
export declare function strictKeyCheck(repoRoot: string): {
|
|
61
|
+
ok: boolean;
|
|
62
|
+
usingDefault: boolean;
|
|
63
|
+
message: string;
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=key_setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key_setup.d.ts","sourceRoot":"","sources":["../../src/nemesis/key_setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAUH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,SAAS,GAAG,QAAQ,CAAC;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB;oEACgE;IAChE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iEAAiE;IACjE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAYD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,eAAe,CAyDjE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAiBpG;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAiCxG"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2.53.0 — HMAC KEY SETUP WIZARD + STRICT mode.
|
|
3
|
+
*
|
|
4
|
+
* Closes P0-1 from the v2.52 session audit: pre-v2.53 NEMESIS warned LOUD
|
|
5
|
+
* when falling back to the public default key, but didn't auto-fix it
|
|
6
|
+
* + didn't refuse to operate. Result: every developer who ignored the
|
|
7
|
+
* warning shipped HMAC receipts an attacker can forge.
|
|
8
|
+
*
|
|
9
|
+
* v2.53 protocol:
|
|
10
|
+
* 1. First-run wizard auto-generates a 32-byte random key + writes it
|
|
11
|
+
* to .mneme/nemesis/hmac.key with 0o600 perms (owner-only).
|
|
12
|
+
* 2. MNEME_NEMESIS_STRICT=1 env var → key_management refuses to return
|
|
13
|
+
* the default-insecure key (throws instead). For CI / production /
|
|
14
|
+
* regulated environments.
|
|
15
|
+
* 3. Idempotent: re-running detects existing key + skips generation.
|
|
16
|
+
*
|
|
17
|
+
* Defensive: never throws on filesystem errors (best-effort), only the
|
|
18
|
+
* STRICT-mode refusal is intentional.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync, chmodSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { randomBytes } from "node:crypto";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
const REPO_KEY_REL = ".mneme/nemesis/hmac.key";
|
|
25
|
+
const USER_KEY_REL = ".mneme/nemesis/hmac.key";
|
|
26
|
+
function keyPath(opts) {
|
|
27
|
+
if (opts.target === "user")
|
|
28
|
+
return join(homedir(), USER_KEY_REL);
|
|
29
|
+
return join(opts.repoRoot, REPO_KEY_REL);
|
|
30
|
+
}
|
|
31
|
+
function envHasValidKey() {
|
|
32
|
+
const k = process.env["MNEME_NEMESIS_KEY"];
|
|
33
|
+
return typeof k === "string" && k.length >= 16;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Run the key setup wizard. Idempotent; never overwrites an existing
|
|
37
|
+
* key unless force=true. Returns structured outcome.
|
|
38
|
+
*/
|
|
39
|
+
export function runKeyWizard(opts) {
|
|
40
|
+
try {
|
|
41
|
+
if (envHasValidKey() && !opts.force) {
|
|
42
|
+
const env = process.env["MNEME_NEMESIS_KEY"];
|
|
43
|
+
return {
|
|
44
|
+
ok: true,
|
|
45
|
+
action: "skipped",
|
|
46
|
+
keyLength: env.length,
|
|
47
|
+
reason: "MNEME_NEMESIS_KEY already set — no file action needed",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const p = keyPath(opts);
|
|
51
|
+
if (existsSync(p) && !opts.force) {
|
|
52
|
+
try {
|
|
53
|
+
const k = readFileSync(p, "utf8").trim();
|
|
54
|
+
if (k.length >= 16) {
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
action: "already-present",
|
|
58
|
+
path: p,
|
|
59
|
+
keyLength: k.length,
|
|
60
|
+
reason: `existing key at ${p} (${k.length} chars)`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch { /* fall through to regenerate */ }
|
|
65
|
+
}
|
|
66
|
+
if (opts.dryRun) {
|
|
67
|
+
return {
|
|
68
|
+
ok: true,
|
|
69
|
+
action: opts.target === "user" ? "generated-user" : "generated-repo",
|
|
70
|
+
path: p,
|
|
71
|
+
keyLength: 64,
|
|
72
|
+
reason: `dry-run: would write a fresh 64-char hex key to ${p}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Generate + write
|
|
76
|
+
const dir = p.replace(/[\/\\][^\/\\]+$/, "");
|
|
77
|
+
try {
|
|
78
|
+
mkdirSync(dir, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
catch { /* ok */ }
|
|
81
|
+
const key = randomBytes(32).toString("hex");
|
|
82
|
+
writeFileSync(p, key + "\n", { mode: 0o600 });
|
|
83
|
+
// Best-effort chmod (Windows ignores)
|
|
84
|
+
try {
|
|
85
|
+
chmodSync(p, 0o600);
|
|
86
|
+
}
|
|
87
|
+
catch { /* */ }
|
|
88
|
+
return {
|
|
89
|
+
ok: true,
|
|
90
|
+
action: opts.target === "user" ? "generated-user" : "generated-repo",
|
|
91
|
+
path: p,
|
|
92
|
+
keyLength: key.length,
|
|
93
|
+
reason: `generated fresh 64-char hex key at ${p} (mode 0600)`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
action: "failed",
|
|
100
|
+
keyLength: 0,
|
|
101
|
+
reason: `wizard failed: ${e.message}`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check key file permissions (Unix only). Returns warning on world-readable
|
|
107
|
+
* files. On Windows, returns ok=true with note since chmod is a no-op.
|
|
108
|
+
*/
|
|
109
|
+
export function checkKeyPermissions(repoRoot) {
|
|
110
|
+
const p = join(repoRoot, REPO_KEY_REL);
|
|
111
|
+
if (!existsSync(p))
|
|
112
|
+
return { ok: true, reason: "no key file present" };
|
|
113
|
+
try {
|
|
114
|
+
const s = statSync(p);
|
|
115
|
+
const mode = s.mode & 0o777;
|
|
116
|
+
if (process.platform === "win32") {
|
|
117
|
+
return { ok: true, mode, reason: "Windows: chmod is a no-op; key access controlled by NTFS ACLs" };
|
|
118
|
+
}
|
|
119
|
+
// Owner-only is the goal: 0o600
|
|
120
|
+
if (mode === 0o600 || mode === 0o400) {
|
|
121
|
+
return { ok: true, mode, reason: `mode ${mode.toString(8)} — owner-only (correct)` };
|
|
122
|
+
}
|
|
123
|
+
return { ok: false, mode, reason: `mode ${mode.toString(8)} is too permissive; should be 600 (owner read/write only)` };
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
return { ok: false, reason: `stat failed: ${e.message}` };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* STRICT MODE check: throws when the user is operating with a
|
|
131
|
+
* default-insecure HMAC key AND has opted into strict enforcement
|
|
132
|
+
* via MNEME_NEMESIS_STRICT=1. Call this from any code path that
|
|
133
|
+
* issues forensic-grade receipts (EU stamp / cli-activity / SIBYL).
|
|
134
|
+
*
|
|
135
|
+
* Non-strict mode: returns { ok: false, message } so caller can decide.
|
|
136
|
+
*/
|
|
137
|
+
export function strictKeyCheck(repoRoot) {
|
|
138
|
+
// Resolve via the same path key_management uses.
|
|
139
|
+
const env = process.env["MNEME_NEMESIS_KEY"];
|
|
140
|
+
if (typeof env === "string" && env.length >= 16) {
|
|
141
|
+
return { ok: true, usingDefault: false, message: "MNEME_NEMESIS_KEY env var set" };
|
|
142
|
+
}
|
|
143
|
+
const repo = join(repoRoot, REPO_KEY_REL);
|
|
144
|
+
if (existsSync(repo)) {
|
|
145
|
+
try {
|
|
146
|
+
const k = readFileSync(repo, "utf8").trim();
|
|
147
|
+
if (k.length >= 16)
|
|
148
|
+
return { ok: true, usingDefault: false, message: `repo key at ${repo}` };
|
|
149
|
+
}
|
|
150
|
+
catch { /* */ }
|
|
151
|
+
}
|
|
152
|
+
const user = join(homedir(), USER_KEY_REL);
|
|
153
|
+
if (existsSync(user)) {
|
|
154
|
+
try {
|
|
155
|
+
const k = readFileSync(user, "utf8").trim();
|
|
156
|
+
if (k.length >= 16)
|
|
157
|
+
return { ok: true, usingDefault: false, message: `user key at ${user}` };
|
|
158
|
+
}
|
|
159
|
+
catch { /* */ }
|
|
160
|
+
}
|
|
161
|
+
const strict = process.env["MNEME_NEMESIS_STRICT"] === "1";
|
|
162
|
+
if (strict) {
|
|
163
|
+
throw new Error("STRICT MODE: MNEME_NEMESIS_STRICT=1 set + no production key configured. " +
|
|
164
|
+
`Run: node -e "require('@mneme-ai/core').nemesis.runKeyWizard({repoRoot:'${repoRoot}'})" ` +
|
|
165
|
+
"OR set MNEME_NEMESIS_KEY env var (≥16 chars).");
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
usingDefault: true,
|
|
170
|
+
message: "using default-insecure HMAC key — receipts are forgeable. Set MNEME_NEMESIS_STRICT=1 to enforce.",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=key_setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key_setup.js","sourceRoot":"","sources":["../../src/nemesis/key_setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v2.54.0 — LETHE: GDPR forget primitive (Merkle exclusion proof).
|
|
3
|
+
*
|
|
4
|
+
* Greek mythology: river in Hades that erases memory.
|
|
5
|
+
*
|
|
6
|
+
* Problem: EU GDPR Art 17 (right to erasure) + AI-specific provisions
|
|
7
|
+
* require that personal data — including fingerprint-style behavioural
|
|
8
|
+
* markers — be REMOVABLE on request. But Mneme's HMAC chains are
|
|
9
|
+
* tamper-evident by design: removing rows breaks the chain.
|
|
10
|
+
*
|
|
11
|
+
* Solution (cryptographically rigorous, not just "delete"):
|
|
12
|
+
* 1. Build a Merkle tree over the chain rows.
|
|
13
|
+
* 2. To "forget" row R, REPLACE the row's leaf with `H("forgotten:" || nonce)`
|
|
14
|
+
* where nonce is fresh per request.
|
|
15
|
+
* 3. Recompute the Merkle root from the modified leaves.
|
|
16
|
+
* 4. Issue a FORGET RECEIPT: signed proof tree showing
|
|
17
|
+
* (a) the row WAS in the chain (by original leaf hash, kept ONLY as
|
|
18
|
+
* the path to the root — never the content)
|
|
19
|
+
* (b) the row is NOW erased (replacement leaf)
|
|
20
|
+
* (c) the new Merkle root that subsequent verifiers can use
|
|
21
|
+
* (d) timestamp + GDPR jurisdiction tag
|
|
22
|
+
*
|
|
23
|
+
* Verifiers downstream:
|
|
24
|
+
* - Future readers see the new root + the FORGET ENVELOPE; they cannot
|
|
25
|
+
* recover the original content (it was the only place stored).
|
|
26
|
+
* - The exclusion proof is a few hundred bytes regardless of chain size.
|
|
27
|
+
*
|
|
28
|
+
* Composes: createHash + the existing HMAC-chained ledger pattern.
|
|
29
|
+
* Pure deterministic + defensive; never throws.
|
|
30
|
+
*/
|
|
31
|
+
export interface MerkleProofStep {
|
|
32
|
+
/** Sibling hash at this level. */
|
|
33
|
+
sibling: string;
|
|
34
|
+
/** Position of the current node relative to its sibling. */
|
|
35
|
+
position: "left" | "right";
|
|
36
|
+
}
|
|
37
|
+
export interface MerkleTree {
|
|
38
|
+
/** Root hash. */
|
|
39
|
+
root: string;
|
|
40
|
+
/** Leaf hashes (in input order). */
|
|
41
|
+
leaves: string[];
|
|
42
|
+
/** Total number of leaves. */
|
|
43
|
+
count: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build a Merkle tree from a list of row contents. Odd-count layers
|
|
47
|
+
* duplicate the last element (standard Bitcoin-style construction).
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildMerkleTree(rows: string[]): MerkleTree;
|
|
50
|
+
/**
|
|
51
|
+
* Build an inclusion proof for the leaf at `index`.
|
|
52
|
+
* Returns the sibling path from the leaf up to (but not including) the root.
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildInclusionProof(rows: string[], index: number): {
|
|
55
|
+
proof: MerkleProofStep[];
|
|
56
|
+
root: string;
|
|
57
|
+
leafHash: string;
|
|
58
|
+
} | null;
|
|
59
|
+
/** Verify an inclusion proof. Pure. */
|
|
60
|
+
export declare function verifyInclusionProof(leafHash: string, proof: MerkleProofStep[], expectedRoot: string): boolean;
|
|
61
|
+
export interface ForgetReceipt {
|
|
62
|
+
/** ISO-8601 timestamp of the forget request. */
|
|
63
|
+
at: string;
|
|
64
|
+
/** GDPR jurisdiction tag (EU/UK/etc) — for the audit trail. */
|
|
65
|
+
jurisdiction: string;
|
|
66
|
+
/** Source ledger identifier (e.g. "cli-activity.jsonl" / "drift-cursor.jsonl"). */
|
|
67
|
+
ledger: string;
|
|
68
|
+
/** Row index that was forgotten. */
|
|
69
|
+
forgottenIndex: number;
|
|
70
|
+
/** Inclusion proof THAT THE ROW WAS THERE (original leaf hash + path). */
|
|
71
|
+
originalLeafHash: string;
|
|
72
|
+
/** Replacement leaf hash. */
|
|
73
|
+
replacementLeafHash: string;
|
|
74
|
+
/** Merkle proof of inclusion of the original leaf in the ORIGINAL root. */
|
|
75
|
+
inclusionProof: MerkleProofStep[];
|
|
76
|
+
/** Original Merkle root (before erase). */
|
|
77
|
+
originalRoot: string;
|
|
78
|
+
/** New Merkle root (after erase). */
|
|
79
|
+
newRoot: string;
|
|
80
|
+
/** HMAC over the canonical receipt body. */
|
|
81
|
+
hmac: string;
|
|
82
|
+
}
|
|
83
|
+
export interface ForgetInput {
|
|
84
|
+
repoRoot: string;
|
|
85
|
+
ledgerRelative: string;
|
|
86
|
+
/** Which row to forget (0-based). */
|
|
87
|
+
rowIndex: number;
|
|
88
|
+
jurisdiction?: string;
|
|
89
|
+
/** Don't actually rewrite the ledger; only build the receipt. */
|
|
90
|
+
dryRun?: boolean;
|
|
91
|
+
}
|
|
92
|
+
export interface ForgetResult {
|
|
93
|
+
ok: boolean;
|
|
94
|
+
receipt?: ForgetReceipt;
|
|
95
|
+
newLedgerPath?: string;
|
|
96
|
+
backupPath?: string;
|
|
97
|
+
reason: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Read a JSONL ledger + perform GDPR forget on a specific row. Pure
|
|
101
|
+
* cryptographic operation; ledger rewrite + backup are best-effort.
|
|
102
|
+
*/
|
|
103
|
+
export declare function forgetRow(input: ForgetInput): ForgetResult;
|
|
104
|
+
/** Verify a forget receipt cryptographically — does the inclusion proof
|
|
105
|
+
* reconstruct the original root + does the HMAC match? Pure. */
|
|
106
|
+
export declare function verifyForgetReceipt(receipt: ForgetReceipt): {
|
|
107
|
+
ok: boolean;
|
|
108
|
+
reason: string;
|
|
109
|
+
};
|
|
110
|
+
/** List all forget receipts in this repo. */
|
|
111
|
+
export declare function listForgetReceipts(repoRoot: string): ForgetReceipt[];
|
|
112
|
+
//# sourceMappingURL=lethe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lethe.d.ts","sourceRoot":"","sources":["../../src/nemesis/lethe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAuBH,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAiB1D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,eAAe,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAsBtI;AAED,uCAAuC;AACvC,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE,YAAY,EAAE,MAAM,GAC/D,OAAO,CAQT;AAMD,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,EAAE,EAAE,MAAM,CAAC;IACX,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,gBAAgB,EAAE,MAAM,CAAC;IACzB,6BAA6B;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,2EAA2E;IAC3E,cAAc,EAAE,eAAe,EAAE,CAAC;IAClC,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;CACd;AAMD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,CAqD1D;AAED;iEACiE;AACjE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAQ3F;AAED,6CAA6C;AAC7C,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CASpE"}
|