superlocalmemory 2.8.3 → 2.8.6

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/ATTRIBUTION.md CHANGED
@@ -38,7 +38,7 @@ is_valid = QualixarSigner.verify(signed_output)
38
38
 
39
39
  ### Research Initiative
40
40
 
41
- Qualixar is a research platform for AI agent development tools. SuperLocalMemory is one of several research initiatives under the Qualixar umbrella. For more information, visit qualixar.com.
41
+ Qualixar is a research initiative for AI agent development tools by Varun Pratap Bhardwaj. SuperLocalMemory is one of several research initiatives under the Qualixar umbrella.
42
42
 
43
43
  ### Third-Party Acknowledgments
44
44
 
package/CHANGELOG.md CHANGED
@@ -16,6 +16,34 @@ SuperLocalMemory V2 - Intelligent local memory system for AI coding assistants.
16
16
 
17
17
  ---
18
18
 
19
+ ## [2.8.6] - 2026-03-06
20
+
21
+ ### Fixed
22
+ - Environment variable support across all CLI tools
23
+ - Multi-tool memory database sharing
24
+
25
+ ### Contributors
26
+ - Paweł Przytuła (@pawelel) - Issue #7 and PR #8
27
+
28
+ ---
29
+
30
+ ## [2.8.3] - 2026-03-05
31
+
32
+ ### Fixed
33
+ - Windows installation and cross-platform compatibility
34
+ - Database stability under concurrent usage
35
+ - Forward compatibility with latest Python versions
36
+
37
+ ### Added
38
+ - Full Windows support with PowerShell scripts for all operations
39
+ - `slm attribution` command for license and creator information
40
+
41
+ ### Improved
42
+ - Overall reliability and code quality
43
+ - Dependency management for reproducible installs
44
+
45
+ ---
46
+
19
47
  ## [2.8.2] - 2026-03-04
20
48
 
21
49
  ### Fixed
package/bin/slm CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  set -e
12
12
 
13
- SLM_DIR="${HOME}/.claude-memory"
13
+ SLM_DIR="${SL_MEMORY_PATH:-${HOME}/.claude-memory}"
14
14
 
15
15
  # Check if SuperLocalMemory is installed
16
16
  if [ ! -d "$SLM_DIR" ]; then
@@ -310,6 +310,38 @@ except Exception as e:
310
310
  "
311
311
  ;;
312
312
 
313
+ attribution)
314
+ python3 -c "
315
+ import sys
316
+ sys.path.insert(0, '${SLM_DIR}')
317
+ try:
318
+ from memory_store_v2 import MemoryStoreV2
319
+ store = MemoryStoreV2()
320
+ attr = store.get_attribution()
321
+ print('SuperLocalMemory — Attribution')
322
+ print('=' * 45)
323
+ print(f\"Creator: {attr.get('creator_name', 'Varun Pratap Bhardwaj')}\")
324
+ print(f\"Role: {attr.get('creator_role', 'Solution Architect')}\")
325
+ print(f\"Platform: {attr.get('platform', 'Qualixar')}\")
326
+ print(f\"License: {attr.get('license', 'MIT')}\")
327
+ print(f\"Website: {attr.get('website', 'https://superlocalmemory.com')}\")
328
+ print(f\"Author: {attr.get('author_website', 'https://varunpratap.com')}\")
329
+ print('=' * 45)
330
+ try:
331
+ from qualixar_attribution import QualixarSigner
332
+ from qualixar_watermark import encode_watermark
333
+ print('Layer 1 (Visible): Active')
334
+ print('Layer 2 (Cryptographic): Active')
335
+ print('Layer 3 (Steganographic): Active')
336
+ except ImportError:
337
+ print('Layer 1 (Visible): Active')
338
+ print('Layer 2 (Cryptographic): Not available')
339
+ print('Layer 3 (Steganographic): Not available')
340
+ except Exception as e:
341
+ print(f'Error: {e}')
342
+ "
343
+ ;;
344
+
313
345
  help|--help|-h)
314
346
  cat <<EOF
315
347
  SuperLocalMemory V2 - Universal CLI
@@ -356,6 +388,9 @@ HTTP SERVER (MCP):
356
388
  slm serve [PORT] Start MCP HTTP server (default port 8417)
357
389
  For ChatGPT/remote: ngrok http PORT
358
390
 
391
+ ATTRIBUTION:
392
+ slm attribution Show creator attribution and provenance status
393
+
359
394
  ADVANCED:
360
395
  slm reset soft Soft reset (clear memories)
361
396
  slm reset hard --confirm Hard reset (nuclear option)
package/bin/slm-npm CHANGED
@@ -16,8 +16,8 @@ const path = require('path');
16
16
  const os = require('os');
17
17
  const fs = require('fs');
18
18
 
19
- // Installation directory
20
- const SLM_DIR = path.join(os.homedir(), '.claude-memory');
19
+ // Installation directory (SL_MEMORY_PATH overrides default)
20
+ const SLM_DIR = process.env.SL_MEMORY_PATH || path.join(os.homedir(), '.claude-memory');
21
21
 
22
22
  // Check if SuperLocalMemory is installed
23
23
  if (!fs.existsSync(SLM_DIR)) {
package/bin/slm.bat CHANGED
@@ -9,7 +9,9 @@ REM ATTRIBUTION REQUIRED: This notice must be preserved in all copies.
9
9
  setlocal enabledelayedexpansion
10
10
 
11
11
  REM Determine installation location
12
- if exist "%USERPROFILE%\.claude-memory\memory_store_v2.py" (
12
+ if defined SL_MEMORY_PATH (
13
+ set "INSTALL_DIR=%SL_MEMORY_PATH%"
14
+ ) else if exist "%USERPROFILE%\.claude-memory\memory_store_v2.py" (
13
15
  set INSTALL_DIR=%USERPROFILE%\.claude-memory
14
16
  ) else if exist "%~dp0..\src\memory_store_v2.py" (
15
17
  set INSTALL_DIR=%~dp0..\src
@@ -181,7 +183,7 @@ echo slm build-graph
181
183
  echo slm switch-profile work
182
184
  echo.
183
185
  echo Installation: %INSTALL_DIR%
184
- echo Database: %USERPROFILE%\.claude-memory\memory.db
186
+ echo Database: %INSTALL_DIR%\memory.db
185
187
  echo.
186
188
  echo Documentation: https://github.com/varun369/SuperLocalMemoryV2/wiki
187
189
  echo Report issues: https://github.com/varun369/SuperLocalMemoryV2/issues
package/install.ps1 CHANGED
@@ -15,7 +15,7 @@ param(
15
15
 
16
16
  $ErrorActionPreference = "Stop"
17
17
 
18
- $INSTALL_DIR = Join-Path $env:USERPROFILE ".claude-memory"
18
+ $INSTALL_DIR = if ($env:SL_MEMORY_PATH) { $env:SL_MEMORY_PATH } else { Join-Path $env:USERPROFILE ".claude-memory" }
19
19
  $REPO_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path
20
20
 
21
21
  # Auto-detect non-interactive environment
@@ -122,6 +122,35 @@ if (Test-Path $uiServerPath) {
122
122
  Write-Host "OK UI server copied" -ForegroundColor Green
123
123
  }
124
124
 
125
+ # Copy security middleware (required by ui_server.py)
126
+ $securityMiddlewarePath = Join-Path $REPO_DIR "security_middleware.py"
127
+ if (Test-Path $securityMiddlewarePath) {
128
+ Copy-Item -Path $securityMiddlewarePath -Destination $INSTALL_DIR -Force
129
+ Write-Host "OK Security middleware copied" -ForegroundColor Green
130
+ }
131
+
132
+ # Copy routes (required by ui_server.py)
133
+ $routesDir = Join-Path $REPO_DIR "routes"
134
+ $installRoutesDir = Join-Path $INSTALL_DIR "routes"
135
+ if (Test-Path $routesDir) {
136
+ if (-not (Test-Path $installRoutesDir)) {
137
+ New-Item -ItemType Directory -Path $installRoutesDir -Force | Out-Null
138
+ }
139
+ Copy-Item -Path (Join-Path $routesDir "*") -Destination $installRoutesDir -Recurse -Force
140
+ Write-Host "OK Routes copied" -ForegroundColor Green
141
+ }
142
+
143
+ # Copy UI assets (required by ui_server.py)
144
+ $uiDir = Join-Path $REPO_DIR "ui"
145
+ $installUiDir = Join-Path $INSTALL_DIR "ui"
146
+ if (Test-Path $uiDir) {
147
+ if (-not (Test-Path $installUiDir)) {
148
+ New-Item -ItemType Directory -Path $installUiDir -Force | Out-Null
149
+ }
150
+ Copy-Item -Path (Join-Path $uiDir "*") -Destination $installUiDir -Recurse -Force
151
+ Write-Host "OK UI assets copied" -ForegroundColor Green
152
+ }
153
+
125
154
  # Copy MCP server
126
155
  $mcpServerPath = Join-Path $REPO_DIR "mcp_server.py"
127
156
  if (Test-Path $mcpServerPath) {
@@ -169,8 +198,9 @@ if (Test-Path $setupValidatorPath) {
169
198
  # Fallback: create basic database
170
199
  & python -c @"
171
200
  import sqlite3
201
+ import os
172
202
  from pathlib import Path
173
- db_path = Path.home() / '.claude-memory' / 'memory.db'
203
+ db_path = Path(os.environ.get('SL_MEMORY_PATH') or str(Path.home() / '.claude-memory')) / 'memory.db'
174
204
  conn = sqlite3.connect(db_path)
175
205
  cursor = conn.cursor()
176
206
  cursor.execute('''CREATE TABLE IF NOT EXISTS memories (
@@ -228,8 +258,9 @@ Write-Host "Initializing advanced features..."
228
258
  # Add sample memories if database is empty (for first-time users)
229
259
  $memoryCount = & python -c @"
230
260
  import sqlite3
261
+ import os
231
262
  from pathlib import Path
232
- db_path = Path.home() / '.claude-memory' / 'memory.db'
263
+ db_path = Path(os.environ.get('SL_MEMORY_PATH') or str(Path.home() / '.claude-memory')) / 'memory.db'
233
264
  conn = sqlite3.connect(db_path)
234
265
  cursor = conn.cursor()
235
266
  cursor.execute('SELECT COUNT(*) FROM memories')
@@ -261,8 +292,9 @@ try {
261
292
  & python "$INSTALL_DIR\pattern_learner.py" update 2>$null | Out-Null
262
293
  $patternCount = & python -c @"
263
294
  import sqlite3
295
+ import os
264
296
  from pathlib import Path
265
- db_path = Path.home() / '.claude-memory' / 'memory.db'
297
+ db_path = Path(os.environ.get('SL_MEMORY_PATH') or str(Path.home() / '.claude-memory')) / 'memory.db'
266
298
  conn = sqlite3.connect(db_path)
267
299
  cursor = conn.cursor()
268
300
  cursor.execute('SELECT COUNT(*) FROM identity_patterns')
package/install.sh CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  set -e
10
10
 
11
- INSTALL_DIR="${HOME}/.claude-memory"
11
+ INSTALL_DIR="${SL_MEMORY_PATH:-${HOME}/.claude-memory}"
12
12
  REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
13
13
 
14
14
  # Parse command line arguments
@@ -214,6 +214,11 @@ if [ -f "${REPO_DIR}/ui_server.py" ]; then
214
214
  echo "✓ UI server copied"
215
215
  fi
216
216
 
217
+ if [ -f "${REPO_DIR}/security_middleware.py" ]; then
218
+ cp "${REPO_DIR}/security_middleware.py" "${INSTALL_DIR}/"
219
+ echo "✓ Security middleware copied"
220
+ fi
221
+
217
222
  if [ -d "${REPO_DIR}/ui" ]; then
218
223
  mkdir -p "${INSTALL_DIR}/ui/js"
219
224
  cp "${REPO_DIR}/ui/index.html" "${INSTALL_DIR}/ui/" 2>/dev/null || true
@@ -258,8 +263,7 @@ echo "✓ Directories created"
258
263
  if [ ! -f "${INSTALL_DIR}/audit.db" ]; then
259
264
  python3 -c "
260
265
  import sqlite3
261
- from pathlib import Path
262
- audit_path = Path.home() / '.claude-memory' / 'audit.db'
266
+ audit_path = '${INSTALL_DIR}/audit.db'
263
267
  conn = sqlite3.connect(audit_path)
264
268
  cursor = conn.cursor()
265
269
  cursor.execute('''CREATE TABLE IF NOT EXISTS audit_events (
@@ -298,8 +302,7 @@ else
298
302
  # Fallback: create basic tables
299
303
  python3 -c "
300
304
  import sqlite3
301
- from pathlib import Path
302
- db_path = Path.home() / '.claude-memory' / 'memory.db'
305
+ db_path = '${INSTALL_DIR}/memory.db'
303
306
  conn = sqlite3.connect(db_path)
304
307
  cursor = conn.cursor()
305
308
  cursor.execute('''CREATE TABLE IF NOT EXISTS memories (
@@ -399,8 +402,7 @@ echo "Initializing advanced features..."
399
402
  # Add sample memories if database is empty (for first-time users)
400
403
  MEMORY_COUNT=$(python3 -c "
401
404
  import sqlite3
402
- from pathlib import Path
403
- db_path = Path.home() / '.claude-memory' / 'memory.db'
405
+ db_path = '${INSTALL_DIR}/memory.db'
404
406
  conn = sqlite3.connect(db_path)
405
407
  cursor = conn.cursor()
406
408
  cursor.execute('SELECT COUNT(*) FROM memories')
@@ -428,8 +430,7 @@ echo "○ Learning patterns..."
428
430
  if python3 "${INSTALL_DIR}/pattern_learner.py" update > /dev/null 2>&1; then
429
431
  PATTERN_COUNT=$(python3 -c "
430
432
  import sqlite3
431
- from pathlib import Path
432
- db_path = Path.home() / '.claude-memory' / 'memory.db'
433
+ db_path = '${INSTALL_DIR}/memory.db'
433
434
  conn = sqlite3.connect(db_path)
434
435
  cursor = conn.cursor()
435
436
  cursor.execute('SELECT COUNT(*) FROM identity_patterns')
@@ -486,8 +487,8 @@ else
486
487
  fi
487
488
 
488
489
  # Check if PATH already configured
489
- PATH_EXPORT="export PATH=\"\${HOME}/.claude-memory/bin:\${PATH}\""
490
- if grep -q ".claude-memory/bin" "${SHELL_CONFIG}" 2>/dev/null; then
490
+ PATH_EXPORT="export PATH=\"${INSTALL_DIR}/bin:\${PATH}\""
491
+ if grep -Fq -- "${INSTALL_DIR}/bin" "${SHELL_CONFIG}" 2>/dev/null; then
491
492
  echo "○ PATH already configured in ${SHELL_CONFIG}"
492
493
  else
493
494
  # Add PATH export to shell config
@@ -498,7 +499,7 @@ else
498
499
  fi
499
500
 
500
501
  # Add to current session PATH
501
- export PATH="${HOME}/.claude-memory/bin:${PATH}"
502
+ export PATH="${INSTALL_DIR}/bin:${PATH}"
502
503
  echo "✓ Commands available in current session"
503
504
 
504
505
  # ============================================================================
@@ -932,7 +933,7 @@ case "$INSTALL_CHOICE" in
932
933
  echo " pip3 install -r ${REPO_DIR}/requirements-search.txt"
933
934
  echo ""
934
935
  echo "Start Web Dashboard:"
935
- echo " python3 ~/.claude-memory/ui_server.py"
936
+ echo " python3 ${INSTALL_DIR}/ui_server.py"
936
937
  echo " Then open: http://localhost:8000"
937
938
  ;;
938
939
  esac
package/mcp_server.py CHANGED
@@ -33,7 +33,15 @@ from pathlib import Path
33
33
  from typing import Optional, Dict, List, Any
34
34
 
35
35
  # Add src directory to path (use existing code!)
36
- MEMORY_DIR = Path.home() / ".claude-memory"
36
+ import logging
37
+ _raw_memory_dir = os.environ.get("SL_MEMORY_PATH")
38
+ MEMORY_DIR = Path(_raw_memory_dir).expanduser() if _raw_memory_dir else (Path.home() / ".claude-memory")
39
+ MEMORY_DIR = MEMORY_DIR.resolve()
40
+
41
+ _required_modules = ("memory_store_v2.py", "graph_engine.py", "pattern_learner.py")
42
+ if not MEMORY_DIR.is_dir() or not all((MEMORY_DIR / m).exists() for m in _required_modules):
43
+ raise RuntimeError(f"Invalid SuperLocalMemory install directory: {MEMORY_DIR}")
44
+
37
45
  sys.path.insert(0, str(MEMORY_DIR))
38
46
 
39
47
  # Import existing core modules (zero duplicate logic)
@@ -61,6 +69,26 @@ try:
61
69
  except ImportError:
62
70
  TRUST_AVAILABLE = False
63
71
 
72
+ # Qualixar Attribution (v2.8.3 — 3-layer provenance)
73
+ try:
74
+ from qualixar_attribution import QualixarSigner
75
+ from qualixar_watermark import encode_watermark
76
+ _signer = QualixarSigner("superlocalmemory", "2.8.3")
77
+ ATTRIBUTION_AVAILABLE = True
78
+ except ImportError:
79
+ _signer = None
80
+ ATTRIBUTION_AVAILABLE = False
81
+
82
+
83
+ def _sign_response(response: dict) -> dict:
84
+ """Apply Layer 2 cryptographic signing to MCP tool responses."""
85
+ if _signer and isinstance(response, dict):
86
+ try:
87
+ return _signer.sign(response)
88
+ except Exception:
89
+ pass
90
+ return response
91
+
64
92
  # Learning System (v2.7+)
65
93
  try:
66
94
  sys.path.insert(0, str(Path(__file__).parent / "src"))
@@ -577,14 +605,14 @@ def _eager_init():
577
605
  pass
578
606
  try:
579
607
  from behavioral.outcome_tracker import OutcomeTracker
580
- OutcomeTracker(str(Path.home() / ".claude-memory" / "learning.db"))
608
+ OutcomeTracker(str(MEMORY_DIR / "learning.db"))
581
609
  except Exception:
582
- pass
610
+ logging.getLogger("superlocalmemory.mcp").exception("OutcomeTracker eager initialization failed")
583
611
  try:
584
612
  from compliance.audit_db import AuditDB
585
- AuditDB(str(Path.home() / ".claude-memory" / "audit.db"))
613
+ AuditDB(str(MEMORY_DIR / "audit.db"))
586
614
  except Exception:
587
- pass
615
+ logging.getLogger("superlocalmemory.mcp").exception("AuditDB eager initialization failed")
588
616
 
589
617
  # Run once at module load
590
618
  _eager_init()
@@ -676,12 +704,12 @@ async def remember(
676
704
  # Format response
677
705
  preview = content[:100] + "..." if len(content) > 100 else content
678
706
 
679
- return {
707
+ return _sign_response({
680
708
  "success": True,
681
709
  "memory_id": memory_id,
682
710
  "message": f"Memory saved with ID {memory_id}",
683
711
  "content_preview": preview
684
- }
712
+ })
685
713
 
686
714
  except Exception as e:
687
715
  return {
@@ -805,13 +833,13 @@ async def recall(
805
833
  if r.get('score', 0) >= min_score
806
834
  ]
807
835
 
808
- return {
836
+ return _sign_response({
809
837
  "success": True,
810
838
  "query": query,
811
839
  "results": filtered_results,
812
840
  "count": len(filtered_results),
813
841
  "total_searched": len(results)
814
- }
842
+ })
815
843
 
816
844
  except Exception as e:
817
845
  return {
@@ -888,10 +916,10 @@ async def get_status() -> dict:
888
916
  # Call existing get_stats method
889
917
  stats = store.get_stats()
890
918
 
891
- return {
919
+ return _sign_response({
892
920
  "success": True,
893
921
  **stats
894
- }
922
+ })
895
923
 
896
924
  except Exception as e:
897
925
  return {
@@ -1637,6 +1665,54 @@ async def project_context_prompt(project_name: str) -> str:
1637
1665
  # SERVER STARTUP
1638
1666
  # ============================================================================
1639
1667
 
1668
+ @mcp.tool(annotations=ToolAnnotations(
1669
+ readOnlyHint=True,
1670
+ destructiveHint=False,
1671
+ openWorldHint=False,
1672
+ ))
1673
+ async def get_attribution() -> dict:
1674
+ """
1675
+ Get creator attribution and provenance verification for SuperLocalMemory.
1676
+
1677
+ Returns creator information, license details, and verification status
1678
+ for the 3-layer Qualixar attribution system.
1679
+
1680
+ Returns:
1681
+ {
1682
+ "creator": str,
1683
+ "license": str,
1684
+ "platform": str,
1685
+ "layers": {
1686
+ "visible": bool,
1687
+ "cryptographic": bool,
1688
+ "steganographic": bool
1689
+ }
1690
+ }
1691
+ """
1692
+ try:
1693
+ store = get_store()
1694
+ attribution = store.get_attribution()
1695
+
1696
+ return _sign_response({
1697
+ "success": True,
1698
+ **attribution,
1699
+ "website": "https://superlocalmemory.com",
1700
+ "author_website": "https://varunpratap.com",
1701
+ "attribution_layers": {
1702
+ "layer1_visible": True,
1703
+ "layer2_cryptographic": ATTRIBUTION_AVAILABLE,
1704
+ "layer3_steganographic": ATTRIBUTION_AVAILABLE,
1705
+ },
1706
+ })
1707
+
1708
+ except Exception as e:
1709
+ return {
1710
+ "success": False,
1711
+ "error": _sanitize_error(e),
1712
+ "message": "Failed to get attribution"
1713
+ }
1714
+
1715
+
1640
1716
  if __name__ == "__main__":
1641
1717
  import argparse
1642
1718
 
@@ -1662,7 +1738,7 @@ if __name__ == "__main__":
1662
1738
  # Print startup message to stderr (stdout is used for MCP protocol)
1663
1739
  print("=" * 60, file=sys.stderr)
1664
1740
  print("SuperLocalMemory V2 - MCP Server", file=sys.stderr)
1665
- print("Version: 2.7.4", file=sys.stderr)
1741
+ print("Version: 2.8.3", file=sys.stderr)
1666
1742
  print("=" * 60, file=sys.stderr)
1667
1743
  print("Created by: Varun Pratap Bhardwaj (Solution Architect)", file=sys.stderr)
1668
1744
  print("Repository: https://github.com/varun369/SuperLocalMemoryV2", file=sys.stderr)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlocalmemory",
3
- "version": "2.8.3",
3
+ "version": "2.8.6",
4
4
  "description": "Your AI Finally Remembers You - Local-first intelligent memory system for AI assistants. Works with Claude, Cursor, Windsurf, VS Code/Copilot, Codex, and 17+ AI tools. 100% local, zero cloud dependencies.",
5
5
  "keywords": [
6
6
  "ai-memory",
@@ -1105,7 +1105,8 @@ class MemoryStoreV2:
1105
1105
 
1106
1106
  # Qualixar platform provenance (non-breaking additions)
1107
1107
  attribution['platform'] = 'Qualixar'
1108
- attribution['verify_url'] = 'https://qualixar.com'
1108
+ attribution['website'] = 'https://superlocalmemory.com'
1109
+ attribution['author_website'] = 'https://varunpratap.com'
1109
1110
 
1110
1111
  return attribution
1111
1112
 
@@ -1136,7 +1137,16 @@ class MemoryStoreV2:
1136
1137
  output.append(entry)
1137
1138
  char_count += len(entry)
1138
1139
 
1139
- return ''.join(output)
1140
+ text = ''.join(output)
1141
+
1142
+ # Layer 3: Steganographic watermark on text exports
1143
+ try:
1144
+ from qualixar_watermark import encode_watermark
1145
+ text = encode_watermark(text, "slm")
1146
+ except ImportError:
1147
+ pass
1148
+
1149
+ return text
1140
1150
 
1141
1151
 
1142
1152
  # CLI interface (V1 compatible + V2 extensions)
package/ui_server.py CHANGED
@@ -20,6 +20,9 @@ import sys
20
20
  from pathlib import Path
21
21
  from datetime import datetime
22
22
 
23
+ _script_dir = str(Path(__file__).parent.resolve())
24
+ sys.path = [p for p in sys.path if p not in ("", _script_dir)]
25
+
23
26
  try:
24
27
  from fastapi import FastAPI
25
28
  from fastapi.staticfiles import StaticFiles
@@ -33,6 +36,8 @@ except ImportError:
33
36
  "Install with: pip install 'fastapi[all]' uvicorn websockets"
34
37
  )
35
38
 
39
+ sys.path.insert(0, _script_dir)
40
+
36
41
  from security_middleware import SecurityHeadersMiddleware
37
42
 
38
43
  # Add src/ and routes/ to path
@@ -50,7 +55,7 @@ app = FastAPI(
50
55
  description="Real-Time Memory Dashboard with Event Bus, Agent Registry, and Trust Scoring",
51
56
  version="2.5.0",
52
57
  docs_url="/api/docs",
53
- redoc_url="/api/redoc"
58
+ redoc_url="/api/redoc",
54
59
  )
55
60
 
56
61
  # Middleware (order matters: security headers should be outermost)
@@ -59,9 +64,9 @@ app.add_middleware(GZipMiddleware, minimum_size=1000)
59
64
  app.add_middleware(
60
65
  CORSMiddleware,
61
66
  allow_origins=[
62
- "http://localhost:8765", # Dashboard
67
+ "http://localhost:8765", # Dashboard
63
68
  "http://127.0.0.1:8765",
64
- "http://localhost:8417", # MCP
69
+ "http://localhost:8417", # MCP
65
70
  "http://127.0.0.1:8417",
66
71
  ],
67
72
  allow_credentials=True,
@@ -84,10 +89,11 @@ try:
84
89
  allowed, remaining = limiter.is_allowed(client_ip)
85
90
  if not allowed:
86
91
  from fastapi.responses import JSONResponse
92
+
87
93
  return JSONResponse(
88
94
  status_code=429,
89
95
  content={"error": "Too many requests. Please slow down."},
90
- headers={"Retry-After": str(limiter.window)}
96
+ headers={"Retry-After": str(limiter.window)},
91
97
  )
92
98
 
93
99
  response = await call_next(request)
@@ -107,9 +113,12 @@ try:
107
113
  headers = dict(request.headers)
108
114
  if not check_api_key(headers, is_write=is_write):
109
115
  from fastapi.responses import JSONResponse
116
+
110
117
  return JSONResponse(
111
118
  status_code=401,
112
- content={"error": "Invalid or missing API key. Set X-SLM-API-Key header."}
119
+ content={
120
+ "error": "Invalid or missing API key. Set X-SLM-API-Key header."
121
+ },
113
122
  )
114
123
  response = await call_next(request)
115
124
  return response
@@ -136,6 +145,7 @@ from routes.ws import router as ws_router, manager as ws_manager
136
145
  # v2.7 Learning routes (graceful)
137
146
  try:
138
147
  from routes.learning import router as learning_router
148
+
139
149
  LEARNING_ROUTES = True
140
150
  except ImportError:
141
151
  LEARNING_ROUTES = False
@@ -154,18 +164,21 @@ if LEARNING_ROUTES:
154
164
  # v2.8 routes (graceful — don't fail if engines unavailable)
155
165
  try:
156
166
  from routes.lifecycle import router as lifecycle_router
167
+
157
168
  app.include_router(lifecycle_router)
158
169
  except ImportError:
159
170
  pass
160
171
 
161
172
  try:
162
173
  from routes.behavioral import router as behavioral_router
174
+
163
175
  app.include_router(behavioral_router)
164
176
  except ImportError:
165
177
  pass
166
178
 
167
179
  try:
168
180
  from routes.compliance import router as compliance_router
181
+
169
182
  app.include_router(compliance_router)
170
183
  except ImportError:
171
184
  pass
@@ -173,6 +186,7 @@ except ImportError:
173
186
  # Wire WebSocket manager into routes that need broadcast capability
174
187
  import routes.profiles as _profiles_mod
175
188
  import routes.data_io as _data_io_mod
189
+
176
190
  _profiles_mod.ws_manager = ws_manager
177
191
  _data_io_mod.ws_manager = ws_manager
178
192
 
@@ -180,6 +194,7 @@ _data_io_mod.ws_manager = ws_manager
180
194
  # Basic Routes (root page + health check)
181
195
  # ============================================================================
182
196
 
197
+
183
198
  @app.get("/", response_class=HTMLResponse)
184
199
  async def root():
185
200
  """Serve main UI page."""
@@ -206,7 +221,7 @@ async def health_check():
206
221
  "status": "healthy",
207
222
  "version": "2.5.0",
208
223
  "database": "connected" if DB_PATH.exists() else "missing",
209
- "timestamp": datetime.now().isoformat()
224
+ "timestamp": datetime.now().isoformat(),
210
225
  }
211
226
 
212
227
 
@@ -214,6 +229,7 @@ async def health_check():
214
229
  # Startup Events
215
230
  # ============================================================================
216
231
 
232
+
217
233
  @app.on_event("startup")
218
234
  async def startup_event():
219
235
  """Register Event Bus listener for SSE bridge on startup."""
@@ -229,8 +245,12 @@ if __name__ == "__main__":
229
245
  import socket
230
246
 
231
247
  parser = argparse.ArgumentParser(description="SuperLocalMemory V2 - Web Dashboard")
232
- parser.add_argument("--port", type=int, default=8765, help="Port to run on (default 8765)")
233
- parser.add_argument("--profile", type=str, default=None, help="Memory profile to use")
248
+ parser.add_argument(
249
+ "--port", type=int, default=8765, help="Port to run on (default 8765)"
250
+ )
251
+ parser.add_argument(
252
+ "--profile", type=str, default=None, help="Memory profile to use"
253
+ )
234
254
  args = parser.parse_args()
235
255
 
236
256
  def find_available_port(preferred):
@@ -1,214 +0,0 @@
1
- # Security Quick Reference — For Developers
2
-
3
- **Last Updated:** March 4, 2026
4
-
5
- ## TL;DR — Safe Coding Patterns
6
-
7
- ### ✅ DO THIS (Safe)
8
-
9
- ```javascript
10
- // Safe: Use textContent for plain text
11
- element.textContent = userContent;
12
-
13
- // Safe: Use escapeHtml() before DOM insertion
14
- element.innerHTML = escapeHtml(userContent);
15
-
16
- // Safe: Create elements programmatically
17
- const div = document.createElement('div');
18
- div.textContent = userContent;
19
- parent.appendChild(div);
20
- ```
21
-
22
- ### ❌ DON'T DO THIS (Dangerous)
23
-
24
- ```javascript
25
- // DANGEROUS: Direct assignment with user content
26
- element.innerHTML = userContent; // XSS VULNERABLE
27
-
28
- // DANGEROUS: Using code evaluation with user input
29
- // Never use eval, Function constructor, or similar
30
-
31
- // DANGEROUS: Inline event handlers with user content
32
- element.setAttribute('onclick', userCode); // XSS VULNERABLE
33
- ```
34
-
35
- ## Security Checklist — Before Committing
36
-
37
- - [ ] No direct innerHTML assignments without escapeHtml()
38
- - [ ] All user content either uses textContent or is escaped
39
- - [ ] No code evaluation with user input
40
- - [ ] No inline event handlers with dynamic content
41
- - [ ] All API endpoints return explicit Content-Type
42
- - [ ] SQL queries use parameterized statements (never string concatenation)
43
- - [ ] No secrets in code or config files
44
- - [ ] Run security tests: pytest tests/test_security_headers.py -v
45
-
46
- ## Common Patterns
47
-
48
- ### Rendering User-Generated Content
49
-
50
- **Pattern 1: Plain Text Only**
51
- ```javascript
52
- const contentDiv = document.getElementById('memory-content');
53
- contentDiv.textContent = memory.content; // Automatically safe
54
- ```
55
-
56
- **Pattern 2: Mixed Content (HTML Structure + User Text)**
57
- ```javascript
58
- const html = `
59
- <div class="memory-card">
60
- <h3>${escapeHtml(memory.title)}</h3>
61
- <p>${escapeHtml(memory.content)}</p>
62
- <span class="badge">${escapeHtml(memory.category)}</span>
63
- </div>
64
- `;
65
- container.innerHTML = html;
66
- ```
67
-
68
- **Pattern 3: Building DOM Programmatically (Most Secure)**
69
- ```javascript
70
- const card = document.createElement('div');
71
- card.className = 'memory-card';
72
-
73
- const title = document.createElement('h3');
74
- title.textContent = memory.title;
75
-
76
- const content = document.createElement('p');
77
- content.textContent = memory.content;
78
-
79
- card.appendChild(title);
80
- card.appendChild(content);
81
- container.appendChild(card);
82
- ```
83
-
84
- ### FastAPI Routes
85
-
86
- **Safe Pattern:**
87
- ```python
88
- @router.get("/api/memories")
89
- async def get_memories():
90
- # FastAPI automatically sets Content-Type: application/json
91
- return {
92
- "memories": memories, # User content here is safe in JSON
93
- "total": len(memories)
94
- }
95
- ```
96
-
97
- **SQL Queries:**
98
- ```python
99
- # SAFE: Parameterized query
100
- cursor.execute("SELECT * FROM memories WHERE id = ?", (memory_id,))
101
-
102
- # DANGEROUS: String concatenation (SQL injection)
103
- # Never use f-strings or concatenation for SQL queries
104
- ```
105
-
106
- ## Testing XSS Protection
107
-
108
- ```python
109
- # Test that XSS payloads are handled safely
110
- def test_xss_payload():
111
- payload = "<script>alert('xss')</script>"
112
-
113
- # This should be safe when:
114
- # 1. Returned as JSON with Content-Type: application/json
115
- # 2. Rendered with escapeHtml() on client
116
- # 3. Protected by CSP headers
117
-
118
- response = client.get(f"/api/memory?content={payload}")
119
- assert response.headers["Content-Type"] == "application/json"
120
- assert response.headers["X-Content-Type-Options"] == "nosniff"
121
- ```
122
-
123
- ## Security Headers Reference
124
-
125
- All responses include these headers automatically via SecurityHeadersMiddleware:
126
-
127
- - X-Content-Type-Options: nosniff
128
- - X-Frame-Options: DENY
129
- - X-XSS-Protection: 1; mode=block
130
- - Content-Security-Policy: (comprehensive policy)
131
- - Referrer-Policy: strict-origin-when-cross-origin
132
- - Cache-Control: no-store (API endpoints only)
133
-
134
- **Don't override these headers** unless you have a specific security reason and understand the implications.
135
-
136
- ## CORS Configuration
137
-
138
- Current allowed origins:
139
- - http://localhost:8765
140
- - http://127.0.0.1:8765
141
- - http://localhost:8417
142
- - http://127.0.0.1:8417
143
-
144
- **To add a new origin:**
145
- 1. Add to the allow_origins list in ui_server.py
146
- 2. Document why it's needed
147
- 3. Never use wildcard * in production
148
-
149
- ## When to Update Security
150
-
151
- ### Add New Route
152
- - [ ] Verify return type has explicit Content-Type
153
- - [ ] Test with XSS payloads
154
- - [ ] Add test case to test_security_headers.py
155
-
156
- ### Add New JavaScript
157
- - [ ] Use textContent for plain text
158
- - [ ] Use escapeHtml() for mixed content
159
- - [ ] Test rendering with XSS payloads
160
-
161
- ### Modify Security Headers
162
- - [ ] Document reason in commit message
163
- - [ ] Update SECURITY.md
164
- - [ ] Run full security test suite
165
- - [ ] Consider security implications
166
-
167
- ## Resources
168
-
169
- - **Full Security Policy:** See SECURITY.md
170
- - **Security Enhancement Details:** .backup/security-enhancement-2026-03-04.md
171
- - **Security Tests:** tests/test_security_headers.py
172
- - **Middleware Source:** security_middleware.py
173
- - **Client-Side Escaping:** ui/app.js (escapeHtml function)
174
-
175
- ## Quick Test Commands
176
-
177
- ```bash
178
- # Run security tests only
179
- pytest tests/test_security_headers.py -v
180
-
181
- # Run all tests
182
- pytest tests/ -x -q
183
-
184
- # Manual verification (browser)
185
- python3 ui_server.py
186
- # Open http://localhost:8765
187
- # DevTools → Network → Check response headers
188
- ```
189
-
190
- ## Red Flags — Stop and Review
191
-
192
- If you see any of these patterns, STOP and review:
193
-
194
- - innerHTML with user content (without escaping)
195
- - Code evaluation functions anywhere
196
- - String concatenation in SQL queries
197
- - CORS with wildcard *
198
- - User input in event handler attributes
199
- - Disabling CSP or security headers
200
- - Secrets or API keys in code
201
-
202
- ## When in Doubt
203
-
204
- **Ask these questions:**
205
- 1. Could a user inject a script tag here?
206
- 2. Is this content escaped before rendering?
207
- 3. Does this SQL query use parameters?
208
- 4. Could this header configuration weaken security?
209
-
210
- **If unsure:** Use the safest pattern (textContent or programmatic DOM creation).
211
-
212
- ---
213
-
214
- **Remember:** Defense in depth. We have multiple layers (headers, CSP, escaping). Use all of them.