speclock 2.5.0 → 3.5.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/README.md +99 -5
- package/package.json +15 -3
- package/src/cli/index.js +314 -1
- package/src/core/auth.js +341 -0
- package/src/core/compliance.js +1 -1
- package/src/core/crypto.js +158 -0
- package/src/core/engine.js +62 -0
- package/src/core/policy.js +719 -0
- package/src/core/sso.js +386 -0
- package/src/core/storage.js +23 -4
- package/src/core/telemetry.js +281 -0
- package/src/dashboard/index.html +338 -0
- package/src/mcp/http-server.js +248 -3
- package/src/mcp/server.js +172 -1
package/src/core/auth.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecLock API Key Authentication
|
|
3
|
+
* Provides API key generation, validation, rotation, and revocation.
|
|
4
|
+
* Keys are SHA-256 hashed before storage — raw keys never stored.
|
|
5
|
+
*
|
|
6
|
+
* Storage: .speclock/auth.json (gitignored)
|
|
7
|
+
* Key format: sl_key_<random hex>
|
|
8
|
+
*
|
|
9
|
+
* Developed by Sandeep Roy (https://github.com/sgroy10)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import crypto from "crypto";
|
|
15
|
+
|
|
16
|
+
const AUTH_FILE = "auth.json";
|
|
17
|
+
const KEY_PREFIX = "sl_key_";
|
|
18
|
+
|
|
19
|
+
// --- RBAC Role Definitions ---
|
|
20
|
+
|
|
21
|
+
export const ROLES = {
|
|
22
|
+
viewer: {
|
|
23
|
+
name: "viewer",
|
|
24
|
+
description: "Read-only access to context, events, and status",
|
|
25
|
+
permissions: ["read"],
|
|
26
|
+
},
|
|
27
|
+
developer: {
|
|
28
|
+
name: "developer",
|
|
29
|
+
description: "Read access + can override locks with reason",
|
|
30
|
+
permissions: ["read", "override"],
|
|
31
|
+
},
|
|
32
|
+
architect: {
|
|
33
|
+
name: "architect",
|
|
34
|
+
description: "Read + write locks, decisions, goals, notes",
|
|
35
|
+
permissions: ["read", "write", "override"],
|
|
36
|
+
},
|
|
37
|
+
admin: {
|
|
38
|
+
name: "admin",
|
|
39
|
+
description: "Full access including auth management and enforcement config",
|
|
40
|
+
permissions: ["read", "write", "override", "admin"],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Tool → required permission mapping
|
|
45
|
+
export const TOOL_PERMISSIONS = {
|
|
46
|
+
// Read-only tools
|
|
47
|
+
speclock_init: "read",
|
|
48
|
+
speclock_get_context: "read",
|
|
49
|
+
speclock_get_changes: "read",
|
|
50
|
+
speclock_get_events: "read",
|
|
51
|
+
speclock_session_briefing: "read",
|
|
52
|
+
speclock_repo_status: "read",
|
|
53
|
+
speclock_suggest_locks: "read",
|
|
54
|
+
speclock_detect_drift: "read",
|
|
55
|
+
speclock_health: "read",
|
|
56
|
+
speclock_report: "read",
|
|
57
|
+
speclock_verify_audit: "read",
|
|
58
|
+
speclock_export_compliance: "read",
|
|
59
|
+
speclock_override_history: "read",
|
|
60
|
+
speclock_semantic_audit: "read",
|
|
61
|
+
|
|
62
|
+
// Write tools
|
|
63
|
+
speclock_set_goal: "write",
|
|
64
|
+
speclock_add_lock: "write",
|
|
65
|
+
speclock_remove_lock: "write",
|
|
66
|
+
speclock_add_decision: "write",
|
|
67
|
+
speclock_add_note: "write",
|
|
68
|
+
speclock_set_deploy_facts: "write",
|
|
69
|
+
speclock_log_change: "write",
|
|
70
|
+
speclock_check_conflict: "read",
|
|
71
|
+
speclock_session_summary: "write",
|
|
72
|
+
speclock_checkpoint: "write",
|
|
73
|
+
speclock_apply_template: "write",
|
|
74
|
+
speclock_audit: "read",
|
|
75
|
+
|
|
76
|
+
// Override tools
|
|
77
|
+
speclock_override_lock: "override",
|
|
78
|
+
|
|
79
|
+
// Admin tools
|
|
80
|
+
speclock_set_enforcement: "admin",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// --- Path helpers ---
|
|
84
|
+
|
|
85
|
+
function authPath(root) {
|
|
86
|
+
return path.join(root, ".speclock", AUTH_FILE);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Auth store ---
|
|
90
|
+
|
|
91
|
+
function readAuthStore(root) {
|
|
92
|
+
const p = authPath(root);
|
|
93
|
+
if (!fs.existsSync(p)) {
|
|
94
|
+
return { enabled: false, keys: [] };
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
98
|
+
} catch {
|
|
99
|
+
return { enabled: false, keys: [] };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function writeAuthStore(root, store) {
|
|
104
|
+
const p = authPath(root);
|
|
105
|
+
fs.writeFileSync(p, JSON.stringify(store, null, 2));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function hashKey(rawKey) {
|
|
109
|
+
return crypto.createHash("sha256").update(rawKey).digest("hex");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function generateRawKey() {
|
|
113
|
+
return KEY_PREFIX + crypto.randomBytes(24).toString("hex");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- Gitignore auth.json ---
|
|
117
|
+
|
|
118
|
+
export function ensureAuthGitignored(root) {
|
|
119
|
+
const giPath = path.join(root, ".speclock", ".gitignore");
|
|
120
|
+
let content = "";
|
|
121
|
+
if (fs.existsSync(giPath)) {
|
|
122
|
+
content = fs.readFileSync(giPath, "utf-8");
|
|
123
|
+
}
|
|
124
|
+
if (!content.includes(AUTH_FILE)) {
|
|
125
|
+
const line = content.endsWith("\n") || content === "" ? AUTH_FILE + "\n" : "\n" + AUTH_FILE + "\n";
|
|
126
|
+
fs.appendFileSync(giPath, line);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- Public API ---
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if auth is enabled for this project.
|
|
134
|
+
*/
|
|
135
|
+
export function isAuthEnabled(root) {
|
|
136
|
+
const store = readAuthStore(root);
|
|
137
|
+
return store.enabled === true && store.keys.length > 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Enable authentication for this project.
|
|
142
|
+
*/
|
|
143
|
+
export function enableAuth(root) {
|
|
144
|
+
const store = readAuthStore(root);
|
|
145
|
+
store.enabled = true;
|
|
146
|
+
writeAuthStore(root, store);
|
|
147
|
+
ensureAuthGitignored(root);
|
|
148
|
+
return { success: true };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Disable authentication (all operations allowed).
|
|
153
|
+
*/
|
|
154
|
+
export function disableAuth(root) {
|
|
155
|
+
const store = readAuthStore(root);
|
|
156
|
+
store.enabled = false;
|
|
157
|
+
writeAuthStore(root, store);
|
|
158
|
+
return { success: true };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Create a new API key with a role and optional name.
|
|
163
|
+
* Returns the raw key (only shown once).
|
|
164
|
+
*/
|
|
165
|
+
export function createApiKey(root, role, name = "") {
|
|
166
|
+
if (!ROLES[role]) {
|
|
167
|
+
return { success: false, error: `Invalid role: "${role}". Valid roles: ${Object.keys(ROLES).join(", ")}` };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const store = readAuthStore(root);
|
|
171
|
+
const rawKey = generateRawKey();
|
|
172
|
+
const keyHash = hashKey(rawKey);
|
|
173
|
+
const keyId = "key_" + crypto.randomBytes(4).toString("hex");
|
|
174
|
+
|
|
175
|
+
store.keys.push({
|
|
176
|
+
id: keyId,
|
|
177
|
+
name: name || `${role}-${keyId}`,
|
|
178
|
+
hash: keyHash,
|
|
179
|
+
role,
|
|
180
|
+
createdAt: new Date().toISOString(),
|
|
181
|
+
lastUsed: null,
|
|
182
|
+
active: true,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (!store.enabled) {
|
|
186
|
+
store.enabled = true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
writeAuthStore(root, store);
|
|
190
|
+
ensureAuthGitignored(root);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
keyId,
|
|
195
|
+
rawKey,
|
|
196
|
+
role,
|
|
197
|
+
name: name || `${role}-${keyId}`,
|
|
198
|
+
message: "Save this key — it cannot be retrieved later.",
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Validate an API key. Returns the key record if valid.
|
|
204
|
+
*/
|
|
205
|
+
export function validateApiKey(root, rawKey) {
|
|
206
|
+
const store = readAuthStore(root);
|
|
207
|
+
|
|
208
|
+
if (!store.enabled) {
|
|
209
|
+
// Auth not enabled — allow everything (backward compatible)
|
|
210
|
+
return { valid: true, role: "admin", authEnabled: false };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!rawKey) {
|
|
214
|
+
return { valid: false, error: "API key required. Auth is enabled for this project." };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const keyHash = hashKey(rawKey);
|
|
218
|
+
const keyRecord = store.keys.find(k => k.hash === keyHash && k.active);
|
|
219
|
+
|
|
220
|
+
if (!keyRecord) {
|
|
221
|
+
return { valid: false, error: "Invalid or revoked API key." };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Update last used
|
|
225
|
+
keyRecord.lastUsed = new Date().toISOString();
|
|
226
|
+
writeAuthStore(root, store);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
valid: true,
|
|
230
|
+
keyId: keyRecord.id,
|
|
231
|
+
role: keyRecord.role,
|
|
232
|
+
name: keyRecord.name,
|
|
233
|
+
authEnabled: true,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Check if a role has permission for a specific tool/action.
|
|
239
|
+
*/
|
|
240
|
+
export function checkPermission(role, toolName) {
|
|
241
|
+
const roleConfig = ROLES[role];
|
|
242
|
+
if (!roleConfig) return false;
|
|
243
|
+
|
|
244
|
+
// Admin has all permissions
|
|
245
|
+
if (role === "admin") return true;
|
|
246
|
+
|
|
247
|
+
const requiredPermission = TOOL_PERMISSIONS[toolName];
|
|
248
|
+
if (!requiredPermission) {
|
|
249
|
+
// Unknown tool — default to admin-only
|
|
250
|
+
return role === "admin";
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return roleConfig.permissions.includes(requiredPermission);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Rotate an API key — revoke old, create new with same role and name.
|
|
258
|
+
*/
|
|
259
|
+
export function rotateApiKey(root, keyId) {
|
|
260
|
+
const store = readAuthStore(root);
|
|
261
|
+
const keyRecord = store.keys.find(k => k.id === keyId && k.active);
|
|
262
|
+
|
|
263
|
+
if (!keyRecord) {
|
|
264
|
+
return { success: false, error: `Active key not found: ${keyId}` };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Revoke old key
|
|
268
|
+
keyRecord.active = false;
|
|
269
|
+
keyRecord.revokedAt = new Date().toISOString();
|
|
270
|
+
keyRecord.revokeReason = "rotated";
|
|
271
|
+
|
|
272
|
+
// Create new key with same role/name
|
|
273
|
+
const rawKey = generateRawKey();
|
|
274
|
+
const newKeyHash = hashKey(rawKey);
|
|
275
|
+
const newKeyId = "key_" + crypto.randomBytes(4).toString("hex");
|
|
276
|
+
|
|
277
|
+
store.keys.push({
|
|
278
|
+
id: newKeyId,
|
|
279
|
+
name: keyRecord.name,
|
|
280
|
+
hash: newKeyHash,
|
|
281
|
+
role: keyRecord.role,
|
|
282
|
+
createdAt: new Date().toISOString(),
|
|
283
|
+
lastUsed: null,
|
|
284
|
+
active: true,
|
|
285
|
+
rotatedFrom: keyId,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
writeAuthStore(root, store);
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
success: true,
|
|
292
|
+
oldKeyId: keyId,
|
|
293
|
+
newKeyId,
|
|
294
|
+
rawKey,
|
|
295
|
+
role: keyRecord.role,
|
|
296
|
+
message: "Key rotated. Save the new key — it cannot be retrieved later.",
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Revoke an API key.
|
|
302
|
+
*/
|
|
303
|
+
export function revokeApiKey(root, keyId, reason = "manual") {
|
|
304
|
+
const store = readAuthStore(root);
|
|
305
|
+
const keyRecord = store.keys.find(k => k.id === keyId && k.active);
|
|
306
|
+
|
|
307
|
+
if (!keyRecord) {
|
|
308
|
+
return { success: false, error: `Active key not found: ${keyId}` };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
keyRecord.active = false;
|
|
312
|
+
keyRecord.revokedAt = new Date().toISOString();
|
|
313
|
+
keyRecord.revokeReason = reason;
|
|
314
|
+
writeAuthStore(root, store);
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
success: true,
|
|
318
|
+
keyId,
|
|
319
|
+
name: keyRecord.name,
|
|
320
|
+
role: keyRecord.role,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* List all API keys (hashes hidden).
|
|
326
|
+
*/
|
|
327
|
+
export function listApiKeys(root) {
|
|
328
|
+
const store = readAuthStore(root);
|
|
329
|
+
return {
|
|
330
|
+
enabled: store.enabled,
|
|
331
|
+
keys: store.keys.map(k => ({
|
|
332
|
+
id: k.id,
|
|
333
|
+
name: k.name,
|
|
334
|
+
role: k.role,
|
|
335
|
+
active: k.active,
|
|
336
|
+
createdAt: k.createdAt,
|
|
337
|
+
lastUsed: k.lastUsed,
|
|
338
|
+
revokedAt: k.revokedAt || null,
|
|
339
|
+
})),
|
|
340
|
+
};
|
|
341
|
+
}
|
package/src/core/compliance.js
CHANGED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecLock Encrypted Storage
|
|
3
|
+
* AES-256-GCM encryption for brain.json and events.log at rest.
|
|
4
|
+
*
|
|
5
|
+
* Key derivation: PBKDF2 from SPECLOCK_ENCRYPTION_KEY env var
|
|
6
|
+
* Transparent: encrypt on write, decrypt on read
|
|
7
|
+
* Format: Base64(IV:AuthTag:CipherText) per line/file
|
|
8
|
+
*
|
|
9
|
+
* Developed by Sandeep Roy (https://github.com/sgroy10)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import crypto from "crypto";
|
|
13
|
+
|
|
14
|
+
const ALGORITHM = "aes-256-gcm";
|
|
15
|
+
const IV_LENGTH = 16;
|
|
16
|
+
const AUTH_TAG_LENGTH = 16;
|
|
17
|
+
const SALT = "speclock-v3-salt"; // Static salt (key is already strong from env)
|
|
18
|
+
const ITERATIONS = 100000;
|
|
19
|
+
const KEY_LENGTH = 32;
|
|
20
|
+
const ENCRYPTED_MARKER = "SPECLOCK_ENCRYPTED:";
|
|
21
|
+
|
|
22
|
+
// --- Key Derivation ---
|
|
23
|
+
|
|
24
|
+
let _derivedKey = null;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if encryption is enabled (env var set).
|
|
28
|
+
*/
|
|
29
|
+
export function isEncryptionEnabled() {
|
|
30
|
+
return !!process.env.SPECLOCK_ENCRYPTION_KEY;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Derive a 256-bit key from the master password.
|
|
35
|
+
*/
|
|
36
|
+
export function deriveKey(masterKey) {
|
|
37
|
+
if (!masterKey) {
|
|
38
|
+
throw new Error("SPECLOCK_ENCRYPTION_KEY is required for encryption.");
|
|
39
|
+
}
|
|
40
|
+
return crypto.pbkdf2Sync(masterKey, SALT, ITERATIONS, KEY_LENGTH, "sha512");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get or derive the encryption key from env var.
|
|
45
|
+
*/
|
|
46
|
+
function getKey() {
|
|
47
|
+
if (_derivedKey) return _derivedKey;
|
|
48
|
+
const masterKey = process.env.SPECLOCK_ENCRYPTION_KEY;
|
|
49
|
+
if (!masterKey) {
|
|
50
|
+
throw new Error("SPECLOCK_ENCRYPTION_KEY environment variable is not set.");
|
|
51
|
+
}
|
|
52
|
+
_derivedKey = deriveKey(masterKey);
|
|
53
|
+
return _derivedKey;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Clear cached key (for testing).
|
|
58
|
+
*/
|
|
59
|
+
export function clearKeyCache() {
|
|
60
|
+
_derivedKey = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// --- Encrypt / Decrypt ---
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Encrypt a string. Returns: SPECLOCK_ENCRYPTED:<base64(iv:tag:ciphertext)>
|
|
67
|
+
*/
|
|
68
|
+
export function encrypt(plaintext) {
|
|
69
|
+
const key = getKey();
|
|
70
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
71
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
72
|
+
|
|
73
|
+
let encrypted = cipher.update(plaintext, "utf-8");
|
|
74
|
+
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|
75
|
+
const authTag = cipher.getAuthTag();
|
|
76
|
+
|
|
77
|
+
// Pack: IV + AuthTag + Ciphertext
|
|
78
|
+
const packed = Buffer.concat([iv, authTag, encrypted]);
|
|
79
|
+
return ENCRYPTED_MARKER + packed.toString("base64");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Decrypt a string. Input: SPECLOCK_ENCRYPTED:<base64(iv:tag:ciphertext)>
|
|
84
|
+
*/
|
|
85
|
+
export function decrypt(ciphertext) {
|
|
86
|
+
if (!ciphertext.startsWith(ENCRYPTED_MARKER)) {
|
|
87
|
+
// Not encrypted — return as-is (backward compatible with plaintext)
|
|
88
|
+
return ciphertext;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const key = getKey();
|
|
92
|
+
const packed = Buffer.from(ciphertext.slice(ENCRYPTED_MARKER.length), "base64");
|
|
93
|
+
|
|
94
|
+
if (packed.length < IV_LENGTH + AUTH_TAG_LENGTH + 1) {
|
|
95
|
+
throw new Error("Invalid encrypted data: too short.");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const iv = packed.subarray(0, IV_LENGTH);
|
|
99
|
+
const authTag = packed.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
|
|
100
|
+
const encrypted = packed.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
|
|
101
|
+
|
|
102
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
103
|
+
decipher.setAuthTag(authTag);
|
|
104
|
+
|
|
105
|
+
let decrypted = decipher.update(encrypted);
|
|
106
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
107
|
+
return decrypted.toString("utf-8");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if a string is encrypted.
|
|
112
|
+
*/
|
|
113
|
+
export function isEncrypted(data) {
|
|
114
|
+
return typeof data === "string" && data.startsWith(ENCRYPTED_MARKER);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --- File-level helpers ---
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Encrypt a JSON object for storage.
|
|
121
|
+
*/
|
|
122
|
+
export function encryptJSON(obj) {
|
|
123
|
+
const json = JSON.stringify(obj, null, 2);
|
|
124
|
+
return encrypt(json);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Decrypt and parse a JSON string.
|
|
129
|
+
*/
|
|
130
|
+
export function decryptJSON(data) {
|
|
131
|
+
const json = decrypt(data);
|
|
132
|
+
return JSON.parse(json);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Encrypt each line of an events log (JSONL format).
|
|
137
|
+
* Each line is encrypted independently.
|
|
138
|
+
*/
|
|
139
|
+
export function encryptLines(text) {
|
|
140
|
+
if (!text || !text.trim()) return text;
|
|
141
|
+
const lines = text.trim().split("\n");
|
|
142
|
+
return lines.map(line => {
|
|
143
|
+
if (!line.trim()) return line;
|
|
144
|
+
return encrypt(line);
|
|
145
|
+
}).join("\n") + "\n";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Decrypt each line of an encrypted events log.
|
|
150
|
+
*/
|
|
151
|
+
export function decryptLines(text) {
|
|
152
|
+
if (!text || !text.trim()) return text;
|
|
153
|
+
const lines = text.trim().split("\n");
|
|
154
|
+
return lines.map(line => {
|
|
155
|
+
if (!line.trim()) return line;
|
|
156
|
+
return decrypt(line);
|
|
157
|
+
}).join("\n") + "\n";
|
|
158
|
+
}
|
package/src/core/engine.js
CHANGED
|
@@ -532,3 +532,65 @@ export function applyTemplate(root, templateName) {
|
|
|
532
532
|
export { verifyAuditChain } from "./audit.js";
|
|
533
533
|
export { exportCompliance } from "./compliance.js";
|
|
534
534
|
export { checkFeature, checkLimits, getLicenseInfo } from "./license.js";
|
|
535
|
+
|
|
536
|
+
// --- Authentication & RBAC (v3.0) ---
|
|
537
|
+
export {
|
|
538
|
+
isAuthEnabled,
|
|
539
|
+
enableAuth,
|
|
540
|
+
disableAuth,
|
|
541
|
+
createApiKey,
|
|
542
|
+
validateApiKey,
|
|
543
|
+
checkPermission,
|
|
544
|
+
rotateApiKey,
|
|
545
|
+
revokeApiKey,
|
|
546
|
+
listApiKeys,
|
|
547
|
+
ROLES,
|
|
548
|
+
TOOL_PERMISSIONS,
|
|
549
|
+
} from "./auth.js";
|
|
550
|
+
|
|
551
|
+
// --- Encrypted Storage (v3.0) ---
|
|
552
|
+
export {
|
|
553
|
+
isEncryptionEnabled,
|
|
554
|
+
isEncrypted,
|
|
555
|
+
encrypt,
|
|
556
|
+
decrypt,
|
|
557
|
+
clearKeyCache,
|
|
558
|
+
} from "./crypto.js";
|
|
559
|
+
|
|
560
|
+
// --- Policy-as-Code (v3.5) ---
|
|
561
|
+
export {
|
|
562
|
+
loadPolicy,
|
|
563
|
+
savePolicy,
|
|
564
|
+
initPolicy,
|
|
565
|
+
addPolicyRule,
|
|
566
|
+
removePolicyRule,
|
|
567
|
+
listPolicyRules,
|
|
568
|
+
evaluatePolicy,
|
|
569
|
+
exportPolicy,
|
|
570
|
+
importPolicy,
|
|
571
|
+
generateNotifications,
|
|
572
|
+
} from "./policy.js";
|
|
573
|
+
|
|
574
|
+
// --- Telemetry & Analytics (v3.5) ---
|
|
575
|
+
export {
|
|
576
|
+
isTelemetryEnabled,
|
|
577
|
+
trackToolUsage,
|
|
578
|
+
trackConflict,
|
|
579
|
+
trackFeature,
|
|
580
|
+
trackSession,
|
|
581
|
+
getTelemetrySummary,
|
|
582
|
+
flushToRemote,
|
|
583
|
+
resetTelemetry,
|
|
584
|
+
} from "./telemetry.js";
|
|
585
|
+
|
|
586
|
+
// --- OAuth/OIDC SSO (v3.5) ---
|
|
587
|
+
export {
|
|
588
|
+
isSSOEnabled,
|
|
589
|
+
getSSOConfig,
|
|
590
|
+
saveSSOConfig,
|
|
591
|
+
getAuthorizationUrl,
|
|
592
|
+
handleCallback,
|
|
593
|
+
validateSession,
|
|
594
|
+
revokeSession,
|
|
595
|
+
listSessions,
|
|
596
|
+
} from "./sso.js";
|