yakmesh 2.8.2 → 3.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.
- package/CHANGELOG.md +637 -0
- package/CONTRIBUTING.md +42 -0
- package/Caddyfile +77 -0
- package/README.md +119 -29
- package/adapters/adapter-mlv-bible/README.md +124 -0
- package/adapters/adapter-mlv-bible/index.js +400 -0
- package/adapters/chat-mod-adapter.js +532 -0
- package/adapters/content-adapter.js +273 -0
- package/content/api.js +50 -41
- package/content/index.js +2 -2
- package/content/store.js +355 -173
- package/dashboard/index.html +19 -3
- package/database/replication.js +117 -37
- package/docs/CRYPTO-AGILITY.md +204 -0
- package/docs/MTLS-RESEARCH.md +367 -0
- package/docs/NAMCHE-SPEC.md +681 -0
- package/docs/PEERQUANTA-YAKMESH-INTEGRATION.md +407 -0
- package/docs/PRECISION-DISCLOSURE.md +96 -0
- package/docs/README.md +76 -0
- package/docs/ROADMAP-2.4.0.md +447 -0
- package/docs/ROADMAP-2.5.0.md +244 -0
- package/docs/SECURITY-AUDIT-REPORT.md +306 -0
- package/docs/SST-INTEGRATION.md +712 -0
- package/docs/STEADYWATCH-IMPLEMENTATION.md +303 -0
- package/docs/TERNARY-AUDIT-REPORT.md +247 -0
- package/docs/TME-FAQ.md +221 -0
- package/docs/WHITEPAPER.md +623 -0
- package/docs/adapters.html +1001 -0
- package/docs/advanced-systems.html +1045 -0
- package/docs/annex.html +1046 -0
- package/docs/api.html +970 -0
- package/docs/business/response-templates.md +160 -0
- package/docs/c2c.html +1225 -0
- package/docs/cli.html +1332 -0
- package/docs/configuration.html +1248 -0
- package/docs/darshan.html +1085 -0
- package/docs/dharma.html +966 -0
- package/docs/docs-bundle.html +1075 -0
- package/docs/docs.css +3120 -0
- package/docs/docs.js +556 -0
- package/docs/doko.html +969 -0
- package/docs/geo-proof.html +858 -0
- package/docs/getting-started.html +840 -0
- package/docs/gumba-tutorial.html +1144 -0
- package/docs/gumba.html +1098 -0
- package/docs/index.html +914 -0
- package/docs/jhilke.html +1312 -0
- package/docs/karma.html +1100 -0
- package/docs/katha.html +1037 -0
- package/docs/lama.html +978 -0
- package/docs/mandala.html +1067 -0
- package/docs/mani.html +964 -0
- package/docs/mantra.html +967 -0
- package/docs/mesh.html +1409 -0
- package/docs/nakpak.html +869 -0
- package/docs/namche.html +928 -0
- package/docs/nav-order.json +53 -0
- package/docs/prahari.html +1043 -0
- package/docs/prism-bash.min.js +1 -0
- package/docs/prism-javascript.min.js +1 -0
- package/docs/prism-json.min.js +1 -0
- package/docs/prism-tomorrow.min.css +1 -0
- package/docs/prism.min.js +1 -0
- package/docs/privacy.html +699 -0
- package/docs/quick-reference.html +1181 -0
- package/docs/sakshi.html +1402 -0
- package/docs/sandboxing.md +386 -0
- package/docs/seva.html +911 -0
- package/docs/sherpa.html +871 -0
- package/docs/studio.html +860 -0
- package/docs/stupa.html +995 -0
- package/docs/tailwind.min.css +2 -0
- package/docs/tattva.html +1332 -0
- package/docs/terms.html +686 -0
- package/docs/time-server-deployment.md +166 -0
- package/docs/time-sources.html +1392 -0
- package/docs/tivra.html +1127 -0
- package/docs/trademark-policy.html +686 -0
- package/docs/tribhuj.html +1183 -0
- package/docs/trust-security.html +1029 -0
- package/docs/tutorials/backup-recovery.html +654 -0
- package/docs/tutorials/dashboard.html +604 -0
- package/docs/tutorials/domain-setup.html +605 -0
- package/docs/tutorials/host-website.html +456 -0
- package/docs/tutorials/mesh-network.html +505 -0
- package/docs/tutorials/mobile-access.html +445 -0
- package/docs/tutorials/privacy.html +467 -0
- package/docs/tutorials/raspberry-pi.html +600 -0
- package/docs/tutorials/security-basics.html +539 -0
- package/docs/tutorials/share-files.html +431 -0
- package/docs/tutorials/troubleshooting.html +637 -0
- package/docs/tutorials/trust-karma.html +419 -0
- package/docs/tutorials/yak-protocol.html +456 -0
- package/docs/tutorials.html +1034 -0
- package/docs/vani.html +1270 -0
- package/docs/webserver.html +809 -0
- package/docs/yak-protocol.html +940 -0
- package/docs/yak-timeserver-design.md +475 -0
- package/docs/yakapp.html +1015 -0
- package/docs/ypc27.html +1069 -0
- package/docs/yurt.html +1344 -0
- package/embedded-docs/bundle.js +334 -74
- package/gossip/protocol.js +247 -27
- package/identity/key-resolver.js +262 -0
- package/identity/machine-seed.js +632 -0
- package/identity/node-key.js +669 -368
- package/identity/tribhuj-ratchet.js +506 -0
- package/knowledge-base.js +37 -8
- package/launcher/yakmesh.bat +62 -0
- package/launcher/yakmesh.sh +70 -0
- package/mesh/annex.js +462 -108
- package/mesh/beacon-broadcast.js +113 -1
- package/mesh/darshan.js +1718 -0
- package/mesh/gumba.js +1567 -0
- package/mesh/jhilke.js +651 -0
- package/mesh/katha.js +1012 -0
- package/mesh/nakpak-routing.js +8 -5
- package/mesh/network.js +724 -34
- package/mesh/pulse-sync.js +4 -1
- package/mesh/rate-limiter.js +127 -15
- package/mesh/seva.js +526 -0
- package/mesh/sherpa-discovery.js +89 -8
- package/mesh/sybil-defense.js +19 -5
- package/mesh/temporal-encoder.js +4 -3
- package/mesh/vani.js +1364 -0
- package/mesh/yurt.js +1340 -0
- package/models/entropy-sentinel.onnx +0 -0
- package/models/karma-trust.onnx +0 -0
- package/models/manifest.json +43 -0
- package/models/sakshi-anomaly.onnx +0 -0
- package/oracle/code-proof-protocol.js +7 -6
- package/oracle/codebase-lock.js +257 -28
- package/oracle/index.js +74 -15
- package/oracle/ma902-snmp.js +678 -0
- package/oracle/module-sealer.js +5 -3
- package/oracle/network-identity.js +16 -0
- package/oracle/packet-checksum.js +201 -0
- package/oracle/sst.js +579 -0
- package/oracle/ternary-144t.js +714 -0
- package/oracle/ternary-ml.js +481 -0
- package/oracle/time-api.js +239 -0
- package/oracle/time-source.js +137 -47
- package/oracle/validation-oracle-hardened.js +1111 -1071
- package/oracle/validation-oracle.js +4 -2
- package/oracle/ypc27.js +211 -0
- package/package.json +20 -3
- package/protocol/yak-handler.js +35 -9
- package/protocol/yak-protocol.js +28 -13
- package/reference/cpp/yakmesh_mceliece_shard.cpp +168 -0
- package/reference/cpp/yakmesh_ypc27.cpp +179 -0
- package/sbom.json +87 -0
- package/scripts/security-audit.mjs +264 -0
- package/scripts/update-docs-nav.js +194 -0
- package/scripts/update-docs-sidebar.cjs +164 -0
- package/security/crypto-config.js +4 -3
- package/security/dharma-moderation.js +517 -0
- package/security/doko-identity.js +193 -143
- package/security/domain-consensus.js +86 -85
- package/security/fs-hardening.js +620 -0
- package/security/hardware-attestation.js +5 -3
- package/security/hybrid-trust.js +227 -87
- package/security/karma-rate-limiter.js +692 -0
- package/security/khata-protocol.js +22 -21
- package/security/khata-trust-integration.js +277 -150
- package/security/memory-safety.js +635 -0
- package/security/mesh-auth.js +11 -10
- package/security/mesh-revocation.js +373 -5
- package/security/namche-gateway.js +298 -69
- package/security/sakshi.js +460 -3
- package/security/sangha.js +770 -0
- package/security/secure-config.js +473 -0
- package/security/silicon-parity.js +13 -10
- package/security/steadywatch.js +1142 -0
- package/security/strike-system.js +32 -3
- package/security/temporal-signing.js +488 -0
- package/security/trit-commitment.js +464 -0
- package/server/crypto/annex.js +247 -0
- package/server/darshan-api.js +343 -0
- package/server/index.js +3259 -362
- package/server/komm-api.js +668 -0
- package/utils/accel.js +2273 -0
- package/utils/ternary-id.js +79 -0
- package/utils/verify-worker.js +57 -0
- package/webserver/index.js +95 -5
- package/assets/yakmesh-logo.png +0 -0
- package/assets/yakmesh-logo.svg +0 -80
- package/assets/yakmesh-logo2.png +0 -0
- package/assets/yakmesh-logo2sm.png +0 -0
- package/assets/ymsm.png +0 -0
- package/website/assets/silhouettes/adapters.svg +0 -107
- package/website/assets/silhouettes/api-endpoints.svg +0 -115
- package/website/assets/silhouettes/atomic-clock.svg +0 -83
- package/website/assets/silhouettes/base-camp.svg +0 -81
- package/website/assets/silhouettes/bridge.svg +0 -69
- package/website/assets/silhouettes/docs-bundle.svg +0 -113
- package/website/assets/silhouettes/doko-basket.svg +0 -70
- package/website/assets/silhouettes/fortress.svg +0 -93
- package/website/assets/silhouettes/gateway.svg +0 -54
- package/website/assets/silhouettes/gears.svg +0 -93
- package/website/assets/silhouettes/globe-satellite.svg +0 -67
- package/website/assets/silhouettes/karma-wheel.svg +0 -137
- package/website/assets/silhouettes/lama-council.svg +0 -141
- package/website/assets/silhouettes/mandala-network.svg +0 -169
- package/website/assets/silhouettes/mani-stones.svg +0 -149
- package/website/assets/silhouettes/mantra-wheel.svg +0 -116
- package/website/assets/silhouettes/mesh-nodes.svg +0 -113
- package/website/assets/silhouettes/nakpak.svg +0 -56
- package/website/assets/silhouettes/peak-lightning.svg +0 -73
- package/website/assets/silhouettes/sherpa.svg +0 -69
- package/website/assets/silhouettes/stupa-tower.svg +0 -119
- package/website/assets/silhouettes/tattva-eye.svg +0 -78
- package/website/assets/silhouettes/terminal.svg +0 -74
- package/website/assets/silhouettes/webserver.svg +0 -145
- package/website/assets/silhouettes/yak.svg +0 -78
- package/website/assets/yakmesh-logo.png +0 -0
- package/website/assets/yakmesh-logo.webp +0 -0
- package/website/assets/yakmesh-logo128x140.webp +0 -0
- package/website/assets/yakmesh-logo2.png +0 -0
- package/website/assets/yakmesh-logo2.svg +0 -51
- package/website/assets/yakmesh-logo40x44.webp +0 -0
- package/website/assets/yakmesh.gif +0 -0
- package/website/assets/yakmesh.ico +0 -0
- package/website/assets/yakmesh.jpg +0 -0
- package/website/assets/yakmesh.pdf +0 -0
- package/website/assets/yakmesh.png +0 -0
- package/website/assets/yakmesh.svg +0 -70
- package/website/assets/yakmesh128.webp +0 -0
- package/website/assets/yakmesh32.png +0 -0
- package/website/assets/yakmesh32.svg +0 -65
- package/website/assets/yakmesh32o.ico +0 -2
- package/website/assets/yakmesh32o.svg +0 -65
- package/website/assets/yakmesh32o.svgz +0 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yakmesh File System Hardening — SANGHA-FS Extension
|
|
3
|
+
*
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* PHILOSOPHY: FILES AS COLLECTIVE PARTICIPANTS
|
|
6
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
+
*
|
|
8
|
+
* Traditional FS security: chmod 400, read-only mounts, ACLs.
|
|
9
|
+
* SANGHA-FS: Critical files JOIN the collective as attestation participants.
|
|
10
|
+
*
|
|
11
|
+
* Each protected file has a GUARDIAN that:
|
|
12
|
+
* - Periodically hashes the file and attests its integrity to SANGHA
|
|
13
|
+
* - Participates in collective circulation (antibodies visit file guardians)
|
|
14
|
+
* - Triggers collective response if tampering is detected
|
|
15
|
+
*
|
|
16
|
+
* This means:
|
|
17
|
+
* - Tampering is detected within one circulation cycle (≤5s)
|
|
18
|
+
* - All components are alerted simultaneously
|
|
19
|
+
* - The mesh can quarantine a compromised node
|
|
20
|
+
*
|
|
21
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
22
|
+
* PROTECTED ASSETS
|
|
23
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
24
|
+
*
|
|
25
|
+
* 1. IDENTITY FILES
|
|
26
|
+
* - machine-seed.json — Hardware-encrypted seed (CRITICAL)
|
|
27
|
+
* - node-key.json — Public identity (HIGH)
|
|
28
|
+
*
|
|
29
|
+
* 2. CONFIGURATION
|
|
30
|
+
* - yakmesh.config.js — Runtime config (oracle-verified)
|
|
31
|
+
*
|
|
32
|
+
* 3. DATABASE
|
|
33
|
+
* - yakmesh.db — SQLite database (integrity header)
|
|
34
|
+
*
|
|
35
|
+
* @module security/fs-hardening
|
|
36
|
+
* @version 1.0.0
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { createLogger } from '../utils/logger.js';
|
|
40
|
+
import { sha3_256 } from '../utils/accel.js';
|
|
41
|
+
import { bytesToHex } from '@noble/hashes/utils.js';
|
|
42
|
+
import { readFileSync, chmodSync, statSync, existsSync, watch } from 'fs';
|
|
43
|
+
import { join, resolve } from 'path';
|
|
44
|
+
import { platform } from 'os';
|
|
45
|
+
import { EventEmitter } from 'events';
|
|
46
|
+
|
|
47
|
+
const log = createLogger('security:fs-hardening');
|
|
48
|
+
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// CONSTANTS
|
|
51
|
+
// =============================================================================
|
|
52
|
+
|
|
53
|
+
/** Protection levels */
|
|
54
|
+
export const PROTECTION_LEVEL = Object.freeze({
|
|
55
|
+
CRITICAL: 'CRITICAL', // Identity seed, private keys — IMMUTABLE after startup
|
|
56
|
+
HIGH: 'HIGH', // Public identity, config — LOCKED after startup
|
|
57
|
+
NORMAL: 'NORMAL', // Database, logs — MONITORED for integrity
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/** File permission modes (POSIX) */
|
|
61
|
+
const POSIX_MODES = Object.freeze({
|
|
62
|
+
CRITICAL: 0o400, // r-------- (owner read only)
|
|
63
|
+
HIGH: 0o600, // rw------- (owner read/write)
|
|
64
|
+
NORMAL: 0o644, // rw-r--r-- (owner rw, others read)
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/** Default files to protect */
|
|
68
|
+
const DEFAULT_PROTECTED_FILES = [
|
|
69
|
+
{ path: 'data/machine-seed.json', level: PROTECTION_LEVEL.CRITICAL },
|
|
70
|
+
{ path: 'data/node-key.json', level: PROTECTION_LEVEL.HIGH },
|
|
71
|
+
{ path: 'data/yakmesh.db', level: PROTECTION_LEVEL.NORMAL },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
// =============================================================================
|
|
75
|
+
// FILE GUARDIAN
|
|
76
|
+
// =============================================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* FileGuardian — Protects a single file and attests to SANGHA
|
|
80
|
+
*
|
|
81
|
+
* Each guardian:
|
|
82
|
+
* - Takes an initial hash at startup
|
|
83
|
+
* - Periodically re-hashes and compares
|
|
84
|
+
* - Participates in SANGHA attestation cycles
|
|
85
|
+
* - Triggers collective alert on tampering
|
|
86
|
+
*/
|
|
87
|
+
export class FileGuardian extends EventEmitter {
|
|
88
|
+
#filePath;
|
|
89
|
+
#level;
|
|
90
|
+
#baselineHash;
|
|
91
|
+
#baselineMtime;
|
|
92
|
+
#baselineSize;
|
|
93
|
+
#locked;
|
|
94
|
+
#watcher;
|
|
95
|
+
#checkInterval;
|
|
96
|
+
#lastCheck;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {string} filePath - Absolute path to the file
|
|
100
|
+
* @param {string} level - Protection level (CRITICAL/HIGH/NORMAL)
|
|
101
|
+
*/
|
|
102
|
+
constructor(filePath, level = PROTECTION_LEVEL.NORMAL) {
|
|
103
|
+
super();
|
|
104
|
+
this.#filePath = resolve(filePath);
|
|
105
|
+
this.#level = level;
|
|
106
|
+
this.#baselineHash = null;
|
|
107
|
+
this.#baselineMtime = null;
|
|
108
|
+
this.#baselineSize = null;
|
|
109
|
+
this.#locked = false;
|
|
110
|
+
this.#watcher = null;
|
|
111
|
+
this.#checkInterval = null;
|
|
112
|
+
this.#lastCheck = 0;
|
|
113
|
+
|
|
114
|
+
Object.seal(this);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Get file path */
|
|
118
|
+
get path() { return this.#filePath; }
|
|
119
|
+
|
|
120
|
+
/** Get protection level */
|
|
121
|
+
get level() { return this.#level; }
|
|
122
|
+
|
|
123
|
+
/** Get baseline hash */
|
|
124
|
+
get baselineHash() { return this.#baselineHash; }
|
|
125
|
+
|
|
126
|
+
/** Is file locked (immutable)? */
|
|
127
|
+
get isLocked() { return this.#locked; }
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Initialize guardian — take baseline snapshot
|
|
131
|
+
* @returns {Promise<void>}
|
|
132
|
+
*/
|
|
133
|
+
async init() {
|
|
134
|
+
if (!existsSync(this.#filePath)) {
|
|
135
|
+
log.warn('Protected file does not exist yet', { path: this.#filePath });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Take baseline snapshot
|
|
140
|
+
await this.#takeBaseline();
|
|
141
|
+
|
|
142
|
+
// Apply restrictive permissions on POSIX systems
|
|
143
|
+
this.#applyPermissions();
|
|
144
|
+
|
|
145
|
+
// Set up file watcher for real-time detection
|
|
146
|
+
this.#startWatcher();
|
|
147
|
+
|
|
148
|
+
log.info('FileGuardian initialized', {
|
|
149
|
+
path: this.#filePath,
|
|
150
|
+
level: this.#level,
|
|
151
|
+
hash: this.#baselineHash?.slice(0, 16) + '...',
|
|
152
|
+
size: this.#baselineSize,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Take baseline snapshot of file
|
|
158
|
+
*/
|
|
159
|
+
async #takeBaseline() {
|
|
160
|
+
const content = readFileSync(this.#filePath);
|
|
161
|
+
const hash = sha3_256(content);
|
|
162
|
+
const stat = statSync(this.#filePath);
|
|
163
|
+
|
|
164
|
+
this.#baselineHash = bytesToHex(hash);
|
|
165
|
+
this.#baselineMtime = stat.mtimeMs;
|
|
166
|
+
this.#baselineSize = stat.size;
|
|
167
|
+
this.#lastCheck = Date.now();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Apply restrictive file permissions (POSIX only)
|
|
172
|
+
*/
|
|
173
|
+
#applyPermissions() {
|
|
174
|
+
if (platform() === 'win32') {
|
|
175
|
+
// Windows uses NTFS ACLs — handled differently
|
|
176
|
+
// TODO: Use icacls for Windows permission hardening
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const mode = POSIX_MODES[this.#level] || POSIX_MODES.NORMAL;
|
|
181
|
+
try {
|
|
182
|
+
chmodSync(this.#filePath, mode);
|
|
183
|
+
log.debug('Applied file permissions', {
|
|
184
|
+
path: this.#filePath,
|
|
185
|
+
mode: mode.toString(8),
|
|
186
|
+
});
|
|
187
|
+
} catch (e) {
|
|
188
|
+
log.warn('Failed to set file permissions', {
|
|
189
|
+
path: this.#filePath,
|
|
190
|
+
error: e.message,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Start file system watcher for real-time tampering detection
|
|
197
|
+
*/
|
|
198
|
+
#startWatcher() {
|
|
199
|
+
// NORMAL-level files (database) change constantly — only watch for deletion
|
|
200
|
+
// CRITICAL/HIGH files get full change monitoring
|
|
201
|
+
try {
|
|
202
|
+
this.#watcher = watch(this.#filePath, (eventType) => {
|
|
203
|
+
if (eventType === 'change' && this.#level !== PROTECTION_LEVEL.NORMAL) {
|
|
204
|
+
// File was modified — verify integrity (CRITICAL/HIGH only)
|
|
205
|
+
this.verify().catch(e => {
|
|
206
|
+
log.error('Watch-triggered verification failed', { error: e.message });
|
|
207
|
+
});
|
|
208
|
+
} else if (eventType === 'rename') {
|
|
209
|
+
// File was renamed/deleted — always report regardless of level
|
|
210
|
+
this.emit('tamper', {
|
|
211
|
+
type: 'FILE_REMOVED',
|
|
212
|
+
path: this.#filePath,
|
|
213
|
+
level: this.#level,
|
|
214
|
+
timestamp: Date.now(),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
} catch (e) {
|
|
219
|
+
log.warn('Could not set up file watcher', {
|
|
220
|
+
path: this.#filePath,
|
|
221
|
+
error: e.message,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Lock the file (prevent further modifications)
|
|
228
|
+
* For CRITICAL files, this makes them effectively read-only after startup.
|
|
229
|
+
*/
|
|
230
|
+
lock() {
|
|
231
|
+
if (this.#locked) return;
|
|
232
|
+
|
|
233
|
+
this.#locked = true;
|
|
234
|
+
|
|
235
|
+
// Re-apply most restrictive permissions
|
|
236
|
+
if (platform() !== 'win32') {
|
|
237
|
+
try {
|
|
238
|
+
chmodSync(this.#filePath, 0o400); // Read-only
|
|
239
|
+
} catch (e) {
|
|
240
|
+
log.warn('Could not lock file', { path: this.#filePath, error: e.message });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
log.info('File locked (read-only)', { path: this.#filePath });
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Re-take the baseline snapshot.
|
|
249
|
+
* Used after startup grace period so that HIGH-level files capture
|
|
250
|
+
* their post-initialization state (e.g. node-key.json after derivation).
|
|
251
|
+
*/
|
|
252
|
+
async rebaseline() {
|
|
253
|
+
if (!existsSync(this.#filePath)) return;
|
|
254
|
+
await this.#takeBaseline();
|
|
255
|
+
log.info('FileGuardian re-baselined', {
|
|
256
|
+
path: this.#filePath,
|
|
257
|
+
level: this.#level,
|
|
258
|
+
hash: this.#baselineHash?.slice(0, 16) + '...',
|
|
259
|
+
size: this.#baselineSize,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Verify file integrity against baseline
|
|
265
|
+
* @returns {Promise<{ valid: boolean, error?: string }>}
|
|
266
|
+
*/
|
|
267
|
+
async verify() {
|
|
268
|
+
if (!this.#baselineHash) {
|
|
269
|
+
return { valid: true, error: 'No baseline (file not yet created)' };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!existsSync(this.#filePath)) {
|
|
273
|
+
const tamperEvent = {
|
|
274
|
+
type: 'FILE_DELETED',
|
|
275
|
+
path: this.#filePath,
|
|
276
|
+
level: this.#level,
|
|
277
|
+
timestamp: Date.now(),
|
|
278
|
+
};
|
|
279
|
+
this.emit('tamper', tamperEvent);
|
|
280
|
+
return { valid: false, error: 'File deleted' };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// NORMAL-level files (e.g. yakmesh.db) are mutable by design.
|
|
284
|
+
// Only monitor for existence/deletion — NOT content hashing.
|
|
285
|
+
// The database changes constantly during normal operation.
|
|
286
|
+
if (this.#level === PROTECTION_LEVEL.NORMAL) {
|
|
287
|
+
this.#lastCheck = Date.now();
|
|
288
|
+
return { valid: true };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const content = readFileSync(this.#filePath);
|
|
293
|
+
const hash = bytesToHex(sha3_256(content));
|
|
294
|
+
const stat = statSync(this.#filePath);
|
|
295
|
+
|
|
296
|
+
this.#lastCheck = Date.now();
|
|
297
|
+
|
|
298
|
+
// Check hash (CRITICAL and HIGH only)
|
|
299
|
+
if (hash !== this.#baselineHash) {
|
|
300
|
+
const tamperEvent = {
|
|
301
|
+
type: 'HASH_MISMATCH',
|
|
302
|
+
path: this.#filePath,
|
|
303
|
+
level: this.#level,
|
|
304
|
+
expected: this.#baselineHash.slice(0, 16) + '...',
|
|
305
|
+
actual: hash.slice(0, 16) + '...',
|
|
306
|
+
timestamp: Date.now(),
|
|
307
|
+
};
|
|
308
|
+
this.emit('tamper', tamperEvent);
|
|
309
|
+
return { valid: false, error: 'Hash mismatch' };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Check size (belt + suspenders)
|
|
313
|
+
if (stat.size !== this.#baselineSize) {
|
|
314
|
+
const tamperEvent = {
|
|
315
|
+
type: 'SIZE_MISMATCH',
|
|
316
|
+
path: this.#filePath,
|
|
317
|
+
level: this.#level,
|
|
318
|
+
expected: this.#baselineSize,
|
|
319
|
+
actual: stat.size,
|
|
320
|
+
timestamp: Date.now(),
|
|
321
|
+
};
|
|
322
|
+
this.emit('tamper', tamperEvent);
|
|
323
|
+
return { valid: false, error: 'Size mismatch' };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return { valid: true };
|
|
327
|
+
} catch (e) {
|
|
328
|
+
return { valid: false, error: e.message };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Get state for SANGHA attestation
|
|
334
|
+
* @returns {Promise<object>}
|
|
335
|
+
*/
|
|
336
|
+
async getState() {
|
|
337
|
+
const verification = await this.verify();
|
|
338
|
+
return {
|
|
339
|
+
path: this.#filePath,
|
|
340
|
+
level: this.#level,
|
|
341
|
+
hash: this.#baselineHash?.slice(0, 16),
|
|
342
|
+
locked: this.#locked,
|
|
343
|
+
valid: verification.valid,
|
|
344
|
+
lastCheck: this.#lastCheck,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Stop the guardian
|
|
350
|
+
*/
|
|
351
|
+
stop() {
|
|
352
|
+
if (this.#watcher) {
|
|
353
|
+
this.#watcher.close();
|
|
354
|
+
this.#watcher = null;
|
|
355
|
+
}
|
|
356
|
+
if (this.#checkInterval) {
|
|
357
|
+
clearInterval(this.#checkInterval);
|
|
358
|
+
this.#checkInterval = null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// =============================================================================
|
|
364
|
+
// FS HARDENING MANAGER
|
|
365
|
+
// =============================================================================
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* FSHardening — Manages all file guardians and SANGHA integration
|
|
369
|
+
*/
|
|
370
|
+
export class FSHardening extends EventEmitter {
|
|
371
|
+
#guardians;
|
|
372
|
+
#dataDir;
|
|
373
|
+
#sangha;
|
|
374
|
+
#started;
|
|
375
|
+
#verifyInterval;
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* @param {string} dataDir - Base data directory
|
|
379
|
+
*/
|
|
380
|
+
constructor(dataDir = './data') {
|
|
381
|
+
super();
|
|
382
|
+
this.#guardians = new Map();
|
|
383
|
+
this.#dataDir = resolve(dataDir);
|
|
384
|
+
this.#sangha = null;
|
|
385
|
+
this.#started = false;
|
|
386
|
+
this.#verifyInterval = null;
|
|
387
|
+
|
|
388
|
+
Object.seal(this);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Register a file for protection
|
|
393
|
+
* @param {string} relativePath - Path relative to data directory
|
|
394
|
+
* @param {string} level - Protection level
|
|
395
|
+
*/
|
|
396
|
+
async protect(relativePath, level = PROTECTION_LEVEL.NORMAL) {
|
|
397
|
+
const fullPath = join(this.#dataDir, '..', relativePath);
|
|
398
|
+
|
|
399
|
+
if (this.#guardians.has(fullPath)) {
|
|
400
|
+
log.warn('File already protected', { path: fullPath });
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const guardian = new FileGuardian(fullPath, level);
|
|
405
|
+
|
|
406
|
+
// Forward tamper events
|
|
407
|
+
guardian.on('tamper', (event) => {
|
|
408
|
+
this.#handleTamper(event);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
await guardian.init();
|
|
412
|
+
this.#guardians.set(fullPath, guardian);
|
|
413
|
+
|
|
414
|
+
// Lock CRITICAL files immediately
|
|
415
|
+
if (level === PROTECTION_LEVEL.CRITICAL) {
|
|
416
|
+
guardian.lock();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Handle tampering event
|
|
422
|
+
*/
|
|
423
|
+
#handleTamper(event) {
|
|
424
|
+
log.error('🚨 FILE TAMPERING DETECTED', event);
|
|
425
|
+
|
|
426
|
+
// Emit for external handlers
|
|
427
|
+
this.emit('tamper', event);
|
|
428
|
+
|
|
429
|
+
// If SANGHA is connected, trigger collective response
|
|
430
|
+
if (this.#sangha) {
|
|
431
|
+
try {
|
|
432
|
+
// Record as anomaly in current antibody circulation
|
|
433
|
+
const anomaly = {
|
|
434
|
+
componentId: 'fs',
|
|
435
|
+
type: event.type,
|
|
436
|
+
details: event,
|
|
437
|
+
timestamp: Date.now(),
|
|
438
|
+
};
|
|
439
|
+
this.emit('anomaly', anomaly);
|
|
440
|
+
} catch (e) {
|
|
441
|
+
log.error('Failed to report to SANGHA', { error: e.message });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Initialize with default protected files
|
|
448
|
+
*/
|
|
449
|
+
async init() {
|
|
450
|
+
for (const file of DEFAULT_PROTECTED_FILES) {
|
|
451
|
+
try {
|
|
452
|
+
await this.protect(file.path, file.level);
|
|
453
|
+
} catch (e) {
|
|
454
|
+
log.warn('Could not protect file', { path: file.path, error: e.message });
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
log.info('FS Hardening initialized', {
|
|
459
|
+
guardians: this.#guardians.size,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Bind to SANGHA collective
|
|
465
|
+
* @param {Sangha} sangha - The SANGHA instance
|
|
466
|
+
*/
|
|
467
|
+
bindSangha(sangha) {
|
|
468
|
+
this.#sangha = sangha;
|
|
469
|
+
log.info('FS Hardening bound to SANGHA collective');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Start periodic verification
|
|
474
|
+
* @param {number} intervalMs - Verification interval (default: 30s)
|
|
475
|
+
*/
|
|
476
|
+
start(intervalMs = 30000) {
|
|
477
|
+
if (this.#started) return;
|
|
478
|
+
|
|
479
|
+
this.#started = true;
|
|
480
|
+
|
|
481
|
+
// Periodic verification
|
|
482
|
+
this.#verifyInterval = setInterval(async () => {
|
|
483
|
+
await this.verifyAll();
|
|
484
|
+
}, intervalMs);
|
|
485
|
+
|
|
486
|
+
// After startup grace period (5s):
|
|
487
|
+
// - Re-baseline HIGH files (they may have been written during init)
|
|
488
|
+
// - Then lock them
|
|
489
|
+
setTimeout(async () => {
|
|
490
|
+
for (const [path, guardian] of this.#guardians) {
|
|
491
|
+
if (guardian.level === PROTECTION_LEVEL.HIGH && !guardian.isLocked) {
|
|
492
|
+
await guardian.rebaseline();
|
|
493
|
+
guardian.lock();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
log.info('Startup grace period ended — HIGH files re-baselined and locked');
|
|
497
|
+
}, 5000);
|
|
498
|
+
|
|
499
|
+
log.info('FS Hardening started', { interval: intervalMs });
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Verify all protected files
|
|
504
|
+
* @returns {Promise<{ valid: boolean, errors: object[] }>}
|
|
505
|
+
*/
|
|
506
|
+
async verifyAll() {
|
|
507
|
+
const errors = [];
|
|
508
|
+
|
|
509
|
+
for (const [path, guardian] of this.#guardians) {
|
|
510
|
+
const result = await guardian.verify();
|
|
511
|
+
if (!result.valid) {
|
|
512
|
+
errors.push({
|
|
513
|
+
path,
|
|
514
|
+
level: guardian.level,
|
|
515
|
+
error: result.error,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (errors.length > 0) {
|
|
521
|
+
log.error('File verification failures', { count: errors.length, errors });
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
valid: errors.length === 0,
|
|
526
|
+
errors,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Get state for SANGHA attestation (FS component)
|
|
532
|
+
* @returns {Promise<object>}
|
|
533
|
+
*/
|
|
534
|
+
async getState() {
|
|
535
|
+
const guardianStates = [];
|
|
536
|
+
|
|
537
|
+
for (const [path, guardian] of this.#guardians) {
|
|
538
|
+
guardianStates.push(await guardian.getState());
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
component: 'fs',
|
|
543
|
+
guardianCount: this.#guardians.size,
|
|
544
|
+
guardians: guardianStates,
|
|
545
|
+
allValid: guardianStates.every(g => g.valid),
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Get status summary
|
|
551
|
+
*/
|
|
552
|
+
getStatus() {
|
|
553
|
+
const files = [];
|
|
554
|
+
|
|
555
|
+
for (const [path, guardian] of this.#guardians) {
|
|
556
|
+
files.push({
|
|
557
|
+
path: guardian.path,
|
|
558
|
+
level: guardian.level,
|
|
559
|
+
locked: guardian.isLocked,
|
|
560
|
+
hash: guardian.baselineHash?.slice(0, 16) + '...',
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
started: this.#started,
|
|
566
|
+
sanghaConnected: !!this.#sangha,
|
|
567
|
+
files,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Stop FS hardening
|
|
573
|
+
*/
|
|
574
|
+
stop() {
|
|
575
|
+
if (!this.#started) return;
|
|
576
|
+
|
|
577
|
+
this.#started = false;
|
|
578
|
+
|
|
579
|
+
if (this.#verifyInterval) {
|
|
580
|
+
clearInterval(this.#verifyInterval);
|
|
581
|
+
this.#verifyInterval = null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
for (const guardian of this.#guardians.values()) {
|
|
585
|
+
guardian.stop();
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
log.info('FS Hardening stopped');
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// =============================================================================
|
|
593
|
+
// SINGLETON & EXPORTS
|
|
594
|
+
// =============================================================================
|
|
595
|
+
|
|
596
|
+
let _instance = null;
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Get the FSHardening singleton
|
|
600
|
+
* @param {string} dataDir - Data directory (only used on first call)
|
|
601
|
+
* @returns {FSHardening}
|
|
602
|
+
*/
|
|
603
|
+
export function getFSHardening(dataDir = './data') {
|
|
604
|
+
if (!_instance) {
|
|
605
|
+
_instance = new FSHardening(dataDir);
|
|
606
|
+
}
|
|
607
|
+
return _instance;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Quick protection helper
|
|
612
|
+
* @param {string} filePath - File path
|
|
613
|
+
* @param {string} level - Protection level
|
|
614
|
+
*/
|
|
615
|
+
export async function protectFile(filePath, level = PROTECTION_LEVEL.NORMAL) {
|
|
616
|
+
const fs = getFSHardening();
|
|
617
|
+
await fs.protect(filePath, level);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export default FSHardening;
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { createCipheriv, randomBytes } from 'crypto';
|
|
21
|
-
import { sha3_256 } from '@noble/hashes/sha3.js';
|
|
21
|
+
import { sha3_256 as _nobleSha3 } from '@noble/hashes/sha3.js';
|
|
22
22
|
import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
|
|
23
23
|
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
|
|
24
|
+
// ACCEL: Hardware-accelerated crypto
|
|
25
|
+
import { sha3_256, mlDsa65Sign, mlDsa65Verify } from '../utils/accel.js';
|
|
24
26
|
import { createLogger } from '../utils/logger.js';
|
|
25
27
|
import os from 'os';
|
|
26
28
|
|
|
@@ -610,7 +612,7 @@ export class HardwareAttestation {
|
|
|
610
612
|
|
|
611
613
|
// Sign the response
|
|
612
614
|
const responseBytes = new TextEncoder().encode(JSON.stringify(responseData));
|
|
613
|
-
const signature =
|
|
615
|
+
const signature = mlDsa65Sign(responseBytes, privateKey);
|
|
614
616
|
|
|
615
617
|
return {
|
|
616
618
|
...responseData,
|
|
@@ -639,7 +641,7 @@ export class HardwareAttestation {
|
|
|
639
641
|
const signature = hexToBytes(response.signature);
|
|
640
642
|
const pubKeyBytes = typeof publicKey === 'string' ? hexToBytes(publicKey) : publicKey;
|
|
641
643
|
|
|
642
|
-
if (!
|
|
644
|
+
if (!mlDsa65Verify(signature, responseBytes, pubKeyBytes)) {
|
|
643
645
|
return { valid: false, reason: 'INVALID_SIGNATURE' };
|
|
644
646
|
}
|
|
645
647
|
|