@synoi/gateway-lite 0.1.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/LICENSE +195 -0
- package/README.md +185 -0
- package/bin/synoi-gateway-lite.js +22 -0
- package/dist/cdro-mirror.js +280 -0
- package/dist/daemon.js +194 -0
- package/dist/gap/cited-oracle-inputs.js +283 -0
- package/dist/gap/lite-dashboard.html +502 -0
- package/dist/gap/lite-keystore.js +351 -0
- package/dist/gap/lite-mode.js +31 -0
- package/dist/gap/lite-signing-key.js +362 -0
- package/dist/gap/local-ingest-router.js +1642 -0
- package/dist/gap/operator-enrollment.js +466 -0
- package/dist/gap/store.js +1063 -0
- package/dist/gap/types.js +15 -0
- package/dist/key-provider.js +275 -0
- package/dist/keys.js +250 -0
- package/dist/native-mldsa.js +82 -0
- package/dist/receipt-store.js +801 -0
- package/dist/shadow-mode.js +85 -0
- package/dist/state-drift/quantize.js +107 -0
- package/dist/tenant-store.js +366 -0
- package/dist/verify-router.js +1755 -0
- package/npm-shrinkwrap.json +1400 -0
- package/package.json +46 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* gap/lite-keystore.ts — OS credential-store wrapper for the lite operator
|
|
4
|
+
* signing key (Adversary #1 / Security F3, 2026-07-12 quality gate).
|
|
5
|
+
*
|
|
6
|
+
* PROBLEM: the operator self-sign key (lite-signing-key.ts) was a plaintext
|
|
7
|
+
* JSON file. `mode: 0o600` on the write is a NO-OP on Windows (NTFS ACLs, not
|
|
8
|
+
* POSIX mode bits, govern access there; Node's `fs` mode parameter is
|
|
9
|
+
* silently ignored on Windows), so on the platform most self-hosters
|
|
10
|
+
* actually run this on, the key was readable by ANY process running as the
|
|
11
|
+
* same OS user with no additional barrier at all.
|
|
12
|
+
*
|
|
13
|
+
* CHOICE (founder-directed, "BOTH"): wrap the key at rest with the native OS
|
|
14
|
+
* credential store where available, keep a documented, hardened file
|
|
15
|
+
* fallback where it is not.
|
|
16
|
+
*
|
|
17
|
+
* ABSTRACTION CHOICE: shell out to the OS's OWN already-installed credential
|
|
18
|
+
* CLI (PowerShell + DPAPI on Windows, `security` on macOS, `secret-tool` on
|
|
19
|
+
* Linux) rather than pulling in an npm binding (e.g. keytar, which is
|
|
20
|
+
* unmaintained and requires a native-compiled addon per platform/Node ABI).
|
|
21
|
+
* A native addon dependency would work against the whole point of
|
|
22
|
+
* @synoi/gateway-lite: `npx @synoi/gateway-lite` must install and run with nothing
|
|
23
|
+
* beyond what npm and the OS already provide. Shelling out to the OS's own
|
|
24
|
+
* tool has no compile step, no prebuilt-binary matrix to maintain, and fails
|
|
25
|
+
* CLEANLY (ENOENT / non-zero exit) when the tool is absent, which is exactly
|
|
26
|
+
* the signal isAvailable() below needs to trigger the documented fallback.
|
|
27
|
+
*
|
|
28
|
+
* PLATFORM COVERAGE (tested vs implemented-per-documented-behavior):
|
|
29
|
+
* - Windows (DPAPI via PowerShell `System.Security.Cryptography.
|
|
30
|
+
* ProtectedData`, CurrentUser scope): TESTED in this session (real
|
|
31
|
+
* PowerShell process, real protect/unprotect round-trip). DPAPI ships
|
|
32
|
+
* with every Windows install; no separate tool to be missing.
|
|
33
|
+
* - macOS (`security` CLI, the standard Keychain command-line tool):
|
|
34
|
+
* IMPLEMENTED per documented `security add-generic-password` /
|
|
35
|
+
* `find-generic-password` behavior. NOT executable-tested in this
|
|
36
|
+
* session (no macOS runner available). `security` ships with every
|
|
37
|
+
* macOS install.
|
|
38
|
+
* - Linux (`secret-tool`, part of libsecret-tools / gnome-keyring):
|
|
39
|
+
* IMPLEMENTED per documented `secret-tool store` / `lookup` behavior.
|
|
40
|
+
* NOT executable-tested in this session. NOT guaranteed present on a
|
|
41
|
+
* minimal/headless/server Linux install (no desktop keyring daemon) --
|
|
42
|
+
* this is the platform most likely to hit the file fallback for a
|
|
43
|
+
* legitimate reason, not a bug.
|
|
44
|
+
*
|
|
45
|
+
* FALLBACK: if the platform's keystore is unavailable (tool missing, no
|
|
46
|
+
* desktop session, permission denied, or any other failure), lite-signing-
|
|
47
|
+
* key.ts falls back to the existing file-based store, hardened by this
|
|
48
|
+
* session's Windows ACL fix (icacls owner-only, see hardenFileAcl below),
|
|
49
|
+
* with a LOUD warning surfaced at boot (src/daemon.ts) and disclosed in
|
|
50
|
+
* packages/gateway-lite/README.md. The fallback is not silent.
|
|
51
|
+
*
|
|
52
|
+
* No em dashes. No AI attribution.
|
|
53
|
+
*/
|
|
54
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
55
|
+
if (k2 === undefined) k2 = k;
|
|
56
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
57
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
58
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
59
|
+
}
|
|
60
|
+
Object.defineProperty(o, k2, desc);
|
|
61
|
+
}) : (function(o, m, k, k2) {
|
|
62
|
+
if (k2 === undefined) k2 = k;
|
|
63
|
+
o[k2] = m[k];
|
|
64
|
+
}));
|
|
65
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
66
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
67
|
+
}) : function(o, v) {
|
|
68
|
+
o["default"] = v;
|
|
69
|
+
});
|
|
70
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
71
|
+
var ownKeys = function(o) {
|
|
72
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
73
|
+
var ar = [];
|
|
74
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
75
|
+
return ar;
|
|
76
|
+
};
|
|
77
|
+
return ownKeys(o);
|
|
78
|
+
};
|
|
79
|
+
return function (mod) {
|
|
80
|
+
if (mod && mod.__esModule) return mod;
|
|
81
|
+
var result = {};
|
|
82
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
83
|
+
__setModuleDefault(result, mod);
|
|
84
|
+
return result;
|
|
85
|
+
};
|
|
86
|
+
})();
|
|
87
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
88
|
+
exports.detectKeystorePlatform = detectKeystorePlatform;
|
|
89
|
+
exports._setForceUnavailableForTest = _setForceUnavailableForTest;
|
|
90
|
+
exports.createLiteKeystore = createLiteKeystore;
|
|
91
|
+
exports.hardenFileAcl = hardenFileAcl;
|
|
92
|
+
const node_child_process_1 = require("node:child_process");
|
|
93
|
+
const node_util_1 = require("node:util");
|
|
94
|
+
const fs = __importStar(require("node:fs"));
|
|
95
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
96
|
+
const KEYSTORE_SERVICE = 'synoi-gateway-lite-signing-key';
|
|
97
|
+
function detectKeystorePlatform() {
|
|
98
|
+
if (process.platform === 'win32')
|
|
99
|
+
return 'windows-dpapi';
|
|
100
|
+
if (process.platform === 'darwin')
|
|
101
|
+
return 'macos-keychain';
|
|
102
|
+
if (process.platform === 'linux')
|
|
103
|
+
return 'linux-secret-tool';
|
|
104
|
+
return 'unsupported';
|
|
105
|
+
}
|
|
106
|
+
// ── Windows: DPAPI via PowerShell ───────────────────────────────────────────
|
|
107
|
+
//
|
|
108
|
+
// DPAPI (Data Protection API) encrypts to a blob only the SAME Windows user
|
|
109
|
+
// account can decrypt (CurrentUser scope), using OS-managed key material
|
|
110
|
+
// derived from the user's login credentials. Node has no built-in DPAPI
|
|
111
|
+
// binding; PowerShell's full .NET access (System.Security.Cryptography.
|
|
112
|
+
// ProtectedData) is the OS-native tool for this, present on every Windows
|
|
113
|
+
// install with PowerShell (which ships by default since Windows 7 / Server
|
|
114
|
+
// 2008 R2). The secret is passed via an environment variable to the child
|
|
115
|
+
// process, not a command-line argument, so it does not appear in process
|
|
116
|
+
// listings (ps / Get-Process / Task Manager command-line columns).
|
|
117
|
+
//
|
|
118
|
+
// Storage location: since DPAPI protects BYTES, not a keyed store, "store"
|
|
119
|
+
// here just returns the protected blob (base64) for the caller to persist
|
|
120
|
+
// wherever it likes (lite-signing-key.ts writes it to the same file path the
|
|
121
|
+
// plaintext fallback would have used, but the file now holds ciphertext).
|
|
122
|
+
// "account" is accepted for interface symmetry with the other platforms but
|
|
123
|
+
// is not otherwise used by DPAPI itself.
|
|
124
|
+
class WindowsDpapiKeystore {
|
|
125
|
+
platform = 'windows-dpapi';
|
|
126
|
+
filePath;
|
|
127
|
+
constructor(filePath) {
|
|
128
|
+
this.filePath = filePath;
|
|
129
|
+
}
|
|
130
|
+
async isAvailable() {
|
|
131
|
+
if (process.platform !== 'win32')
|
|
132
|
+
return false;
|
|
133
|
+
try {
|
|
134
|
+
const { stdout } = await execFileAsync('powershell', [
|
|
135
|
+
'-NoProfile', '-NonInteractive', '-Command',
|
|
136
|
+
'Add-Type -AssemblyName System.Security; "ok"',
|
|
137
|
+
]);
|
|
138
|
+
return stdout.trim() === 'ok';
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async store(_account, secret) {
|
|
145
|
+
const { stdout } = await execFileAsync('powershell', ['-NoProfile', '-NonInteractive', '-Command', DPAPI_PROTECT_SCRIPT], { env: { ...process.env, SYNOI_DPAPI_INPUT: secret } });
|
|
146
|
+
const protectedBase64 = stdout.trim();
|
|
147
|
+
fs.writeFileSync(this.filePath, protectedBase64, { encoding: 'utf8', mode: 0o600 });
|
|
148
|
+
}
|
|
149
|
+
async retrieve(_account) {
|
|
150
|
+
if (!fs.existsSync(this.filePath))
|
|
151
|
+
return null;
|
|
152
|
+
const protectedBase64 = fs.readFileSync(this.filePath, 'utf8').trim();
|
|
153
|
+
if (protectedBase64 === '')
|
|
154
|
+
return null;
|
|
155
|
+
try {
|
|
156
|
+
const { stdout } = await execFileAsync('powershell', ['-NoProfile', '-NonInteractive', '-Command', DPAPI_UNPROTECT_SCRIPT], { env: { ...process.env, SYNOI_DPAPI_INPUT: protectedBase64 } });
|
|
157
|
+
return stdout.trim();
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const DPAPI_PROTECT_SCRIPT = [
|
|
165
|
+
'Add-Type -AssemblyName System.Security;',
|
|
166
|
+
'$bytes = [System.Text.Encoding]::UTF8.GetBytes($env:SYNOI_DPAPI_INPUT);',
|
|
167
|
+
'$protected = [System.Security.Cryptography.ProtectedData]::Protect(',
|
|
168
|
+
' $bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser);',
|
|
169
|
+
'[Convert]::ToBase64String($protected)',
|
|
170
|
+
].join(' ');
|
|
171
|
+
const DPAPI_UNPROTECT_SCRIPT = [
|
|
172
|
+
'Add-Type -AssemblyName System.Security;',
|
|
173
|
+
'$bytes = [Convert]::FromBase64String($env:SYNOI_DPAPI_INPUT);',
|
|
174
|
+
'$unprotected = [System.Security.Cryptography.ProtectedData]::Unprotect(',
|
|
175
|
+
' $bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser);',
|
|
176
|
+
'[System.Text.Encoding]::UTF8.GetString($unprotected)',
|
|
177
|
+
].join(' ');
|
|
178
|
+
// ── macOS: the `security` CLI (Keychain) ───────────────────────────────────
|
|
179
|
+
//
|
|
180
|
+
// IMPLEMENTED per documented `security` behavior; not executable-tested in
|
|
181
|
+
// this session (no macOS runner available here). `-w <secret>` on the store
|
|
182
|
+
// path is the standard, documented `security add-generic-password` usage.
|
|
183
|
+
//
|
|
184
|
+
// Security re-clear (2026-07-12): checked for a non-argv input path before
|
|
185
|
+
// accepting this. `secret-tool store` (the Linux path below) documents
|
|
186
|
+
// reading its secret from STDIN, so the same was investigated here.
|
|
187
|
+
// `security(1)`'s `add-generic-password` does not document an equivalent:
|
|
188
|
+
// `-w` takes its value as a literal argument or, if omitted entirely,
|
|
189
|
+
// prompts interactively at a TTY (not usable from a non-interactive daemon
|
|
190
|
+
// spawn). There is no documented `-w -` / stdin form, and no separate
|
|
191
|
+
// subcommand that stores a generic password from STDIN. So this remains a
|
|
192
|
+
// known, accepted characteristic of the `security` CLI itself, not a gap in
|
|
193
|
+
// this code: the secret is briefly visible as a process argument during the
|
|
194
|
+
// store call only (a narrow, one-time window at first run; retrieval does
|
|
195
|
+
// not re-expose it). Deleting-then-adding (rather than -U/--update in
|
|
196
|
+
// place) sidesteps a `security` quirk where -U silently no-ops on certain
|
|
197
|
+
// macOS versions when the access-control list differs.
|
|
198
|
+
class MacosKeychainKeystore {
|
|
199
|
+
platform = 'macos-keychain';
|
|
200
|
+
async isAvailable() {
|
|
201
|
+
if (process.platform !== 'darwin')
|
|
202
|
+
return false;
|
|
203
|
+
try {
|
|
204
|
+
await execFileAsync('security', ['-h']);
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async store(account, secret) {
|
|
212
|
+
// Best-effort delete of any prior entry, then add fresh. Ignore a
|
|
213
|
+
// "could not be found" failure on delete (nothing to remove yet).
|
|
214
|
+
try {
|
|
215
|
+
await execFileAsync('security', ['delete-generic-password', '-a', account, '-s', KEYSTORE_SERVICE]);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
/* no prior entry; fine */
|
|
219
|
+
}
|
|
220
|
+
await execFileAsync('security', [
|
|
221
|
+
'add-generic-password', '-a', account, '-s', KEYSTORE_SERVICE, '-w', secret,
|
|
222
|
+
]);
|
|
223
|
+
}
|
|
224
|
+
async retrieve(account) {
|
|
225
|
+
try {
|
|
226
|
+
const { stdout } = await execFileAsync('security', [
|
|
227
|
+
'find-generic-password', '-a', account, '-s', KEYSTORE_SERVICE, '-w',
|
|
228
|
+
]);
|
|
229
|
+
const value = stdout.trim();
|
|
230
|
+
return value === '' ? null : value;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// ── Linux: `secret-tool` (libsecret) ────────────────────────────────────────
|
|
238
|
+
//
|
|
239
|
+
// IMPLEMENTED per documented `secret-tool` behavior; not executable-tested
|
|
240
|
+
// in this session. Unlike macOS `security -w`, `secret-tool store` reads the
|
|
241
|
+
// secret from STDIN, so it never appears as a process argument at all.
|
|
242
|
+
// `secret-tool` requires a running secret-service provider (gnome-keyring,
|
|
243
|
+
// KWallet via ksecretservice, or similar) -- routinely present on a desktop
|
|
244
|
+
// Linux session, routinely ABSENT on a minimal/headless/server install. That
|
|
245
|
+
// is the expected, legitimate case for isAvailable() to return false and the
|
|
246
|
+
// file fallback to take over, not a defect.
|
|
247
|
+
class LinuxSecretToolKeystore {
|
|
248
|
+
platform = 'linux-secret-tool';
|
|
249
|
+
async isAvailable() {
|
|
250
|
+
if (process.platform !== 'linux')
|
|
251
|
+
return false;
|
|
252
|
+
try {
|
|
253
|
+
await execFileAsync('secret-tool', ['--version']);
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async store(account, secret) {
|
|
261
|
+
await new Promise((resolve, reject) => {
|
|
262
|
+
const child = (0, node_child_process_1.spawn)('secret-tool', [
|
|
263
|
+
'store', '--label', `SynOI lite daemon operator key (${account})`,
|
|
264
|
+
'service', KEYSTORE_SERVICE, 'account', account,
|
|
265
|
+
]);
|
|
266
|
+
let stderr = '';
|
|
267
|
+
child.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
268
|
+
child.on('error', reject);
|
|
269
|
+
child.on('close', (code) => {
|
|
270
|
+
if (code === 0)
|
|
271
|
+
resolve();
|
|
272
|
+
else
|
|
273
|
+
reject(new Error(`secret-tool store exited ${code}: ${stderr}`));
|
|
274
|
+
});
|
|
275
|
+
child.stdin.write(secret);
|
|
276
|
+
child.stdin.end();
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
async retrieve(account) {
|
|
280
|
+
try {
|
|
281
|
+
const { stdout } = await execFileAsync('secret-tool', ['lookup', 'service', KEYSTORE_SERVICE, 'account', account]);
|
|
282
|
+
const value = stdout.trim();
|
|
283
|
+
return value === '' ? null : value;
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// ── Factory ──────────────────────────────────────────────────────────────
|
|
291
|
+
/** TEST ONLY: force createLiteKeystore() to return null (as if no keystore
|
|
292
|
+
* integration exists for this platform), so the file-fallback path can be
|
|
293
|
+
* exercised deterministically without depending on the test runner's own
|
|
294
|
+
* platform having (or lacking) a real keystore tool. */
|
|
295
|
+
let _forceUnavailableForTest = false;
|
|
296
|
+
function _setForceUnavailableForTest(v) {
|
|
297
|
+
_forceUnavailableForTest = v;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Build the keystore for the current platform. `filePath` is only used by
|
|
301
|
+
* the Windows DPAPI implementation (it stores the protected blob at this
|
|
302
|
+
* path rather than in a separate keyed store, since DPAPI has no notion of
|
|
303
|
+
* named entries). macOS/Linux ignore it (they use the OS's own keyed store).
|
|
304
|
+
*/
|
|
305
|
+
function createLiteKeystore(filePath) {
|
|
306
|
+
if (_forceUnavailableForTest)
|
|
307
|
+
return null;
|
|
308
|
+
const platform = detectKeystorePlatform();
|
|
309
|
+
if (platform === 'windows-dpapi')
|
|
310
|
+
return new WindowsDpapiKeystore(filePath);
|
|
311
|
+
if (platform === 'macos-keychain')
|
|
312
|
+
return new MacosKeychainKeystore();
|
|
313
|
+
if (platform === 'linux-secret-tool')
|
|
314
|
+
return new LinuxSecretToolKeystore();
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
// ── File-fallback hardening (Security F3, part b) ──────────────────────────
|
|
318
|
+
//
|
|
319
|
+
// When the OS keystore is unavailable, lite-signing-key.ts falls back to the
|
|
320
|
+
// plaintext file it already used. `fs.writeFileSync(..., { mode: 0o600 })`
|
|
321
|
+
// is a NO-OP on Windows (NTFS ACLs govern access, not POSIX mode bits) --
|
|
322
|
+
// this was the actual gap Adversary #1 flagged. `icacls` is the Windows-
|
|
323
|
+
// native tool for setting a real, enforced ACL: strip inherited permissions
|
|
324
|
+
// and grant Full Control to ONLY the current user (and, implicitly,
|
|
325
|
+
// Administrators via the SYSTEM/built-in Administrators group icacls always
|
|
326
|
+
// preserves unless :r is paired with an explicit deny -- acceptable here
|
|
327
|
+
// since an Administrator already has an unconditional escalation path to
|
|
328
|
+
// any user's files on Windows regardless of this ACL). On macOS/Linux,
|
|
329
|
+
// POSIX mode 0600 (already set by the writeFileSync call site) IS
|
|
330
|
+
// meaningful and enforced by the kernel, so no extra step is needed there.
|
|
331
|
+
async function hardenFileAcl(filePath) {
|
|
332
|
+
if (process.platform !== 'win32') {
|
|
333
|
+
// POSIX mode 0600 on the writeFileSync call already does the job.
|
|
334
|
+
return { hardened: true, detail: 'POSIX file mode 0600 (kernel-enforced)' };
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
// /inheritance:r strips inherited ACEs; /grant:r replaces the grant list
|
|
338
|
+
// for the given principal with exactly what follows (Full Control),
|
|
339
|
+
// removing any broader access the file would otherwise have inherited
|
|
340
|
+
// from its parent directory.
|
|
341
|
+
const user = process.env['USERNAME'] ? `${process.env['USERDOMAIN'] ?? '.'}\\${process.env['USERNAME']}` : undefined;
|
|
342
|
+
if (user === undefined) {
|
|
343
|
+
return { hardened: false, detail: 'could not determine current Windows username for icacls' };
|
|
344
|
+
}
|
|
345
|
+
await execFileAsync('icacls', [filePath, '/inheritance:r', '/grant:r', `${user}:F`]);
|
|
346
|
+
return { hardened: true, detail: `icacls owner-only grant to ${user}` };
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
return { hardened: false, detail: `icacls failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* gap/lite-mode.ts — the ONE lite-mode flag (ADR_014 Section 10, the public
|
|
4
|
+
* daemon carve-out).
|
|
5
|
+
*
|
|
6
|
+
* Factored into its own leaf module (imports nothing else under gap/) so
|
|
7
|
+
* every module that needs to branch between the full gateway's
|
|
8
|
+
* managed/KMS-hybrid behavior and the lite daemon's operator-owned self-sign
|
|
9
|
+
* behavior can read/set the SAME flag without creating an import cycle.
|
|
10
|
+
* local-ingest-router.ts (receipt signing tier) and operator-enrollment.ts
|
|
11
|
+
* (enrollment-record seal tier, Security F1 fix) both depend on this; neither
|
|
12
|
+
* imports the other for this purpose.
|
|
13
|
+
*
|
|
14
|
+
* src/daemon.ts sets this once at boot. The full gateway (src/index.ts)
|
|
15
|
+
* never sets it, so every module's existing, already-paneled full-gateway
|
|
16
|
+
* behavior is byte-for-byte unchanged when this flag is false (the default).
|
|
17
|
+
*
|
|
18
|
+
* No em dashes. No AI attribution.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.isLiteMode = isLiteMode;
|
|
22
|
+
exports.setLiteMode = setLiteMode;
|
|
23
|
+
let _liteMode = process.env['SYNOI_LITE'] === '1';
|
|
24
|
+
/** Whether the current process is running the lite self-sign tier. */
|
|
25
|
+
function isLiteMode() {
|
|
26
|
+
return _liteMode;
|
|
27
|
+
}
|
|
28
|
+
/** daemon.ts / TEST ONLY: explicitly select lite mode. */
|
|
29
|
+
function setLiteMode(v) {
|
|
30
|
+
_liteMode = v;
|
|
31
|
+
}
|