0nmcp 1.7.0 → 2.0.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.
@@ -0,0 +1,254 @@
1
+ // ============================================================
2
+ // 0nMCP — Vault: Semantic Layer Manager
3
+ // ============================================================
4
+ // 7 named layers, each independently encrypted with AES-256-GCM.
5
+ // The credentials layer (layer 2) uses double-encryption via
6
+ // Argon2id for maximum protection of API keys and secrets.
7
+ //
8
+ // Patent Pending: US Provisional Patent Application #63/990,046
9
+ // ============================================================
10
+
11
+ import { encryptAES, decryptAES, deriveKeyArgon2, encryptAESRaw, decryptAESRaw, SALT_LENGTH } from "./crypto-container.js";
12
+ import { randomBytes } from "crypto";
13
+
14
+ // ── Layer Definitions ────────────────────────────────────────
15
+
16
+ export const LAYER_NAMES = [
17
+ "workflows", // 1: .0n workflow definitions
18
+ "credentials", // 2: API keys, tokens, secrets (double-encrypted)
19
+ "env_vars", // 3: Environment variables
20
+ "mcp_configs", // 4: MCP server configurations
21
+ "site_profiles", // 5: CRO9/site configs
22
+ "ai_brain", // 6: AI agent data, prompts, memory
23
+ "audit_trail", // 7: Execution logs, timestamps (append-only)
24
+ ];
25
+
26
+ export const LAYER_COUNT = LAYER_NAMES.length;
27
+
28
+ /**
29
+ * Check if a layer name is valid.
30
+ * @param {string} name
31
+ * @returns {boolean}
32
+ */
33
+ export function isValidLayer(name) {
34
+ return LAYER_NAMES.includes(name);
35
+ }
36
+
37
+ /**
38
+ * Get the index of a layer by name.
39
+ * @param {string} name
40
+ * @returns {number} 0-based index, or -1 if not found
41
+ */
42
+ export function getLayerIndex(name) {
43
+ return LAYER_NAMES.indexOf(name);
44
+ }
45
+
46
+ // ── Layer Encryption ─────────────────────────────────────────
47
+
48
+ /**
49
+ * Seal (encrypt) a single layer's data.
50
+ *
51
+ * @param {string} name - Layer name
52
+ * @param {any} data - Data to encrypt (will be JSON.stringify'd)
53
+ * @param {string} passphrase - Encryption passphrase
54
+ * @returns {{ name: string, ciphertext: Buffer, iv: Buffer, salt: Buffer, tag: Buffer }}
55
+ */
56
+ export function sealLayer(name, data, passphrase) {
57
+ if (!isValidLayer(name)) {
58
+ throw new Error(`Invalid layer name: "${name}". Valid: ${LAYER_NAMES.join(", ")}`);
59
+ }
60
+
61
+ const plaintext = JSON.stringify(data);
62
+ const result = encryptAES(plaintext, passphrase);
63
+
64
+ return {
65
+ name,
66
+ ciphertext: result.ciphertext,
67
+ iv: result.iv,
68
+ salt: result.salt,
69
+ tag: result.tag,
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Unseal (decrypt) a single layer's data.
75
+ *
76
+ * @param {string} name - Layer name
77
+ * @param {Buffer} ciphertext
78
+ * @param {string} passphrase
79
+ * @param {Buffer} iv
80
+ * @param {Buffer} salt
81
+ * @param {Buffer} tag
82
+ * @returns {any} Parsed JSON data
83
+ */
84
+ export function unsealLayer(name, ciphertext, passphrase, iv, salt, tag) {
85
+ if (!isValidLayer(name)) {
86
+ throw new Error(`Invalid layer name: "${name}"`);
87
+ }
88
+
89
+ const decrypted = decryptAES(ciphertext, passphrase, iv, salt, tag);
90
+ return JSON.parse(decrypted.toString("utf8"));
91
+ }
92
+
93
+ // ── Credentials Double-Encryption ────────────────────────────
94
+
95
+ /**
96
+ * Seal credentials with double encryption:
97
+ * 1. First layer: AES-256-GCM with passphrase
98
+ * 2. Second layer: AES-256-GCM with Argon2id-derived key
99
+ *
100
+ * @param {any} data - Credentials data
101
+ * @param {string} passphrase - Encryption passphrase
102
+ * @returns {Promise<{ ciphertext: Buffer, iv: Buffer, salt: Buffer, tag: Buffer, argonSalt: Buffer, innerIv: Buffer, innerTag: Buffer }>}
103
+ */
104
+ export async function sealCredentials(data, passphrase) {
105
+ const plaintext = Buffer.from(JSON.stringify(data), "utf8");
106
+
107
+ // Inner layer: Argon2id-derived key
108
+ const argonSalt = randomBytes(SALT_LENGTH);
109
+ const argonKey = await deriveKeyArgon2(passphrase, argonSalt);
110
+ const inner = encryptAESRaw(plaintext, argonKey);
111
+
112
+ // Outer layer: standard AES-256-GCM with passphrase
113
+ // Combine inner ciphertext + iv + tag for outer encryption
114
+ const innerPayload = Buffer.concat([inner.iv, inner.tag, inner.ciphertext]);
115
+ const outer = encryptAES(innerPayload, passphrase);
116
+
117
+ return {
118
+ ciphertext: outer.ciphertext,
119
+ iv: outer.iv,
120
+ salt: outer.salt,
121
+ tag: outer.tag,
122
+ argonSalt,
123
+ innerIv: inner.iv,
124
+ innerTag: inner.tag,
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Unseal double-encrypted credentials.
130
+ *
131
+ * @param {Buffer} ciphertext
132
+ * @param {string} passphrase
133
+ * @param {Buffer} iv
134
+ * @param {Buffer} salt
135
+ * @param {Buffer} tag
136
+ * @param {Buffer} argonSalt
137
+ * @returns {Promise<any>} Parsed credentials
138
+ */
139
+ export async function unsealCredentials(ciphertext, passphrase, iv, salt, tag, argonSalt) {
140
+ // Outer layer: decrypt with passphrase
141
+ const innerPayload = decryptAES(ciphertext, passphrase, iv, salt, tag);
142
+
143
+ // Extract inner components (iv:12 + tag:16 + ciphertext:rest)
144
+ const innerIv = innerPayload.subarray(0, 12);
145
+ const innerTag = innerPayload.subarray(12, 28);
146
+ const innerCiphertext = innerPayload.subarray(28);
147
+
148
+ // Inner layer: Argon2id-derived key
149
+ const argonKey = await deriveKeyArgon2(passphrase, argonSalt);
150
+ const decrypted = decryptAESRaw(innerCiphertext, argonKey, innerIv, innerTag);
151
+
152
+ return JSON.parse(decrypted.toString("utf8"));
153
+ }
154
+
155
+ // ── Bulk Layer Operations ────────────────────────────────────
156
+
157
+ /**
158
+ * Seal multiple layers at once.
159
+ *
160
+ * @param {Object<string, any>} layerData - { layerName: data }
161
+ * @param {string} passphrase
162
+ * @returns {Promise<Map<string, object>>} Sealed layers
163
+ */
164
+ export async function sealAllLayers(layerData, passphrase) {
165
+ const sealed = new Map();
166
+
167
+ for (const [name, data] of Object.entries(layerData)) {
168
+ if (!isValidLayer(name)) {
169
+ throw new Error(`Invalid layer: "${name}"`);
170
+ }
171
+
172
+ if (name === "credentials") {
173
+ const result = await sealCredentials(data, passphrase);
174
+ sealed.set(name, { ...result, doubleEncrypted: true });
175
+ } else {
176
+ const result = sealLayer(name, data, passphrase);
177
+ sealed.set(name, result);
178
+ }
179
+ }
180
+
181
+ return sealed;
182
+ }
183
+
184
+ /**
185
+ * Unseal multiple layers at once.
186
+ *
187
+ * @param {Map<string, object>} sealedLayers
188
+ * @param {string} passphrase
189
+ * @param {string[]} [onlyLayers] - Optional filter: only unseal these layers
190
+ * @returns {Promise<Object<string, any>>} Unsealed layer data
191
+ */
192
+ export async function unsealAllLayers(sealedLayers, passphrase, onlyLayers = null) {
193
+ const result = {};
194
+
195
+ for (const [name, layerInfo] of sealedLayers) {
196
+ if (onlyLayers && !onlyLayers.includes(name)) continue;
197
+
198
+ if (name === "credentials" && layerInfo.doubleEncrypted) {
199
+ result[name] = await unsealCredentials(
200
+ layerInfo.ciphertext, passphrase,
201
+ layerInfo.iv, layerInfo.salt, layerInfo.tag,
202
+ layerInfo.argonSalt
203
+ );
204
+ } else {
205
+ result[name] = unsealLayer(
206
+ name, layerInfo.ciphertext, passphrase,
207
+ layerInfo.iv, layerInfo.salt, layerInfo.tag
208
+ );
209
+ }
210
+ }
211
+
212
+ return result;
213
+ }
214
+
215
+ // ── Layer Access Matrix ──────────────────────────────────────
216
+
217
+ /**
218
+ * Create a layer access matrix for escrow parties.
219
+ *
220
+ * @param {Object<string, string[]>} partyAccess - { partyId: [layerNames] }
221
+ * @returns {Object<string, Object<string, boolean>>} Full matrix
222
+ */
223
+ export function createAccessMatrix(partyAccess) {
224
+ const matrix = {};
225
+
226
+ for (const [partyId, layers] of Object.entries(partyAccess)) {
227
+ matrix[partyId] = {};
228
+ for (const layerName of LAYER_NAMES) {
229
+ matrix[partyId][layerName] = layers.includes(layerName);
230
+ }
231
+ }
232
+
233
+ return matrix;
234
+ }
235
+
236
+ /**
237
+ * Validate an access matrix.
238
+ *
239
+ * @param {Object} matrix
240
+ * @returns {{ valid: boolean, errors: string[] }}
241
+ */
242
+ export function validateAccessMatrix(matrix) {
243
+ const errors = [];
244
+
245
+ for (const [partyId, access] of Object.entries(matrix)) {
246
+ for (const layerName of Object.keys(access)) {
247
+ if (!isValidLayer(layerName)) {
248
+ errors.push(`Party "${partyId}": invalid layer "${layerName}"`);
249
+ }
250
+ }
251
+ }
252
+
253
+ return { valid: errors.length === 0, errors };
254
+ }
@@ -0,0 +1,159 @@
1
+ // ============================================================
2
+ // 0nMCP — Vault: Transfer Registry
3
+ // ============================================================
4
+ // In-memory + file-backed registry for tracking vault transfers.
5
+ // Prevents replay attacks by rejecting duplicate transfer IDs.
6
+ //
7
+ // Patent Pending: US Provisional Patent Application #63/990,046
8
+ // ============================================================
9
+
10
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
11
+ import { join } from "path";
12
+ import { homedir } from "os";
13
+
14
+ const VAULT_DIR = join(homedir(), ".0n", "vault");
15
+ const REGISTRY_PATH = join(VAULT_DIR, "transfers.json");
16
+
17
+ // In-memory cache
18
+ let registry = null;
19
+
20
+ /**
21
+ * Ensure vault directory exists and load registry from disk.
22
+ */
23
+ function ensureRegistry() {
24
+ if (registry !== null) return;
25
+
26
+ if (!existsSync(VAULT_DIR)) {
27
+ mkdirSync(VAULT_DIR, { recursive: true });
28
+ }
29
+
30
+ if (existsSync(REGISTRY_PATH)) {
31
+ try {
32
+ registry = JSON.parse(readFileSync(REGISTRY_PATH, "utf-8"));
33
+ } catch {
34
+ registry = { transfers: {} };
35
+ }
36
+ } else {
37
+ registry = { transfers: {} };
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Save registry to disk.
43
+ */
44
+ function saveRegistry() {
45
+ if (!existsSync(VAULT_DIR)) {
46
+ mkdirSync(VAULT_DIR, { recursive: true });
47
+ }
48
+ writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
49
+ }
50
+
51
+ /**
52
+ * Register a new transfer.
53
+ *
54
+ * @param {string} transferId - UUID v4
55
+ * @param {string} sealHex - Seal of Truth hex string
56
+ * @param {Object} metadata - Additional metadata
57
+ * @returns {{ success: boolean, error?: string }}
58
+ */
59
+ export function registerTransfer(transferId, sealHex, metadata = {}) {
60
+ ensureRegistry();
61
+
62
+ // Replay prevention
63
+ if (registry.transfers[transferId]) {
64
+ return {
65
+ success: false,
66
+ error: `Transfer ID "${transferId}" already registered — replay rejected`,
67
+ };
68
+ }
69
+
70
+ registry.transfers[transferId] = {
71
+ seal: sealHex,
72
+ registered_at: new Date().toISOString(),
73
+ status: "active",
74
+ ...metadata,
75
+ };
76
+
77
+ saveRegistry();
78
+
79
+ return { success: true, transferId };
80
+ }
81
+
82
+ /**
83
+ * Look up a transfer by ID.
84
+ *
85
+ * @param {string} transferId
86
+ * @returns {{ found: boolean, transfer?: Object }}
87
+ */
88
+ export function lookupTransfer(transferId) {
89
+ ensureRegistry();
90
+
91
+ const transfer = registry.transfers[transferId];
92
+ if (!transfer) {
93
+ return { found: false };
94
+ }
95
+
96
+ return { found: true, transfer: { transferId, ...transfer } };
97
+ }
98
+
99
+ /**
100
+ * Revoke a transfer.
101
+ *
102
+ * @param {string} transferId
103
+ * @returns {{ success: boolean, error?: string }}
104
+ */
105
+ export function revokeTransfer(transferId) {
106
+ ensureRegistry();
107
+
108
+ if (!registry.transfers[transferId]) {
109
+ return { success: false, error: `Transfer ID "${transferId}" not found` };
110
+ }
111
+
112
+ if (registry.transfers[transferId].status === "revoked") {
113
+ return { success: false, error: `Transfer "${transferId}" is already revoked` };
114
+ }
115
+
116
+ registry.transfers[transferId].status = "revoked";
117
+ registry.transfers[transferId].revoked_at = new Date().toISOString();
118
+
119
+ saveRegistry();
120
+
121
+ return { success: true, transferId, message: `Transfer "${transferId}" has been revoked` };
122
+ }
123
+
124
+ /**
125
+ * List all transfers.
126
+ *
127
+ * @param {string} [status] - Optional filter: "active", "revoked"
128
+ * @returns {Array<Object>}
129
+ */
130
+ export function listTransfers(status = null) {
131
+ ensureRegistry();
132
+
133
+ const transfers = [];
134
+ for (const [id, data] of Object.entries(registry.transfers)) {
135
+ if (status && data.status !== status) continue;
136
+ transfers.push({ transferId: id, ...data });
137
+ }
138
+
139
+ return transfers;
140
+ }
141
+
142
+ /**
143
+ * Check if a transfer ID has been used (for replay prevention).
144
+ *
145
+ * @param {string} transferId
146
+ * @returns {boolean}
147
+ */
148
+ export function isTransferUsed(transferId) {
149
+ ensureRegistry();
150
+ return !!registry.transfers[transferId];
151
+ }
152
+
153
+ /**
154
+ * Reset registry (for testing only).
155
+ */
156
+ export function resetRegistry() {
157
+ registry = { transfers: {} };
158
+ saveRegistry();
159
+ }
package/vault/seal.js ADDED
@@ -0,0 +1,74 @@
1
+ // ============================================================
2
+ // 0nMCP — Vault: Seal of Truth
3
+ // ============================================================
4
+ // SHA3-256 based content certification hash.
5
+ // Publicly verifiable — anyone can confirm container integrity
6
+ // without decrypting any layer data.
7
+ //
8
+ // Formula:
9
+ // SHA3-256(transfer_id || timestamp || pubkey || SHA3-256(concat(all_ciphertexts)))
10
+ //
11
+ // Patent Pending: US Provisional Patent Application #63/990,046
12
+ // ============================================================
13
+
14
+ import { sha3, sha3Hex } from "./crypto-container.js";
15
+
16
+ /**
17
+ * Create a Seal of Truth for a vault container.
18
+ *
19
+ * @param {string} transferId - UUID v4 transfer ID
20
+ * @param {number} timestamp - Unix timestamp (ms)
21
+ * @param {Buffer} pubkey - Ed25519 public key (32 bytes)
22
+ * @param {Buffer[]} layerCiphertexts - Array of encrypted layer data
23
+ * @returns {{ seal: Buffer, sealHex: string }}
24
+ */
25
+ export function createSeal(transferId, timestamp, pubkey, layerCiphertexts) {
26
+ // Hash all ciphertexts together
27
+ const contentHash = sha3(Buffer.concat(layerCiphertexts));
28
+
29
+ // Combine components: transferId || timestamp || pubkey || contentHash
30
+ const transferIdBuf = Buffer.from(transferId, "utf8");
31
+ const timestampBuf = Buffer.alloc(8);
32
+ timestampBuf.writeBigUInt64BE(BigInt(timestamp));
33
+
34
+ const sealInput = Buffer.concat([transferIdBuf, timestampBuf, pubkey, contentHash]);
35
+ const seal = sha3(sealInput);
36
+
37
+ return {
38
+ seal,
39
+ sealHex: seal.toString("hex"),
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Verify a Seal of Truth.
45
+ *
46
+ * @param {Buffer} seal - 32-byte seal hash
47
+ * @param {string} transferId
48
+ * @param {number} timestamp
49
+ * @param {Buffer} pubkey
50
+ * @param {Buffer[]} layerCiphertexts
51
+ * @returns {boolean} True if seal matches
52
+ */
53
+ export function verifySeal(seal, transferId, timestamp, pubkey, layerCiphertexts) {
54
+ const expected = createSeal(transferId, timestamp, pubkey, layerCiphertexts);
55
+ return Buffer.compare(seal, expected.seal) === 0;
56
+ }
57
+
58
+ /**
59
+ * Generate a human-readable seal summary.
60
+ *
61
+ * @param {Buffer} seal
62
+ * @param {string} transferId
63
+ * @param {number} timestamp
64
+ * @returns {string}
65
+ */
66
+ export function sealSummary(seal, transferId, timestamp) {
67
+ return [
68
+ `Seal of Truth: ${seal.toString("hex")}`,
69
+ `Transfer ID: ${transferId}`,
70
+ `Timestamp: ${new Date(timestamp).toISOString()}`,
71
+ `Algorithm: SHA3-256`,
72
+ `Status: Verified ✓`,
73
+ ].join("\n");
74
+ }