shieldcortex 3.0.1 → 3.0.3

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.
Files changed (42) hide show
  1. package/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
  2. package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
  3. package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
  4. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
  5. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
  6. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +1 -1
  12. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +1 -1
  13. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  14. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  15. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  16. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  17. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  18. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  19. package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
  20. package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +1 -1
  21. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  22. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +1 -1
  23. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
  24. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +1 -1
  25. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  26. package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
  27. package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
  28. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
  29. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
  30. package/dist/database/init.js +16 -4
  31. package/dist/defence/credential-leak/entropy.js +15 -0
  32. package/dist/defence/credential-leak/patterns.js +56 -0
  33. package/dist/defence/firewall/encoding-detector.js +16 -1
  34. package/dist/defence/firewall/instruction-detector.js +18 -0
  35. package/dist/defence/input-sanitisation/index.js +3 -1
  36. package/dist/defence/quarantine/auto-expire.d.ts +9 -0
  37. package/dist/defence/quarantine/auto-expire.js +39 -0
  38. package/dist/memory/store.js +13 -0
  39. package/package.json +1 -1
  40. /package/dashboard/.next/standalone/dashboard/.next/static/{QB0u2SMOnB24QHhhlDrIU → THy6JENQ0c1sq6jQhvIDp}/_buildManifest.js +0 -0
  41. /package/dashboard/.next/standalone/dashboard/.next/static/{QB0u2SMOnB24QHhhlDrIU → THy6JENQ0c1sq6jQhvIDp}/_clientMiddlewareManifest.json +0 -0
  42. /package/dashboard/.next/standalone/dashboard/.next/static/{QB0u2SMOnB24QHhhlDrIU → THy6JENQ0c1sq6jQhvIDp}/_ssgManifest.js +0 -0
@@ -165,6 +165,62 @@ export const API_KEY_PATTERNS = [
165
165
  confidence: 0.30,
166
166
  // UUIDs are very common — only flagged as low confidence
167
167
  },
168
+ // Hugging Face
169
+ {
170
+ name: 'Hugging Face API Token',
171
+ type: 'api_key',
172
+ provider: 'huggingface',
173
+ regex: /hf_[A-Za-z0-9]{34,}/g,
174
+ severity: 'critical',
175
+ confidence: 0.96,
176
+ },
177
+ // Databricks
178
+ {
179
+ name: 'Databricks API Token',
180
+ type: 'api_key',
181
+ provider: 'databricks',
182
+ regex: /dapi[a-f0-9]{32,}/g,
183
+ severity: 'critical',
184
+ confidence: 0.94,
185
+ },
186
+ // DigitalOcean
187
+ {
188
+ name: 'DigitalOcean Personal Access Token',
189
+ type: 'api_key',
190
+ provider: 'digitalocean',
191
+ regex: /dop_v1_[a-f0-9]{64}/g,
192
+ severity: 'critical',
193
+ confidence: 0.97,
194
+ },
195
+ // Firebase Cloud Messaging
196
+ {
197
+ name: 'Firebase Cloud Messaging Key',
198
+ type: 'api_key',
199
+ provider: 'firebase',
200
+ regex: /AAAA[A-Za-z0-9_-]{40,}/g,
201
+ severity: 'critical',
202
+ confidence: 0.90,
203
+ minLength: 44,
204
+ },
205
+ // HashiCorp Vault
206
+ {
207
+ name: 'HashiCorp Vault Token',
208
+ type: 'api_key',
209
+ provider: 'hashicorp',
210
+ regex: /hvs\.[A-Za-z0-9_-]{24,}/g,
211
+ severity: 'critical',
212
+ confidence: 0.96,
213
+ },
214
+ // Azure Subscription Key
215
+ {
216
+ name: 'Azure Subscription Key',
217
+ type: 'api_key',
218
+ provider: 'azure',
219
+ regex: /[a-f0-9]{32}/g,
220
+ severity: 'medium',
221
+ confidence: 0.35,
222
+ minLength: 32,
223
+ },
168
224
  ];
169
225
  // ── Generic Secret Patterns ──
170
226
  export const GENERIC_SECRET_PATTERNS = [
@@ -16,7 +16,7 @@ const ZERO_WIDTH_PATTERN = /[\u200B\u200C\u200D\uFEFF]/g;
16
16
  const RTL_OVERRIDE_PATTERN = /\u202E/g;
17
17
  // Unicode homoglyphs — Cyrillic characters that look like Latin
18
18
  const CYRILLIC_HOMOGLYPHS = /[\u0430\u0435\u043E\u0440\u0441\u0443\u0445\u0410\u0412\u0415\u041A\u041C\u041D\u041E\u0420\u0421\u0422\u0423\u0425]/g;
19
- function tryBase64Decode(str) {
19
+ function tryBase64DecodeSingle(str) {
20
20
  try {
21
21
  const decoded = Buffer.from(str, 'base64').toString('utf-8');
22
22
  // Check if decoded result looks like readable text (mostly printable ASCII)
@@ -30,6 +30,21 @@ function tryBase64Decode(str) {
30
30
  return null;
31
31
  }
32
32
  }
33
+ function tryBase64Decode(str, maxDepth = 3) {
34
+ const decoded = tryBase64DecodeSingle(str);
35
+ if (!decoded)
36
+ return null;
37
+ if (maxDepth > 1) {
38
+ const innerMatch = decoded.match(BASE64_PATTERN);
39
+ if (innerMatch) {
40
+ const innerDecoded = tryBase64Decode(innerMatch[0], maxDepth - 1);
41
+ if (innerDecoded) {
42
+ return `${decoded} → ${innerDecoded}`;
43
+ }
44
+ }
45
+ }
46
+ return decoded;
47
+ }
33
48
  function tryHexDecode(str) {
34
49
  try {
35
50
  const hexChars = str.replace(/0x|\\x|\s/g, '');
@@ -22,6 +22,10 @@ const PATTERN_GROUPS = [
22
22
  /^\[system\]/im,
23
23
  /^<system>/im,
24
24
  /<\/system>/i,
25
+ /##SYSTEM##/i,
26
+ /\{SYSTEM\}/i,
27
+ /<brain>/i,
28
+ /<\/brain>/i,
25
29
  ],
26
30
  },
27
31
  {
@@ -36,6 +40,9 @@ const PATTERN_GROUPS = [
36
40
  /pretend\s+to\s+be/i,
37
41
  /disregard\s+(all\s+)?(previous|above|prior)/i,
38
42
  /override\s+(previous|all|system)/i,
43
+ /summari[sz]e\s+your\s+system\s+prompt/i,
44
+ /repeat\s+your\s+instructions/i,
45
+ /what\s+are\s+your\s+rules/i,
39
46
  ],
40
47
  },
41
48
  {
@@ -96,6 +103,17 @@ const PATTERN_GROUPS = [
96
103
  /\b(disable|remove|bypass|turn\s+off)\s+(all\s+)?(filter|security|protection|safet)/i,
97
104
  ],
98
105
  },
106
+ {
107
+ name: 'prompt_extraction',
108
+ weight: 0.75,
109
+ patterns: [
110
+ /output\s+your\s+prompt/i,
111
+ /show\s+me\s+your\s+instructions/i,
112
+ /what\s+were\s+you\s+told/i,
113
+ /display\s+your\s+(system\s+)?prompt/i,
114
+ /reveal\s+your\s+instructions/i,
115
+ ],
116
+ },
99
117
  ];
100
118
  // Maximum content length to scan (prevents ReDOS on very long inputs)
101
119
  const MAX_SCAN_LENGTH = 50000;
@@ -17,7 +17,7 @@ const ZERO_WIDTH = /[\u200B\u200C\u200D\uFEFF\u2060\u180E]/g;
17
17
  /** BOM (Byte Order Mark) — can confuse parsers */
18
18
  const BOM = /^\uFEFF/;
19
19
  /** Bidirectional override characters — can visually reorder text to disguise content */
20
- const BIDI_OVERRIDES = /[\u202A-\u202E\u2066-\u2069]/g;
20
+ const BIDI_OVERRIDES = /[\u200E\u200F\u202A-\u202E\u2066-\u2069]/g;
21
21
  /** Unusual Unicode separators that can break tokenisation */
22
22
  const HOMOGLYPH_SEPARATORS = /[\u2028\u2029\u00A0\u1680\u2000-\u200A\u205F\u3000]/g;
23
23
  /**
@@ -28,6 +28,8 @@ const HOMOGLYPH_SEPARATORS = /[\u2028\u2029\u00A0\u1680\u2000-\u200A\u205F\u3000
28
28
  export function sanitiseInput(content) {
29
29
  let sanitised = content;
30
30
  const strippedCategories = [];
31
+ // 0. Unicode NFKC normalisation — collapse combining characters to canonical forms
32
+ sanitised = sanitised.normalize('NFKC');
31
33
  // 1. Null bytes
32
34
  if (NULL_BYTES.test(sanitised)) {
33
35
  strippedCategories.push('null_byte');
@@ -12,3 +12,12 @@
12
12
  * @returns Number of items expired (rejected)
13
13
  */
14
14
  export declare function expireQuarantineItems(ttlDays?: number): number;
15
+ /**
16
+ * Prune old reviewed quarantine items and warn on capacity issues.
17
+ * - Deletes approved/rejected/expired items older than 90 days
18
+ * - Warns if pending count exceeds 10,000
19
+ * - Warns if any single source has >500 pending items
20
+ *
21
+ * @returns Number of items pruned
22
+ */
23
+ export declare function pruneQuarantine(retentionDays?: number): number;
@@ -33,5 +33,44 @@ export function expireQuarantineItems(ttlDays = 7) {
33
33
  if (result.changes > 0) {
34
34
  console.error(`[quarantine] Auto-expired ${result.changes} item(s) after ${ttlDays} days`);
35
35
  }
36
+ // Prune old reviewed items
37
+ pruneQuarantine();
38
+ return result.changes;
39
+ }
40
+ /**
41
+ * Prune old reviewed quarantine items and warn on capacity issues.
42
+ * - Deletes approved/rejected/expired items older than 90 days
43
+ * - Warns if pending count exceeds 10,000
44
+ * - Warns if any single source has >500 pending items
45
+ *
46
+ * @returns Number of items pruned
47
+ */
48
+ export function pruneQuarantine(retentionDays = 90) {
49
+ const db = getDatabase();
50
+ // Delete reviewed items older than retention period
51
+ const result = db.prepare(`
52
+ DELETE FROM quarantine
53
+ WHERE status IN ('approved', 'rejected', 'expired')
54
+ AND reviewed_at IS NOT NULL
55
+ AND reviewed_at < datetime('now', '-' || ? || ' days')
56
+ `).run(retentionDays);
57
+ if (result.changes > 0) {
58
+ console.error(`[quarantine] Pruned ${result.changes} reviewed item(s) older than ${retentionDays} days`);
59
+ }
60
+ // Warn on total pending count
61
+ const totalPending = db.prepare("SELECT COUNT(*) as count FROM quarantine WHERE status = 'pending'").get();
62
+ if (totalPending.count > 10000) {
63
+ console.error(`[quarantine] WARNING: ${totalPending.count} pending items — review or increase auto-expiry frequency`);
64
+ }
65
+ // Warn on per-source pending count
66
+ const hotSources = db.prepare(`
67
+ SELECT source_identifier, COUNT(*) as count FROM quarantine
68
+ WHERE status = 'pending'
69
+ GROUP BY source_identifier
70
+ HAVING count > 500
71
+ `).all();
72
+ for (const src of hotSources) {
73
+ console.error(`[quarantine] WARNING: Source "${src.source_identifier}" has ${src.count} pending items`);
74
+ }
36
75
  return result.changes;
37
76
  }
@@ -77,6 +77,18 @@ function escapeFts5Query(query) {
77
77
  .split(/\s+/)
78
78
  .filter(term => term.length > 0)
79
79
  .map(term => {
80
+ // Replace apostrophes with spaces — FTS5 porter unicode61 tokenizer treats
81
+ // apostrophes as word separators during indexing ("don't" → "don" + "t").
82
+ // We must split the same way so queries match the indexed tokens.
83
+ if (term.includes("'")) {
84
+ const parts = term.split("'").filter(p => p.length > 0);
85
+ // Recursively escape each part and join with spaces
86
+ return parts.map(p => {
87
+ if (/[^a-zA-Z0-9_]/.test(p))
88
+ return `"${p.replace(/"/g, '""')}"`;
89
+ return p;
90
+ }).join(' ');
91
+ }
80
92
  // FTS5 boolean operators - quote them to search literally
81
93
  const upperTerm = term.toUpperCase();
82
94
  if (upperTerm === 'AND' || upperTerm === 'OR' || upperTerm === 'NOT') {
@@ -90,6 +102,7 @@ function escapeFts5Query(query) {
90
102
  }
91
103
  return term;
92
104
  })
105
+ .filter(Boolean)
93
106
  .join(' ');
94
107
  }
95
108
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shieldcortex",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "Persistent brain for AI agents. Knowledge graphs, memory decay, contradiction detection, Iron Dome behaviour protection — plus the only defence pipeline that stops memory poisoning. Works with Claude Code, OpenClaw, LangChain, and any MCP agent.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",