enigma-memory 0.1.16 → 0.1.18
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 +23 -13
- package/apps/cli/bin/enigma.mjs +3341 -3151
- package/deploy/SIMULATION.md +14 -9
- package/deploy/docker-compose.local-production-simulation.yml +18 -12
- package/docs/benchmark-attestation-network.md +487 -487
- package/docs/benchmark-reproducibility.md +289 -240
- package/docs/blockchain-only-mechanisms.md +400 -388
- package/docs/client-connectors.md +10 -6
- package/docs/demo-proof-network.md +275 -275
- package/docs/developer-ecosystem.md +7 -5
- package/docs/developer-proof-quickstart.md +325 -325
- package/docs/enigma-memory-ready-conformance.md +376 -376
- package/docs/hosted-cloud-product.md +29 -0
- package/docs/install-anywhere.md +16 -12
- package/docs/memory-benchmarks.md +16 -1
- package/docs/memory-drive-health-model.md +690 -649
- package/docs/proof-network-build-notes.md +240 -240
- package/docs/proof-network.md +339 -282
- package/docs/sdk-api.md +324 -324
- package/docs/solana-proof-rail.md +453 -453
- package/package.json +279 -278
- package/packages/core/src/index.js +1 -1
- package/packages/hosted-cloud/src/index.js +470 -2
- package/packages/mcp-server/src/index.js +1185 -1185
- package/packages/passport/src/index.js +740 -6
- package/packages/proof-network/src/index.js +139 -0
- package/packages/vault/src/index.js +187 -25
- package/scripts/build-benchmark-proof-release.mjs +15 -3
- package/scripts/build-hosted-api-key-lifecycle.mjs +274 -274
- package/scripts/build-hosted-customer-lifecycle.mjs +476 -456
- package/scripts/build-installer-assets.mjs +389 -389
- package/scripts/build-production-unblocker.mjs +409 -409
- package/scripts/build-proof-network-packet.mjs +213 -213
- package/scripts/check.mjs +8 -1
- package/scripts/release-audit.mjs +9 -6
- package/scripts/release-provenance.mjs +6 -2
- package/scripts/run-standard-memory-benchmarks.mjs +1354 -1194
- package/scripts/scan-secrets.mjs +177 -0
- package/scripts/simulate-production-env.mjs +64 -8
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Enigma Memory — working-tree secret scanner.
|
|
3
|
+
// Scans for PEM private keys, bearer-token patterns, DSNs, AWS-style keys,
|
|
4
|
+
// and literal SECURITY.md placeholders. Approved test fixtures must carry an
|
|
5
|
+
// explicit approval comment on the same or previous non-empty line:
|
|
6
|
+
// // secret-scan:approved
|
|
7
|
+
// # secret-scan:approved
|
|
8
|
+
// This script exits non-zero when any unapproved detection remains.
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs'
|
|
11
|
+
import path from 'node:path'
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
15
|
+
const APPROVAL_MARKERS = [
|
|
16
|
+
/secret-scan:(?:approved|ignore)/i,
|
|
17
|
+
/\bno[-_]?secret[-_]?scan\b/i,
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
const SKIP_DIRS = new Set([
|
|
21
|
+
'node_modules',
|
|
22
|
+
'.git',
|
|
23
|
+
'.enigma',
|
|
24
|
+
'.enigma-review-packet',
|
|
25
|
+
'.enigma-review-packet-debug',
|
|
26
|
+
'coverage',
|
|
27
|
+
'dist',
|
|
28
|
+
'build',
|
|
29
|
+
'.cache',
|
|
30
|
+
'tmp',
|
|
31
|
+
'.vscode',
|
|
32
|
+
'.idea',
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
const SKIP_EXTENSIONS = new Set([
|
|
36
|
+
'.png',
|
|
37
|
+
'.jpg',
|
|
38
|
+
'.jpeg',
|
|
39
|
+
'.gif',
|
|
40
|
+
'.webp',
|
|
41
|
+
'.svg',
|
|
42
|
+
'.ico',
|
|
43
|
+
'.pdf',
|
|
44
|
+
'.zip',
|
|
45
|
+
'.tar',
|
|
46
|
+
'.gz',
|
|
47
|
+
'.tgz',
|
|
48
|
+
'.woff',
|
|
49
|
+
'.woff2',
|
|
50
|
+
'.ttf',
|
|
51
|
+
'.otf',
|
|
52
|
+
'.eot',
|
|
53
|
+
'.mp4',
|
|
54
|
+
'.webm',
|
|
55
|
+
'.mp3',
|
|
56
|
+
'.ogg',
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
// The security-placeholder detector matches its own pattern literal below.
|
|
60
|
+
// The preceding comment marks that line as an approved self-reference.
|
|
61
|
+
const DETECTORS = [
|
|
62
|
+
{
|
|
63
|
+
id: 'pem-private-key',
|
|
64
|
+
pattern: /-----BEGIN\s+(?:RSA\s+|EC\s+|DSA\s+|OPENSSH\s+|ENCRYPTED\s+)?PRIVATE\s+KEY-----/i,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'aws-access-key-id',
|
|
68
|
+
pattern: /\b(?:AKIA|ASIA|AROA|AIDA)[0-9A-Z]{16}\b/,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'aws-secret-access-key',
|
|
72
|
+
pattern: /\b(?:aws[_-]?secret[_-]?access[_-]?key|AWS[_-]?SECRET[_-]?ACCESS[_-]?KEY)\s*[=:]\s*["'][A-Za-z0-9/+=]{40}["']/i,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'bearer-token',
|
|
76
|
+
// Skip placeholders like <token>, ${var}, and $ENV_VAR.
|
|
77
|
+
pattern: /(?:Authorization\s*:\s*Bearer\s+(?![<$\(])\S{8,}|\bbearer\s+[:=]\s*["']?[a-zA-Z0-9_\-]{16,}|\bapi[_-]?token\s*[:=]\s*["'][a-zA-Z0-9_\-]{16,}["'])/i,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'dsn-with-password',
|
|
81
|
+
pattern: /\b(?:postgres|postgresql|mysql|mysql2|mongodb|redis|amqp|amqps):\/\/[^:]+:[^@\s]+@[^\/\s]+/i,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'security-placeholder',
|
|
85
|
+
pattern: /REPLACE-WITH/i, // secret-scan:approved self-reference pattern
|
|
86
|
+
},
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
function hasApprovalComment(line) {
|
|
90
|
+
return APPROVAL_MARKERS.some((m) => m.test(line))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function isApproved(lines, index) {
|
|
94
|
+
if (hasApprovalComment(lines[index])) return true
|
|
95
|
+
for (let i = index - 1; i >= 0; i -= 1) {
|
|
96
|
+
const trimmed = lines[i].trim()
|
|
97
|
+
if (trimmed === '') continue
|
|
98
|
+
return hasApprovalComment(trimmed)
|
|
99
|
+
}
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isSkippableFile(rel) {
|
|
104
|
+
const ext = path.extname(rel).toLowerCase()
|
|
105
|
+
if (SKIP_EXTENSIONS.has(ext)) return true
|
|
106
|
+
const base = path.basename(rel)
|
|
107
|
+
if (base === 'package-lock.json') return true
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function* walk(root) {
|
|
112
|
+
const entries = fs.readdirSync(root, { withFileTypes: true })
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
const full = path.join(root, entry.name)
|
|
115
|
+
if (entry.isDirectory()) {
|
|
116
|
+
if (SKIP_DIRS.has(entry.name)) continue
|
|
117
|
+
yield* walk(full)
|
|
118
|
+
} else if (entry.isFile()) {
|
|
119
|
+
yield full
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function scanForSecrets(root) {
|
|
125
|
+
const findings = []
|
|
126
|
+
const rootResolved = path.resolve(root)
|
|
127
|
+
for (const fullPath of walk(rootResolved)) {
|
|
128
|
+
const rel = path.relative(rootResolved, fullPath)
|
|
129
|
+
if (isSkippableFile(rel)) continue
|
|
130
|
+
let text
|
|
131
|
+
try {
|
|
132
|
+
text = fs.readFileSync(fullPath, 'utf8')
|
|
133
|
+
} catch {
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
const lines = text.split(/\r?\n/)
|
|
137
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
138
|
+
const line = lines[i]
|
|
139
|
+
if (isApproved(lines, i)) continue
|
|
140
|
+
for (const detector of DETECTORS) {
|
|
141
|
+
if (detector.pattern.test(line)) {
|
|
142
|
+
findings.push({
|
|
143
|
+
file: rel,
|
|
144
|
+
line: i + 1,
|
|
145
|
+
type: detector.id,
|
|
146
|
+
})
|
|
147
|
+
break
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return findings
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function formatFindings(findings) {
|
|
156
|
+
return findings
|
|
157
|
+
.map((f) => `${f.file}:${f.line}: ${f.type}`)
|
|
158
|
+
.join('\n')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function main() {
|
|
162
|
+
const rootArg = process.argv.includes('--root')
|
|
163
|
+
? process.argv[process.argv.indexOf('--root') + 1]
|
|
164
|
+
: path.resolve(path.dirname(__filename), '..')
|
|
165
|
+
const root = path.resolve(rootArg)
|
|
166
|
+
const findings = scanForSecrets(root)
|
|
167
|
+
if (findings.length > 0) {
|
|
168
|
+
process.stderr.write(`secret-scan: ${findings.length} unapproved detection(s)\n`)
|
|
169
|
+
process.stderr.write(formatFindings(findings) + '\n')
|
|
170
|
+
process.exit(1)
|
|
171
|
+
}
|
|
172
|
+
process.stdout.write('secret-scan ok\n')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
176
|
+
main()
|
|
177
|
+
}
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
// are not production secrets, HSM custody, or real operator evidence.
|
|
6
6
|
|
|
7
7
|
import { spawnSync } from 'node:child_process'
|
|
8
|
-
import { generateKeyPairSync, randomUUID } from 'node:crypto'
|
|
8
|
+
import { createHash, generateKeyPairSync, randomUUID } from 'node:crypto'
|
|
9
9
|
import fs from 'node:fs'
|
|
10
|
+
import os from 'node:os'
|
|
10
11
|
import path from 'node:path'
|
|
11
12
|
import { fileURLToPath } from 'node:url'
|
|
12
13
|
|
|
13
14
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
14
|
-
const DEFAULT_SECRETS_DIR = path.join(
|
|
15
|
+
const DEFAULT_SECRETS_DIR = path.join(os.homedir(), '.enigma', 'simulation-secrets')
|
|
15
16
|
|
|
16
17
|
const REQUIRED_SECRET_FILES = [
|
|
17
18
|
'relay-signing-key',
|
|
@@ -27,6 +28,11 @@ const REQUIRED_SECRET_FILES = [
|
|
|
27
28
|
'tls.key',
|
|
28
29
|
]
|
|
29
30
|
|
|
31
|
+
const BEARER_FILES_FOR_DIGEST = {
|
|
32
|
+
'gateway-admin-auth-bearer': 'ENIGMA_GATEWAY_ADMIN_AUTH_BEARER_SHA256',
|
|
33
|
+
'gateway-data-plane-auth-bearer': 'ENIGMA_GATEWAY_DATA_PLANE_AUTH_BEARER_SHA256',
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
function parseArgs(argv) {
|
|
31
37
|
const flags = { check: false, secretsDir: DEFAULT_SECRETS_DIR }
|
|
32
38
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -41,8 +47,9 @@ function parseArgs(argv) {
|
|
|
41
47
|
process.stdout.write(
|
|
42
48
|
'Usage: node scripts/simulate-production-env.mjs [--check] [--secrets-dir <dir>]\n' +
|
|
43
49
|
'\n' +
|
|
44
|
-
'Generates local-simulation secret files and a self-signed TLS certificate
|
|
45
|
-
`
|
|
50
|
+
'Generates local-simulation secret files and a self-signed TLS certificate.\n' +
|
|
51
|
+
`By default files are written outside the repo to ${DEFAULT_SECRETS_DIR}.\n` +
|
|
52
|
+
'A path inside the repository is rejected to prevent accidental commits.\n' +
|
|
46
53
|
'Run with --check to verify required files exist and are non-empty.\n'
|
|
47
54
|
)
|
|
48
55
|
process.exit(0)
|
|
@@ -51,6 +58,22 @@ function parseArgs(argv) {
|
|
|
51
58
|
return flags
|
|
52
59
|
}
|
|
53
60
|
|
|
61
|
+
function isPathInside(child, parent) {
|
|
62
|
+
const c = path.resolve(child)
|
|
63
|
+
const p = path.resolve(parent)
|
|
64
|
+
const rel = path.relative(p, c)
|
|
65
|
+
return rel === '' || !rel.startsWith('..')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function assertSecretsDirOutsideRepo(secretsDir) {
|
|
69
|
+
if (isPathInside(secretsDir, ROOT)) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Refusing to use secrets directory inside the repository: ${secretsDir}\n` +
|
|
72
|
+
'Choose a path outside the repo (e.g. --secrets-dir ~/.enigma/simulation-secrets).'
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
function generateKmsKeyRef() {
|
|
55
78
|
const pair = generateKeyPairSync('ed25519', {
|
|
56
79
|
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
|
@@ -75,6 +98,7 @@ function generateKmsKeyRef() {
|
|
|
75
98
|
const SECRET_GENERATORS = {
|
|
76
99
|
'relay-signing-key': () => 'local-simulation-relay-signing-key-ref\n',
|
|
77
100
|
'gateway-signing-key': () => 'local-simulation-gateway-signing-key-ref\n',
|
|
101
|
+
// Simulation-only DSN. Never a real credential. secret-scan:approved
|
|
78
102
|
'external-storage-dsn': () => 'postgres://enigma:enigma@postgres:5432/enigma?sslmode=disable\n',
|
|
79
103
|
'kms-key-ref': generateKmsKeyRef,
|
|
80
104
|
'backup-target-uri': () => 'file:///tmp/enigma-backups\n',
|
|
@@ -97,6 +121,28 @@ function isMissingOrEmpty(filePath) {
|
|
|
97
121
|
}
|
|
98
122
|
}
|
|
99
123
|
|
|
124
|
+
function sha256Hex(bufferOrString) {
|
|
125
|
+
return createHash('sha256').update(bufferOrString).digest('hex')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function gatewayBearerSha256(filePath) {
|
|
129
|
+
const token = fs.readFileSync(filePath)
|
|
130
|
+
return `sha256:${sha256Hex(token)}`
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function writeSimulationEnv(secretsDir) {
|
|
134
|
+
const lines = [
|
|
135
|
+
'# Generated by scripts/simulate-production-env.mjs',
|
|
136
|
+
'# This file contains simulation-only digests, not production secrets.',
|
|
137
|
+
'# It is kept next to the other simulation secrets outside the repo.',
|
|
138
|
+
]
|
|
139
|
+
for (const [name, envVar] of Object.entries(BEARER_FILES_FOR_DIGEST)) {
|
|
140
|
+
const digest = gatewayBearerSha256(path.join(secretsDir, name))
|
|
141
|
+
lines.push(`${envVar}=${digest}`)
|
|
142
|
+
}
|
|
143
|
+
fs.writeFileSync(path.join(secretsDir, 'simulation.env'), lines.join('\n') + '\n', { mode: 0o600 })
|
|
144
|
+
}
|
|
145
|
+
|
|
100
146
|
function generateTlsCerts(secretsDir) {
|
|
101
147
|
const key = path.join(secretsDir, 'tls.key')
|
|
102
148
|
const cert = path.join(secretsDir, 'tls.crt')
|
|
@@ -149,6 +195,7 @@ function generateTlsCerts(secretsDir) {
|
|
|
149
195
|
|
|
150
196
|
function generateSecrets(secretsDir) {
|
|
151
197
|
ensureDir(secretsDir)
|
|
198
|
+
assertSecretsDirOutsideRepo(secretsDir)
|
|
152
199
|
for (const name of REQUIRED_SECRET_FILES) {
|
|
153
200
|
if (name === 'tls.crt' || name === 'tls.key') continue
|
|
154
201
|
const filePath = path.join(secretsDir, name)
|
|
@@ -161,6 +208,7 @@ function generateSecrets(secretsDir) {
|
|
|
161
208
|
if (isMissingOrEmpty(keyPath) || isMissingOrEmpty(certPath)) {
|
|
162
209
|
generateTlsCerts(secretsDir)
|
|
163
210
|
}
|
|
211
|
+
writeSimulationEnv(secretsDir)
|
|
164
212
|
}
|
|
165
213
|
|
|
166
214
|
function check(secretsDir) {
|
|
@@ -177,10 +225,17 @@ function check(secretsDir) {
|
|
|
177
225
|
)
|
|
178
226
|
}
|
|
179
227
|
|
|
180
|
-
function printStartInstructions() {
|
|
228
|
+
function printStartInstructions(secretsDir) {
|
|
229
|
+
process.stdout.write('\n')
|
|
230
|
+
process.stdout.write('Simulation secrets written outside the repo to:\n')
|
|
231
|
+
process.stdout.write(` ${secretsDir}\n`)
|
|
232
|
+
process.stdout.write('\n')
|
|
233
|
+
process.stdout.write('To start the local production simulation, set the secrets path and run:\n')
|
|
234
|
+
process.stdout.write(` export ENIGMA_SIM_SECRETS_DIR=${secretsDir}\n`)
|
|
235
|
+
process.stdout.write(` docker compose -f deploy/docker-compose.local-production-simulation.yml --env-file ${path.join(secretsDir, 'simulation.env')} up --build -d\n`)
|
|
181
236
|
process.stdout.write('\n')
|
|
182
|
-
process.stdout.write('To
|
|
183
|
-
process.stdout.write(
|
|
237
|
+
process.stdout.write('To tear down, use the same env file so the gateway bearer hashes resolve:\n')
|
|
238
|
+
process.stdout.write(` docker compose -f deploy/docker-compose.local-production-simulation.yml --env-file ${path.join(secretsDir, 'simulation.env')} down -v --remove-orphans\n`)
|
|
184
239
|
process.stdout.write('\n')
|
|
185
240
|
process.stdout.write('To make the public-looking domain resolve locally, add this line to /etc/hosts\n')
|
|
186
241
|
process.stdout.write('(or C:\\Windows\\System32\\drivers\\etc\\hosts on Windows):\n')
|
|
@@ -198,13 +253,14 @@ function printStartInstructions() {
|
|
|
198
253
|
|
|
199
254
|
function main() {
|
|
200
255
|
const flags = parseArgs(process.argv.slice(2))
|
|
256
|
+
assertSecretsDirOutsideRepo(flags.secretsDir)
|
|
201
257
|
if (flags.check) {
|
|
202
258
|
check(flags.secretsDir)
|
|
203
259
|
return
|
|
204
260
|
}
|
|
205
261
|
generateSecrets(flags.secretsDir)
|
|
206
262
|
check(flags.secretsDir)
|
|
207
|
-
printStartInstructions()
|
|
263
|
+
printStartInstructions(flags.secretsDir)
|
|
208
264
|
}
|
|
209
265
|
|
|
210
266
|
main()
|