clawmatrix 0.4.2 → 0.5.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/src/audit.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  /** Structured audit logger for security events. */
2
2
 
3
+ import type { Store } from "./store.ts";
4
+ import type { LogReplicator } from "./log-replication.ts";
5
+
3
6
  export type AuditEvent =
4
7
  | "conn_open" // New inbound WS connection
5
8
  | "conn_rate_limited" // Connection rejected by rate limiter
@@ -27,6 +30,19 @@ const defaultSink: AuditSink = (entry) => {
27
30
 
28
31
  let sink: AuditSink = defaultSink;
29
32
 
33
+ // ── SQLite persistence (set after Store is initialized) ─────────
34
+
35
+ let storeRef: Store | null = null;
36
+ let replicatorRef: LogReplicator | null = null;
37
+ let localNodeId = "";
38
+
39
+ /** Wire audit to SQLite store for persistence + cross-node replication. */
40
+ export function setAuditStore(store: Store, nodeId: string, replicator?: LogReplicator) {
41
+ storeRef = store;
42
+ localNodeId = nodeId;
43
+ replicatorRef = replicator ?? null;
44
+ }
45
+
30
46
  /** Replace the audit sink (e.g. to write to a file or external service). */
31
47
  export function setAuditSink(s: AuditSink) {
32
48
  sink = s;
@@ -34,9 +50,28 @@ export function setAuditSink(s: AuditSink) {
34
50
 
35
51
  /** Emit an audit event. */
36
52
  export function audit(event: AuditEvent, fields?: Omit<AuditEntry, "ts" | "event">) {
37
- sink({
53
+ const entry: AuditEntry = {
38
54
  ts: new Date().toISOString(),
39
55
  event,
40
56
  ...fields,
41
- });
57
+ };
58
+
59
+ // Always emit to sink (console.warn by default)
60
+ sink(entry);
61
+
62
+ // Persist to SQLite + notify replication
63
+ if (storeRef) {
64
+ try {
65
+ storeRef.insertAudit({
66
+ nodeId: localNodeId,
67
+ ts: Date.now(),
68
+ event,
69
+ ip: fields?.ip,
70
+ detail: fields?.detail,
71
+ });
72
+ replicatorRef?.notifyLocalInsert("audit_log");
73
+ } catch {
74
+ // Non-fatal: don't let store errors break audit logging
75
+ }
76
+ }
42
77
  }
package/src/auth.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createHash, timingSafeEqual as nodeTimingSafeEqual } from "node:crypto";
2
+ import { LRUCache } from "lru-cache";
2
3
 
3
4
  export function generateNonce(): string {
4
5
  const bytes = crypto.getRandomValues(new Uint8Array(32));
@@ -6,8 +7,7 @@ export function generateNonce(): string {
6
7
  }
7
8
 
8
9
  /** Cache imported CryptoKey keyed by a hash of the secret (avoid storing plaintext). Max 8 entries. */
9
- const KEY_CACHE_MAX = 8;
10
- const keyCache = new Map<string, CryptoKey>();
10
+ const keyCache = new LRUCache<string, CryptoKey>({ max: 8 });
11
11
 
12
12
  function cacheKeyFor(secret: string): string {
13
13
  return createHash("sha256").update(secret).digest("hex");
@@ -15,20 +15,15 @@ function cacheKeyFor(secret: string): string {
15
15
 
16
16
  async function getHmacKey(secret: string): Promise<CryptoKey> {
17
17
  const cacheKey = cacheKeyFor(secret);
18
- let key = keyCache.get(cacheKey);
19
- if (key) return key;
20
- key = await crypto.subtle.importKey(
18
+ const cached = keyCache.get(cacheKey);
19
+ if (cached) return cached;
20
+ const key = await crypto.subtle.importKey(
21
21
  "raw",
22
22
  new TextEncoder().encode(secret),
23
23
  { name: "HMAC", hash: "SHA-256" },
24
24
  false,
25
25
  ["sign"],
26
26
  );
27
- if (keyCache.size >= KEY_CACHE_MAX) {
28
- // Evict oldest entry
29
- const oldest = keyCache.keys().next().value!;
30
- keyCache.delete(oldest);
31
- }
32
27
  keyCache.set(cacheKey, key);
33
28
  return key;
34
29
  }