chainlesschain 0.37.12 → 0.40.1
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/package.json +3 -2
- package/src/commands/agent.js +7 -1
- package/src/commands/ask.js +24 -9
- package/src/commands/chat.js +7 -1
- package/src/commands/cli-anything.js +266 -0
- package/src/commands/compliance.js +216 -0
- package/src/commands/dao.js +312 -0
- package/src/commands/dlp.js +278 -0
- package/src/commands/evomap.js +558 -0
- package/src/commands/hardening.js +230 -0
- package/src/commands/matrix.js +168 -0
- package/src/commands/nostr.js +185 -0
- package/src/commands/pqc.js +162 -0
- package/src/commands/scim.js +218 -0
- package/src/commands/serve.js +109 -0
- package/src/commands/siem.js +156 -0
- package/src/commands/social.js +480 -0
- package/src/commands/terraform.js +148 -0
- package/src/constants.js +1 -0
- package/src/index.js +60 -0
- package/src/lib/autonomous-agent.js +487 -0
- package/src/lib/cli-anything-bridge.js +379 -0
- package/src/lib/cli-context-engineering.js +472 -0
- package/src/lib/compliance-manager.js +290 -0
- package/src/lib/content-recommender.js +205 -0
- package/src/lib/dao-governance.js +296 -0
- package/src/lib/dlp-engine.js +304 -0
- package/src/lib/evomap-client.js +135 -0
- package/src/lib/evomap-federation.js +240 -0
- package/src/lib/evomap-governance.js +250 -0
- package/src/lib/evomap-manager.js +227 -0
- package/src/lib/git-integration.js +1 -1
- package/src/lib/hardening-manager.js +275 -0
- package/src/lib/llm-providers.js +14 -1
- package/src/lib/matrix-bridge.js +196 -0
- package/src/lib/nostr-bridge.js +195 -0
- package/src/lib/permanent-memory.js +370 -0
- package/src/lib/plan-mode.js +211 -0
- package/src/lib/pqc-manager.js +196 -0
- package/src/lib/scim-manager.js +212 -0
- package/src/lib/session-manager.js +38 -0
- package/src/lib/siem-exporter.js +137 -0
- package/src/lib/social-manager.js +283 -0
- package/src/lib/task-model-selector.js +232 -0
- package/src/lib/terraform-manager.js +201 -0
- package/src/lib/ws-server.js +474 -0
- package/src/repl/agent-repl.js +796 -41
- package/src/repl/chat-repl.js +14 -6
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAO Governance v2 — proposals, quadratic voting, delegation,
|
|
3
|
+
* treasury management, and governance statistics.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
|
|
8
|
+
/* ── In-memory stores ──────────────────────────────────────── */
|
|
9
|
+
const _proposals = new Map();
|
|
10
|
+
const _votes = new Map();
|
|
11
|
+
const _delegations = new Map();
|
|
12
|
+
const _treasury = { balance: 0, allocations: [] };
|
|
13
|
+
|
|
14
|
+
let _config = {
|
|
15
|
+
votingPeriod: 604800000, // 7 days in ms
|
|
16
|
+
quorum: 0.1, // 10%
|
|
17
|
+
executionDelay: 86400000, // 1 day in ms
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/* ── Schema ────────────────────────────────────────────────── */
|
|
21
|
+
|
|
22
|
+
export function ensureDAOv2Tables(db) {
|
|
23
|
+
db.exec(`
|
|
24
|
+
CREATE TABLE IF NOT EXISTS dao_v2_proposals (
|
|
25
|
+
id TEXT PRIMARY KEY,
|
|
26
|
+
title TEXT,
|
|
27
|
+
description TEXT,
|
|
28
|
+
proposer TEXT,
|
|
29
|
+
status TEXT DEFAULT 'draft',
|
|
30
|
+
votes_for REAL DEFAULT 0,
|
|
31
|
+
votes_against REAL DEFAULT 0,
|
|
32
|
+
voting_type TEXT DEFAULT 'simple',
|
|
33
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
34
|
+
ends_at TEXT,
|
|
35
|
+
executed_at TEXT
|
|
36
|
+
)
|
|
37
|
+
`);
|
|
38
|
+
db.exec(`
|
|
39
|
+
CREATE TABLE IF NOT EXISTS dao_v2_votes (
|
|
40
|
+
id TEXT PRIMARY KEY,
|
|
41
|
+
proposal_id TEXT,
|
|
42
|
+
voter TEXT,
|
|
43
|
+
weight REAL,
|
|
44
|
+
direction TEXT,
|
|
45
|
+
delegated_from TEXT,
|
|
46
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
47
|
+
)
|
|
48
|
+
`);
|
|
49
|
+
db.exec(`
|
|
50
|
+
CREATE TABLE IF NOT EXISTS dao_v2_treasury (
|
|
51
|
+
id TEXT PRIMARY KEY,
|
|
52
|
+
type TEXT,
|
|
53
|
+
amount REAL,
|
|
54
|
+
description TEXT,
|
|
55
|
+
proposal_id TEXT,
|
|
56
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
57
|
+
)
|
|
58
|
+
`);
|
|
59
|
+
db.exec(`
|
|
60
|
+
CREATE TABLE IF NOT EXISTS dao_v2_delegations (
|
|
61
|
+
delegator TEXT PRIMARY KEY,
|
|
62
|
+
delegate TEXT,
|
|
63
|
+
weight REAL DEFAULT 1,
|
|
64
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
65
|
+
)
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ── Proposals ─────────────────────────────────────────────── */
|
|
70
|
+
|
|
71
|
+
export function propose(db, title, description, proposer, options = {}) {
|
|
72
|
+
if (!title) throw new Error("Title is required");
|
|
73
|
+
if (!proposer) throw new Error("Proposer is required");
|
|
74
|
+
|
|
75
|
+
const id = `prop-${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
|
|
76
|
+
const now = new Date().toISOString();
|
|
77
|
+
const votingType = options.votingType || "simple";
|
|
78
|
+
const endsAt = new Date(Date.now() + _config.votingPeriod).toISOString();
|
|
79
|
+
|
|
80
|
+
const proposal = {
|
|
81
|
+
id,
|
|
82
|
+
title,
|
|
83
|
+
description: description || "",
|
|
84
|
+
proposer,
|
|
85
|
+
status: "active",
|
|
86
|
+
votesFor: 0,
|
|
87
|
+
votesAgainst: 0,
|
|
88
|
+
votingType,
|
|
89
|
+
createdAt: now,
|
|
90
|
+
endsAt,
|
|
91
|
+
executedAt: null,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
_proposals.set(id, proposal);
|
|
95
|
+
|
|
96
|
+
db.prepare(
|
|
97
|
+
`INSERT INTO dao_v2_proposals (id, title, description, proposer, status, votes_for, votes_against, voting_type, created_at, ends_at, executed_at)
|
|
98
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
99
|
+
).run(
|
|
100
|
+
id,
|
|
101
|
+
proposal.title,
|
|
102
|
+
proposal.description,
|
|
103
|
+
proposal.proposer,
|
|
104
|
+
proposal.status,
|
|
105
|
+
0,
|
|
106
|
+
0,
|
|
107
|
+
votingType,
|
|
108
|
+
now,
|
|
109
|
+
endsAt,
|
|
110
|
+
null,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return proposal;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* ── Voting ────────────────────────────────────────────────── */
|
|
117
|
+
|
|
118
|
+
export function vote(db, proposalId, voter, direction, weight = 1) {
|
|
119
|
+
const proposal = _proposals.get(proposalId);
|
|
120
|
+
if (!proposal) throw new Error(`Proposal not found: ${proposalId}`);
|
|
121
|
+
if (proposal.status !== "active") {
|
|
122
|
+
throw new Error(`Proposal is not active: ${proposal.status}`);
|
|
123
|
+
}
|
|
124
|
+
if (direction !== "for" && direction !== "against") {
|
|
125
|
+
throw new Error('Direction must be "for" or "against"');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Quadratic voting: effective weight = sqrt(weight)
|
|
129
|
+
const effectiveWeight =
|
|
130
|
+
proposal.votingType === "quadratic" ? Math.sqrt(weight) : weight;
|
|
131
|
+
|
|
132
|
+
const voteId = `vote-${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
|
|
133
|
+
const now = new Date().toISOString();
|
|
134
|
+
|
|
135
|
+
if (direction === "for") {
|
|
136
|
+
proposal.votesFor += effectiveWeight;
|
|
137
|
+
} else {
|
|
138
|
+
proposal.votesAgainst += effectiveWeight;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const voteRecord = {
|
|
142
|
+
id: voteId,
|
|
143
|
+
proposalId,
|
|
144
|
+
voter,
|
|
145
|
+
weight: effectiveWeight,
|
|
146
|
+
direction,
|
|
147
|
+
delegatedFrom: null,
|
|
148
|
+
createdAt: now,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
_votes.set(voteId, voteRecord);
|
|
152
|
+
|
|
153
|
+
db.prepare(
|
|
154
|
+
`INSERT INTO dao_v2_votes (id, proposal_id, voter, weight, direction, delegated_from, created_at)
|
|
155
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
156
|
+
).run(voteId, proposalId, voter, effectiveWeight, direction, null, now);
|
|
157
|
+
|
|
158
|
+
db.prepare(
|
|
159
|
+
`UPDATE dao_v2_proposals SET votes_for = ?, votes_against = ? WHERE id = ?`,
|
|
160
|
+
).run(proposal.votesFor, proposal.votesAgainst, proposalId);
|
|
161
|
+
|
|
162
|
+
return { voteId, proposalId, weight: effectiveWeight, direction };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* ── Delegation ────────────────────────────────────────────── */
|
|
166
|
+
|
|
167
|
+
export function delegate(db, delegator, delegateTo, weight = 1) {
|
|
168
|
+
if (!delegator) throw new Error("Delegator is required");
|
|
169
|
+
if (!delegateTo) throw new Error("Delegate is required");
|
|
170
|
+
|
|
171
|
+
const now = new Date().toISOString();
|
|
172
|
+
|
|
173
|
+
_delegations.set(delegator, { delegator, delegate: delegateTo, weight });
|
|
174
|
+
|
|
175
|
+
db.prepare(
|
|
176
|
+
`INSERT OR REPLACE INTO dao_v2_delegations (delegator, delegate, weight, created_at)
|
|
177
|
+
VALUES (?, ?, ?, ?)`,
|
|
178
|
+
).run(delegator, delegateTo, weight, now);
|
|
179
|
+
|
|
180
|
+
return { delegator, delegate: delegateTo, weight };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* ── Execution ─────────────────────────────────────────────── */
|
|
184
|
+
|
|
185
|
+
export function execute(db, proposalId) {
|
|
186
|
+
const proposal = _proposals.get(proposalId);
|
|
187
|
+
if (!proposal) throw new Error(`Proposal not found: ${proposalId}`);
|
|
188
|
+
if (proposal.votesFor <= proposal.votesAgainst) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"Proposal has not passed (votesFor must exceed votesAgainst)",
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const now = new Date().toISOString();
|
|
195
|
+
proposal.status = "executed";
|
|
196
|
+
proposal.executedAt = now;
|
|
197
|
+
|
|
198
|
+
db.prepare(
|
|
199
|
+
`UPDATE dao_v2_proposals SET status = ?, executed_at = ? WHERE id = ?`,
|
|
200
|
+
).run("executed", now, proposalId);
|
|
201
|
+
|
|
202
|
+
return { proposalId, status: "executed" };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* ── Treasury ──────────────────────────────────────────────── */
|
|
206
|
+
|
|
207
|
+
export function getTreasury() {
|
|
208
|
+
return {
|
|
209
|
+
balance: _treasury.balance,
|
|
210
|
+
allocations: [..._treasury.allocations],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function allocate(db, proposalId, amount, description) {
|
|
215
|
+
if (!proposalId) throw new Error("Proposal ID is required");
|
|
216
|
+
if (amount <= 0) throw new Error("Amount must be positive");
|
|
217
|
+
if (_treasury.balance < amount) {
|
|
218
|
+
throw new Error("Insufficient treasury balance");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const id = `alloc-${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
|
|
222
|
+
const now = new Date().toISOString();
|
|
223
|
+
|
|
224
|
+
_treasury.balance -= amount;
|
|
225
|
+
|
|
226
|
+
const allocation = {
|
|
227
|
+
id,
|
|
228
|
+
proposalId,
|
|
229
|
+
amount,
|
|
230
|
+
description: description || "",
|
|
231
|
+
date: now,
|
|
232
|
+
};
|
|
233
|
+
_treasury.allocations.push(allocation);
|
|
234
|
+
|
|
235
|
+
db.prepare(
|
|
236
|
+
`INSERT INTO dao_v2_treasury (id, type, amount, description, proposal_id, created_at)
|
|
237
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
238
|
+
).run(id, "allocation", amount, description || "", proposalId, now);
|
|
239
|
+
|
|
240
|
+
return allocation;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function depositToTreasury(db, amount, description) {
|
|
244
|
+
if (amount <= 0) throw new Error("Amount must be positive");
|
|
245
|
+
|
|
246
|
+
const id = `dep-${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
|
|
247
|
+
const now = new Date().toISOString();
|
|
248
|
+
|
|
249
|
+
_treasury.balance += amount;
|
|
250
|
+
|
|
251
|
+
db.prepare(
|
|
252
|
+
`INSERT INTO dao_v2_treasury (id, type, amount, description, proposal_id, created_at)
|
|
253
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
254
|
+
).run(id, "deposit", amount, description || "", null, now);
|
|
255
|
+
|
|
256
|
+
return { id, amount, balance: _treasury.balance };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* ── Stats ─────────────────────────────────────────────────── */
|
|
260
|
+
|
|
261
|
+
export function getStats() {
|
|
262
|
+
const all = [..._proposals.values()];
|
|
263
|
+
return {
|
|
264
|
+
totalProposals: all.length,
|
|
265
|
+
active: all.filter((p) => p.status === "active").length,
|
|
266
|
+
executed: all.filter((p) => p.status === "executed").length,
|
|
267
|
+
delegations: _delegations.size,
|
|
268
|
+
treasury: _treasury.balance,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* ── Configuration ─────────────────────────────────────────── */
|
|
273
|
+
|
|
274
|
+
export function configure(config) {
|
|
275
|
+
if (config.votingPeriod !== undefined)
|
|
276
|
+
_config.votingPeriod = config.votingPeriod;
|
|
277
|
+
if (config.quorum !== undefined) _config.quorum = config.quorum;
|
|
278
|
+
if (config.executionDelay !== undefined)
|
|
279
|
+
_config.executionDelay = config.executionDelay;
|
|
280
|
+
return { ..._config };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* ── Reset (for testing) ───────────────────────────────────── */
|
|
284
|
+
|
|
285
|
+
export function _resetState() {
|
|
286
|
+
_proposals.clear();
|
|
287
|
+
_votes.clear();
|
|
288
|
+
_delegations.clear();
|
|
289
|
+
_treasury.balance = 0;
|
|
290
|
+
_treasury.allocations = [];
|
|
291
|
+
_config = {
|
|
292
|
+
votingPeriod: 604800000,
|
|
293
|
+
quorum: 0.1,
|
|
294
|
+
executionDelay: 86400000,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DLP Engine — data loss prevention scanning, incident management,
|
|
3
|
+
* policy creation, and statistics.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
|
|
8
|
+
/* ── In-memory stores ──────────────────────────────────────── */
|
|
9
|
+
const _incidents = new Map();
|
|
10
|
+
const _policies = new Map();
|
|
11
|
+
let _scanCount = 0;
|
|
12
|
+
let _blockCount = 0;
|
|
13
|
+
let _alertCount = 0;
|
|
14
|
+
|
|
15
|
+
const DLP_ACTIONS = {
|
|
16
|
+
ALLOW: "allow",
|
|
17
|
+
ALERT: "alert",
|
|
18
|
+
BLOCK: "block",
|
|
19
|
+
QUARANTINE: "quarantine",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/* ── Schema ────────────────────────────────────────────────── */
|
|
23
|
+
|
|
24
|
+
export function ensureDLPTables(db) {
|
|
25
|
+
db.exec(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS dlp_incidents (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
policy_id TEXT,
|
|
29
|
+
channel TEXT,
|
|
30
|
+
action_taken TEXT,
|
|
31
|
+
content_hash TEXT,
|
|
32
|
+
matched_patterns TEXT,
|
|
33
|
+
severity TEXT DEFAULT 'medium',
|
|
34
|
+
user_id TEXT,
|
|
35
|
+
metadata TEXT,
|
|
36
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
37
|
+
resolved_at TEXT,
|
|
38
|
+
resolution TEXT
|
|
39
|
+
)
|
|
40
|
+
`);
|
|
41
|
+
db.exec(`
|
|
42
|
+
CREATE TABLE IF NOT EXISTS dlp_policies (
|
|
43
|
+
id TEXT PRIMARY KEY,
|
|
44
|
+
name TEXT NOT NULL,
|
|
45
|
+
patterns TEXT,
|
|
46
|
+
keywords TEXT,
|
|
47
|
+
action TEXT DEFAULT 'alert',
|
|
48
|
+
severity TEXT DEFAULT 'medium',
|
|
49
|
+
enabled INTEGER DEFAULT 1,
|
|
50
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
51
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
52
|
+
)
|
|
53
|
+
`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ── Content Scanning ─────────────────────────────────────── */
|
|
57
|
+
|
|
58
|
+
export function scanContent(db, content, channel, userId) {
|
|
59
|
+
if (!content) throw new Error("Content is required");
|
|
60
|
+
|
|
61
|
+
_scanCount++;
|
|
62
|
+
const policies = [..._policies.values()].filter((p) => p.enabled);
|
|
63
|
+
const matchedPolicies = [];
|
|
64
|
+
let highestAction = DLP_ACTIONS.ALLOW;
|
|
65
|
+
|
|
66
|
+
const actionPriority = { allow: 0, alert: 1, block: 2, quarantine: 3 };
|
|
67
|
+
|
|
68
|
+
for (const policy of policies) {
|
|
69
|
+
const patterns = policy.patterns || [];
|
|
70
|
+
const keywords = policy.keywords || [];
|
|
71
|
+
let matched = false;
|
|
72
|
+
|
|
73
|
+
for (const pattern of patterns) {
|
|
74
|
+
try {
|
|
75
|
+
if (new RegExp(pattern, "i").test(content)) {
|
|
76
|
+
matched = true;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
} catch (_err) {
|
|
80
|
+
// Invalid regex — skip
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!matched) {
|
|
85
|
+
for (const kw of keywords) {
|
|
86
|
+
if (content.toLowerCase().includes(kw.toLowerCase())) {
|
|
87
|
+
matched = true;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (matched) {
|
|
94
|
+
matchedPolicies.push(policy);
|
|
95
|
+
if (
|
|
96
|
+
(actionPriority[policy.action] || 0) >
|
|
97
|
+
(actionPriority[highestAction] || 0)
|
|
98
|
+
) {
|
|
99
|
+
highestAction = policy.action;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const incidents = [];
|
|
105
|
+
if (matchedPolicies.length > 0) {
|
|
106
|
+
for (const policy of matchedPolicies) {
|
|
107
|
+
const incidentId = crypto.randomUUID();
|
|
108
|
+
const now = new Date().toISOString();
|
|
109
|
+
const contentHash = crypto
|
|
110
|
+
.createHash("sha256")
|
|
111
|
+
.update(content)
|
|
112
|
+
.digest("hex");
|
|
113
|
+
|
|
114
|
+
const incident = {
|
|
115
|
+
id: incidentId,
|
|
116
|
+
policyId: policy.id,
|
|
117
|
+
channel: channel || "unknown",
|
|
118
|
+
actionTaken: policy.action,
|
|
119
|
+
contentHash,
|
|
120
|
+
matchedPatterns: policy.patterns || [],
|
|
121
|
+
severity: policy.severity,
|
|
122
|
+
userId: userId || "unknown",
|
|
123
|
+
createdAt: now,
|
|
124
|
+
resolvedAt: null,
|
|
125
|
+
resolution: null,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
_incidents.set(incidentId, incident);
|
|
129
|
+
incidents.push(incident);
|
|
130
|
+
|
|
131
|
+
db.prepare(
|
|
132
|
+
`INSERT INTO dlp_incidents (id, policy_id, channel, action_taken, content_hash, matched_patterns, severity, user_id, metadata, created_at)
|
|
133
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
134
|
+
).run(
|
|
135
|
+
incidentId,
|
|
136
|
+
policy.id,
|
|
137
|
+
incident.channel,
|
|
138
|
+
incident.actionTaken,
|
|
139
|
+
contentHash,
|
|
140
|
+
JSON.stringify(incident.matchedPatterns),
|
|
141
|
+
incident.severity,
|
|
142
|
+
incident.userId,
|
|
143
|
+
"{}",
|
|
144
|
+
now,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
highestAction === DLP_ACTIONS.BLOCK ||
|
|
150
|
+
highestAction === DLP_ACTIONS.QUARANTINE
|
|
151
|
+
) {
|
|
152
|
+
_blockCount++;
|
|
153
|
+
} else if (highestAction === DLP_ACTIONS.ALERT) {
|
|
154
|
+
_alertCount++;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
allowed:
|
|
160
|
+
highestAction === DLP_ACTIONS.ALLOW ||
|
|
161
|
+
highestAction === DLP_ACTIONS.ALERT,
|
|
162
|
+
action: highestAction,
|
|
163
|
+
matchedPolicies: matchedPolicies.length,
|
|
164
|
+
incidents,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* ── Incident Management ──────────────────────────────────── */
|
|
169
|
+
|
|
170
|
+
export function listIncidents(filter = {}) {
|
|
171
|
+
let incidents = [..._incidents.values()];
|
|
172
|
+
if (filter.channel) {
|
|
173
|
+
incidents = incidents.filter((i) => i.channel === filter.channel);
|
|
174
|
+
}
|
|
175
|
+
if (filter.severity) {
|
|
176
|
+
incidents = incidents.filter((i) => i.severity === filter.severity);
|
|
177
|
+
}
|
|
178
|
+
if (filter.resolved === false) {
|
|
179
|
+
incidents = incidents.filter((i) => !i.resolvedAt);
|
|
180
|
+
}
|
|
181
|
+
const limit = filter.limit || 50;
|
|
182
|
+
return incidents.slice(0, limit);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function resolveIncident(db, incidentId, resolution) {
|
|
186
|
+
const incident = _incidents.get(incidentId);
|
|
187
|
+
if (!incident) throw new Error(`Incident not found: ${incidentId}`);
|
|
188
|
+
|
|
189
|
+
const now = new Date().toISOString();
|
|
190
|
+
incident.resolvedAt = now;
|
|
191
|
+
incident.resolution = resolution || "resolved";
|
|
192
|
+
|
|
193
|
+
db.prepare(
|
|
194
|
+
`UPDATE dlp_incidents SET resolved_at = ?, resolution = ? WHERE id = ?`,
|
|
195
|
+
).run(now, incident.resolution, incidentId);
|
|
196
|
+
|
|
197
|
+
return { success: true, incidentId, resolution: incident.resolution };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* ── DLP Stats ────────────────────────────────────────────── */
|
|
201
|
+
|
|
202
|
+
export function getDLPStats() {
|
|
203
|
+
const unresolvedIncidents = [..._incidents.values()].filter(
|
|
204
|
+
(i) => !i.resolvedAt,
|
|
205
|
+
).length;
|
|
206
|
+
return {
|
|
207
|
+
scanned: _scanCount,
|
|
208
|
+
blocked: _blockCount,
|
|
209
|
+
alerted: _alertCount,
|
|
210
|
+
totalIncidents: _incidents.size,
|
|
211
|
+
unresolvedIncidents,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ── Policy Management ────────────────────────────────────── */
|
|
216
|
+
|
|
217
|
+
export function createPolicy(db, name, patterns, keywords, action, severity) {
|
|
218
|
+
if (!name) throw new Error("Policy name is required");
|
|
219
|
+
|
|
220
|
+
const id = crypto.randomUUID();
|
|
221
|
+
const now = new Date().toISOString();
|
|
222
|
+
|
|
223
|
+
const policy = {
|
|
224
|
+
id,
|
|
225
|
+
name,
|
|
226
|
+
patterns: patterns || [],
|
|
227
|
+
keywords: keywords || [],
|
|
228
|
+
action: action || DLP_ACTIONS.ALERT,
|
|
229
|
+
severity: severity || "medium",
|
|
230
|
+
enabled: true,
|
|
231
|
+
createdAt: now,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
_policies.set(id, policy);
|
|
235
|
+
|
|
236
|
+
db.prepare(
|
|
237
|
+
`INSERT INTO dlp_policies (id, name, patterns, keywords, action, severity, enabled, created_at, updated_at)
|
|
238
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
239
|
+
).run(
|
|
240
|
+
id,
|
|
241
|
+
name,
|
|
242
|
+
JSON.stringify(policy.patterns),
|
|
243
|
+
JSON.stringify(policy.keywords),
|
|
244
|
+
policy.action,
|
|
245
|
+
policy.severity,
|
|
246
|
+
1,
|
|
247
|
+
now,
|
|
248
|
+
now,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
return policy;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function updatePolicy(db, policyId, updates) {
|
|
255
|
+
const policy = _policies.get(policyId);
|
|
256
|
+
if (!policy) throw new Error(`Policy not found: ${policyId}`);
|
|
257
|
+
|
|
258
|
+
if (updates.name !== undefined) policy.name = updates.name;
|
|
259
|
+
if (updates.patterns !== undefined) policy.patterns = updates.patterns;
|
|
260
|
+
if (updates.keywords !== undefined) policy.keywords = updates.keywords;
|
|
261
|
+
if (updates.action !== undefined) policy.action = updates.action;
|
|
262
|
+
if (updates.severity !== undefined) policy.severity = updates.severity;
|
|
263
|
+
if (updates.enabled !== undefined) policy.enabled = updates.enabled;
|
|
264
|
+
|
|
265
|
+
db.prepare(
|
|
266
|
+
`UPDATE dlp_policies SET name = ?, patterns = ?, keywords = ?, action = ?, severity = ?, enabled = ?, updated_at = ? WHERE id = ?`,
|
|
267
|
+
).run(
|
|
268
|
+
policy.name,
|
|
269
|
+
JSON.stringify(policy.patterns),
|
|
270
|
+
JSON.stringify(policy.keywords),
|
|
271
|
+
policy.action,
|
|
272
|
+
policy.severity,
|
|
273
|
+
policy.enabled ? 1 : 0,
|
|
274
|
+
new Date().toISOString(),
|
|
275
|
+
policyId,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
return policy;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function deletePolicy(db, policyId) {
|
|
282
|
+
const policy = _policies.get(policyId);
|
|
283
|
+
if (!policy) throw new Error(`Policy not found: ${policyId}`);
|
|
284
|
+
|
|
285
|
+
_policies.delete(policyId);
|
|
286
|
+
|
|
287
|
+
db.prepare(`DELETE FROM dlp_policies WHERE id = ?`).run(policyId);
|
|
288
|
+
|
|
289
|
+
return { success: true, policyId };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function listDLPPolicies() {
|
|
293
|
+
return [..._policies.values()];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* ── Reset (for testing) ───────────────────────────────────── */
|
|
297
|
+
|
|
298
|
+
export function _resetState() {
|
|
299
|
+
_incidents.clear();
|
|
300
|
+
_policies.clear();
|
|
301
|
+
_scanCount = 0;
|
|
302
|
+
_blockCount = 0;
|
|
303
|
+
_alertCount = 0;
|
|
304
|
+
}
|