📚 Docs / Security — Architecture, Policies & Best Practices

Security — Architecture, Policies & Best Practices

Security architecture and implementation details for Lawyer Assistant. This document covers the app's security model, Content Security Policy (CSP), Electron sandboxing, data handling, and security testing.

1. Security principles

Local-first by design

Documents never leave your machine by default.

Zero telemetry

Open source


2. Content Security Policy (CSP)

The app enforces a strict CSP in production to prevent XSS, code injection, and unauthorized resource loading.

CSP rules (production build)

html<meta
  http-equiv="Content-Security-Policy"
  content="default-src 'self';
           script-src 'self';
           style-src 'self' 'unsafe-inline';
           img-src 'self' data: blob:;
           font-src 'self' data:;
           connect-src 'self' http://localhost:* ws://localhost:*;
           object-src 'none';
           base-uri 'self';
           form-action 'self';"
/>

What it prevents

DirectiveWhat it blocks
script-src 'self'❌ Inline <script> tags<br>❌ eval(), new Function()<br>❌ External scripts from CDNs
style-src 'self' 'unsafe-inline'⚠️ Allows inline styles (needed for Tailwind)<br>✅ Blocks external stylesheets
connect-src 'self' http://localhost:*❌ Requests to external domains<br>✅ Allows backend (8765) + Vite HMR (5173)
object-src 'none'<object>, <embed>, <applet>

Development mode relaxations

During development (npm run dev), CSP allows:

Production builds never allow unsafe-inline for scripts or unsafe-eval.

CSP injection (automated)

CSP is injected into dist/index.html during build by a Vite plugin:

File: frontend/vite.config.ts

typescriptfunction contentSecurityPolicy(): Plugin {
  return {
    name: 'inject-csp',
    transformIndexHtml(html) {
      const isDev = process.env.NODE_ENV === 'development';
      const csp = isDev
        ? "default-src 'self'; script-src 'self' 'unsafe-inline'; ..."
        : "default-src 'self'; script-src 'self'; ...";
      
      return html.replace(
        '<head>',
        `<head>\n    <meta http-equiv="Content-Security-Policy" content="${csp}">`
      );
    },
  };
}

CSP regression guard

A CI check (frontend/scripts/check-csp.mjs) fails the build if:

  1. dist/index.html is missing the CSP <meta> tag
  2. Production CSP contains unsafe-eval

Run locally:

bashcd frontend
npm run build
npm run check:csp

CI workflow (.github/workflows/ci.yml):

yaml- name: Check CSP
  run: |
    cd frontend
    npm run build
    npm run check:csp

A CSP violation breaks the build — the security posture can't regress silently.


3. Electron security model

Renderer sandbox

The renderer process runs in a strict sandbox with no Node.js access:

File: frontend/electron/src/main/window.ts

typescriptconst win = new BrowserWindow({
  webPreferences: {
    sandbox: true,               // ✅ Renderer isolated from OS
    contextIsolation: true,       // ✅ Renderer can't touch Electron APIs
    nodeIntegration: false,       // ✅ No require() in renderer
    preload: path.join(__dirname, 'preload.js'),
  },
});

What sandboxing prevents

AttackHow it's prevented
Arbitrary file accessRenderer has no fs module access
OS command executionRenderer has no child_process access
Electron API abusecontextIsolation=true isolates APIs
Node.js module loadingnodeIntegration=false disables require()

Preload script (controlled bridge)

The preload script (preload.ts) exposes only safe, controlled IPC:

typescriptcontextBridge.exposeInMainWorld('electron', {
  // Safe IPC — main process validates all requests
  send: (channel: string, data: any) => ipcRenderer.send(channel, data),
  invoke: (channel: string, data: any) => ipcRenderer.invoke(channel, data),
  on: (channel: string, func: Function) => {
    ipcRenderer.on(channel, (event, ...args) => func(...args));
  },
});

Renderer cannot call arbitrary Node.js APIs — only the pre-defined IPC channels.

No --no-sandbox flag

The main process never uses the --no-sandbox Chromium flag (which disables the sandbox entirely). Using it anywhere in the codebase breaks the security check.

Electron security regression guard

A CI check (frontend/electron/scripts/check-electron-security.mjs) fails the build if:

  1. Any BrowserWindow doesn't set sandbox: true, contextIsolation: true, and nodeIntegration: false explicitly
  2. The --no-sandbox flag appears anywhere in the main process sources

Run locally:

bashcd frontend/electron
npm run build
npm run check:security

CI workflow:

yaml- name: Check Electron Security
  run: |
    cd frontend/electron
    npm run build
    npm run check:security

An insecure webPreferences configuration breaks the build.


4. API key handling

Storage

API keys (backend/api_config.json) are:

Transmission

User control

The launcher's API provider card:


5. File upload security

Upload constraints

ConstraintLimitEnforcement
Max files per message5Frontend pre-check + backend validation
Max folder files50Frontend file picker filter
Scan upload size cap50 MBBackend /api/scan/upload rejects larger
Supported extensions only.pdf, .docx, .txt, .png, .jpg, ...Backend SUPPORTED_EXTENSIONS filter

Path validation

Uploaded files are saved to the active workspace root — never to arbitrary paths supplied by the user:

python# Backend: main.py
@app.post("/api/upload")
async def upload_file(file: UploadFile):
    root = get_workspace_root()
    if root is None:
        raise HTTPException(400, "No workspace selected")
    
    # Safe: sanitize filename, save under workspace root only
    safe_name = secure_filename(file.filename)
    dest = root / safe_name
    
    # Write file
    with dest.open("wb") as f:
        f.write(await file.read())
    
    return {"path": str(dest), "filename": safe_name}

No path traversal: ../../etc/passwd in a filename is sanitized to etcpasswd.

Temp file exclusion

Temp/lock files are never ingested (ignored during workspace scanning):

Patterns excluded:

This prevents indexing transient metadata files that could leak into search results.


6. Network security

Backend binds to localhost only

The FastAPI server binds to 127.0.0.1:8765not 0.0.0.0:

python# Backend: main.py
if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8765)

The backend is never exposed to the network — only the local Electron app can reach it.

CORS policy

CORS allows only:

External origins cannot call the API.

pythonapp.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],
    allow_origin_regex=r"^file://.*",
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

No external network calls (local mode)

In local mode:

The app is fully functional offline.


7. Data retention

What's stored where

DataLocationPersistence
Raw uploads<workspace>/Permanent (user's files)
Vector index<workspace>/workspace/chroma_db/Permanent (per project)
Chat history<workspace>/workspace/history.jsonPermanent (per project)
LangGraph threadsdata/conversations.dbPermanent (project-independent)
Compliance flagsdata/flags.dbPermanent (project-independent)
Logslogs/legal_retrieval.logRotating (10 MB, 5 backups)

What's NOT stored

Clearing data

Per-project data (workspace):

bashrm -rf /path/to/project/workspace/

Global data (conversations, flags):

bashrm -rf data/conversations.db data/flags.db

Logs:

bashrm -rf logs/

Models (14 GB):

bashrm -rf models/

8. Dependency security

Dependency auditing

The project uses well-known, actively maintained dependencies:

Backend (Python):

Frontend (TypeScript):

Audit commands:

bash# Python
cd backend
pip-audit

# Node.js
npm audit
npm audit fix

Run audits in CI to catch vulnerable dependencies.

Dependency pinning

Python dependencies (backend/requirements.txt) use version pins:

fastapi==0.109.0
chromadb==0.4.22
sentence-transformers==2.2.2

Not ranges (>=0.109.0) — exact versions for reproducibility.

Supply chain attacks

Defense:


9. Secure coding practices

Input validation

All API inputs are validated via Pydantic models:

pythonclass ChatRequest(BaseModel):
    query: str = Field(..., min_length=1, max_length=10000)
    mode: Literal["rag", "retrieval_only", "direct"] = "rag"
    top_k: int = Field(default=5, ge=1, le=100)
    # ... validates types, ranges, required fields

Invalid input is rejected before processing.

SQL injection prevention

The app uses:

No raw SQL string concatenation anywhere in the codebase.

Path traversal prevention

File operations use safe path resolution:

python# Safe
workspace_root = Path("/absolute/path/to/project")
user_file = "contract.pdf"
safe_path = (workspace_root / user_file).resolve()

# Check: must be under workspace root
if not safe_path.is_relative_to(workspace_root):
    raise ValueError("Path traversal attempt")

User-supplied paths are never used directly.

Command injection prevention

The app does not execute user-supplied shell commands. The only shell commands are:

XSS prevention

User input is never inserted into HTML via dangerouslySetInnerHTML.


10. Electron-specific security

Remote module disabled

The deprecated remote module is not used — all main↔renderer communication is via IPC.

webSecurity enabled

webSecurity: true (default) is never disabled — prevents:

No allowRunningInsecureContent

Production builds never set allowRunningInsecureContent: true (which would allow HTTP content in HTTPS pages).

Native modules sandboxed

Electron's native modules (require('electron')) are not accessible in the renderer due to nodeIntegration: false + contextIsolation: true.


11. Security testing

Automated checks in CI

Two security checks run on every push/PR:

1. CSP validation (.github/workflows/ci.yml):

yaml- name: Build frontend
  run: cd frontend && npm run build
- name: Check CSP
  run: cd frontend && npm run check:csp

2. Electron security validation:

yaml- name: Build Electron
  run: cd frontend/electron && npm run build
- name: Check Electron Security
  run: cd frontend/electron && npm run check:security

Both checks fail the build on violations.

Manual security testing

Test CSP enforcement:

bashcd frontend
npm run build
npm run check:csp

Test Electron sandbox:

bashcd frontend/electron
npm run build
npm run check:security

Test dependency vulnerabilities:

bashcd backend
pip-audit

cd ..
npm audit

12. Threat model

In-scope threats

XSS via user input — mitigated by CSP + React escaping ✅ Code injection — mitigated by sandbox + CSP ✅ Path traversal — mitigated by safe path resolution ✅ API key leakage — mitigated by git-ignore + log redaction ✅ Dependency vulnerabilities — mitigated by audits + pinning ✅ Electron sandbox escape — mitigated by strict webPreferences

Out-of-scope threats

⚠️ Physical access to machine — user's OS-level security responsibility ⚠️ Malicious Ollama models — user must trust model sources ⚠️ Compromised PyPI/npm packages — supply chain risk (audits help) ⚠️ OS kernel exploits — outside app's control


13. Incident response

Reporting a vulnerability

Email: [your-security-email@example.com]

What to include:

Response SLA:

Disclosure policy


14. Security checklist for contributors

Before submitting a PR:


15. Security updates

Where to watch:

How to update:

bash# Pull latest
git pull origin main

# Update dependencies
cd backend && pip install -r requirements.txt
cd frontend && npm install
cd frontend/electron && npm install

16. Privacy guarantees

Local mode

100% private — no network calls, no data leaves your machine ✅ Offline capable — works without internet ✅ No telemetry — zero usage tracking

API mode

⚠️ Selective data sent — only the small text snippet needed for the answer ✅ Documents stay local — full files never uploaded ✅ User controls provider — choose OpenAI, Anthropic, etc. ✅ No telemetry — app still doesn't track usage

What's NEVER sent


17. Compliance notes

GDPR

The app does not collect, process, or transmit personal data — all processing is local (user's machine). API mode sends only text snippets to the chosen provider (user-controlled).

HIPAA / PHI

The app is not HIPAA-compliant out-of-the-box. Healthcare organizations must:

The app is designed for legal professionals handling confidential client documents. Local-first architecture ensures client privilege is maintained.


DocCoverage
ARCHITECTURE.mdSystem overview, ports
BACKEND.mdAPI endpoints, validation
FRONTEND.mdUI security, CSP
API_PROVIDER.mdCloud mode security
DEVELOPMENT.mdSecure development practices
DEPLOYMENT.mdProduction security hardening

Questions, answered

Short, self-contained answers about this guide.

How are API keys protected?

Keys are stored in a git-ignored config file with file-permission protection, never logged (redacted from all output), never echoed back to the UI, and only sent to the chosen provider over HTTPS.

How does the app prevent path traversal?

Uploaded files are resolved against the workspace root and rejected if they escape it — the docs show the exact safe-path pattern. Temp and lock files are excluded from scanning entirely.

Does the backend expose anything to the network?

No — the FastAPI server binds to localhost only, CORS allows only the Vite dev server and Electron, and in local mode there are no outbound HTTP requests at all.