On-Premise AI • Security Architecture • Swiss SaaS
Swiss Core Systems shield logo

SWISS CORE
SYSTEMS

An on-premise AI assistant for Swiss professional practices, built so that a compromised language model still cannot make the server do anything.

Single-tenant AI workspace for Ticino law firms, private banks, fiduciaries, wealth managers and notaries — where client data sits under professional secrecy (Art. 321 StGB), FINMA supervision and the Swiss FADP, and cloud AI is not an option.

Single-tenant per deployment Metadata-only persistence Bilingual IT / EN Beta — live demo funnel

Role

Sole architect & engineer

Product, backend, frontend, security, infrastructure, compliance docs

Timeline

~7 weeks

29 Jun → 15 Aug 2026, active development

Status

Beta, deployed

Live prospect-facing demo at demo.swisscoresystems.com

Market

Ticino practices

Law, private banking, fiduciary, wealth management, notarial

Verified scale

Both quality gates were re-run while writing this case study: pytest → 878 passed · vite build → 1755 modules, 234 KB gzip. No test touches the network.

878

passing backend tests

75

API endpoints

52

reviewed data connectors

42

declarative AI tasks across 8 verticals

29.5k

lines of backend code

22.7k

lines of frontend code

12.6k

lines of test code

12

database tables · 11 migrations

The problem

Swiss professional practices sit on exactly the kind of data that makes cloud AI a non-starter: client files under professional secrecy, banking data under FINMA supervision, personal data under the revised Swiss FADP and GDPR. A law firm in Lugano cannot paste a client mandate into ChatGPT — not because the output would be bad, but because doing so is a disclosure.

The product sells security and discretion first, features second. That inverts the usual priority order and it shows up everywhere in the codebase: a security or data-honesty defect outranks a missing feature. Three constraints shaped every decision:

01

Single-tenant per deployment

Each firm gets its own instance. No shared database, no cross-customer query paths to get wrong.

02

Metadata-only persistence

Uploaded document text lives in a process-local dictionary and never touches disk. Chat content is persisted only when an operator explicitly enables TTL-expiring content logging.

03

The model is never trusted

Not with a destination, not with a permission, not with a fact. The untrusted component is structurally denied authority — by code, not by policy.

The engineering centre

The model never chooses where the server sends a request

The dashboards feature exists because a wealth manager wants a market desk. The design exists because that feature hands a language model a request-generating capability inside a private network. Four boundaries refuse that — each enforced by code:

01

Authoring — a selection language, not an expression language

The model's only machine-readable output is a dashboard specification validated fail-closed. No field can hold a URL, hostname, port, path, header, credential, file path or code — and there is no arithmetic, no template string, no formula. A perfect prompt injection yields a wrong-but-inert dashboard, never a request.

02

Connector — destinations are constants in reviewed code

A spec names a connector by registry id and passes typed parameter values. Host, path, method, headers and credentials are constants in reviewed Python. When the model wants a source that doesn't exist, it can only file a request into an admin queue — only a person adds a data source.

03

Runtime — opening a dashboard runs no LLM at all

Load spec → validate → fetch → normalise → render. Fetched payloads never flow back into a prompt, so a hostile upstream response cannot become prompt injection. The docstring explicitly warns the next engineer not to add a "summarise this tile" feature without re-reading it.

04

Visibility — a SQL predicate, never a post-load filter

Ownership and sharing are enforced in the query on every path — list, search, get, patch, delete, and the context fed into a model turn. A user who completely jailbreaks the assistant still cannot read a colleague's work, because the assistant is not what decides.

"The model is never trusted with authority. Not with a destination, not with a permission, not with a fact."

Defence in depth, honestly labelled

Each control's actual strength is written down in the codebase — including the ones that are only tripwires, and the one residual risk that stays open by documented decision.

A tripwire that says so

Regex prompt-injection detection whose own docstring reads: "this is for alerting, not prevention." It audits the realistic office-walk-in case; the structural boundary is what protects the server.

Every upstream response is hostile

An allowlist says nothing about what an approved host sends back. Payload structure is bounded (depth, keys, lengths, 256 KB cap), declared types are enforced, and prose fields are escaped while numeric fields reject markup outright.

Public links are frozen

Sharing outside the firm freezes fetched figures behind an unguessable token. Opening one is a SELECT — no fetch, no connector, no model — so an anonymous visitor can never cause outbound calls from inside a firm's network.

A load-bearing SSRF guard

Where the URL genuinely is user-supplied (prospect enrichment), every resolved address must be globally routable, redirects are walked by hand with each hop re-checked, and 46 dedicated tests assert a refused target produced no request at all — not merely no result.

Data honesty as a hard rule

A dashboard tile is ok, stale (greyed, labelled with its age) or error. Never a zero, never a blank, never interpolated, never mock — enforced through snapshots and PDF exports too, because a document outlives the screen it came from.

An uncertainty contract

Hallucination here is a liability event, so one contract — a procedure, not an exhortation — is written once and imported into every prompt: ask when unsure, never invent figures or citations, mark what is source, knowledge or assumption. "I don't know" is a correct answer.

Inside the product

The workspace as a firm sees it — captured from the live demo, which runs in Italian for its Ticino audience. Note the honesty label on every market figure: indicative, possibly delayed, stamped with the time it was fetched.

Swiss Core Systems market desk dashboard: SMI index tile with day and 52-week ranges and a one-month chart, UBS and Nestlé quote tiles, and a market news feed — every tile labelled indicative with its fetch time

A market desk dashboard — SMI with day and 52-week ranges, UBS and Nestlé quote tiles, curated market news. The header reads "Version 3": every edit went through propose → review → save, and one click exports the desk to PDF or Word.

AI chat in the private-banking vertical rendering a portfolio composition donut and a portfolio performance chart as structured UI blocks, with task chips above the conversation

Chat in the private-banking vertical, rendering portfolio blocks — composition donut and performance chart drawn by the hand-built SVG renderer, with history fetched and metrics computed server-side, never by the model. Task chips sit above; "messages are not saved" is stated below.

Document library showing a change-of-conditions letter template with declared placeholders, an autofill form and an A4 paper-sheet preview with unfilled placeholders highlighted

The document library — a change-of-conditions template with declared {{placeholders}}, server-side autofill ("empty fields are flagged, never invented") and the A4 paper-sheet preview that breaks where the printed copy breaks.

What a firm actually gets

A complete workspace — 8 professional verticals, 42 declarative AI tasks, live market dashboards, a document library and a built-in go-to-market funnel.

Conversations as trees

Editing a sent message forks a sibling branch instead of overwriting history — both responses survive and the user switches between them. Every read path filters on the owner in SQL.

One UI block protocol, three renderers

Assistant replies carry strict JSON blocks — tables, charts, stats, forms, tickers — rendered by hand-built SVG in the browser, by docx/pptx on the server, and by PDF. Charts in documents are followed by their figures as real text.

Dashboards from natural language

"The SMI and USD/CHF every morning" becomes a validated spec, with a repair loop and propose → review → save on every edit. Masonry grid, drag-and-drop, append-only version history, weekday-morning snapshots.

52 reviewed data connectors

Swiss National Bank, ECB, FINMA news, Yahoo Finance, Swiss and Italian financial press, GDELT — plus computed indicators and portfolio-weighted figures, calculated server-side, never by the model.

Two document stores, never mixed

Chat attachments live in RAM only and die on restart; the library persists what a user explicitly filed. AI drafting proposes, the user saves — nothing is persisted that nobody decided to keep. Deterministic nLPD/GDPR quality checks always run.

Exports that match the screen

One pipeline renders chat replies and dashboards to docx, pptx and PDF — with an A4 paper-sheet preview paginated by real measurement, so the preview breaks where the printed copy breaks.

A full prospect funnel, built in

Self-service demo requests, SSRF-guarded website enrichment, ideal-customer scoring, human approval, sector-seeded public demos behind guest identities, and outreach email with a dry-run default and the Swiss UWG opt-out appended automatically.

Audit, retention, offboarding

Append-only JSONL audit trail, opt-in TTL content logging, retention policy as data, dependency-ordered purges and first-class user offboarding — plus a cron watchdog that emails when the service degrades.

Frontend craft

A flat ink-on-paper design system with measured AA contrast, ~650 bilingual UI strings, hand-built SVG charts with no charting library, and an 11-step guided tour that drives the browser to each feature's own page.

Deployment architecture

Drawn in the product's own design language: zero border radius, 2 px borders, no shadows. The single-worker assumption is explicit — the codebase states in four places that if it ever changes, every process-local cache moves to Redis together.

nginx :443

TLS · HSTS · CSP · rate limiting · serves the built frontend · reverse-proxies /api — single origin, deliberately no CORS anywhere

FastAPI (uvicorn, --workers 1)

Alembic migrations at startup · JWT in httpOnly SameSite cookies backed by revocable session rows · RBAC as one reviewed map (28 actions × 4 roles) · provider-abstracted LLM layer (OpenAI · mock · on-prem)

PostgreSQL 16

12 tables · metadata only by default · append-only dashboard version history

cron
  • · morning snapshots — weekdays 08:45, before the desk arrives
  • · nightly retention purges
  • · health watchdog every 5 min, alerts by email
  • · backup / restore scripts, tested and documented

External cron, every job runnable by hand — debuggable at 09:00 when a desk reports a missing snapshot.

Technical stack

Backend

  • Python 3.12 + FastAPI — async API, vertical-slice architecture
  • SQLAlchemy 2.0 + Alembic — async ORM, migrations as the single source of truth
  • Pydantic v2 — fail-closed validation at every boundary
  • PostgreSQL 16 — production database (SQLite in dev)
  • Provider-abstracted LLM layer — OpenAI, deterministic mock, on-prem stub
  • Resend — transactional email, dry-run by default

Frontend

  • React 18 + Vite 5 — SPA with background jobs that survive navigation
  • Tailwind CSS v4 — semantic tokens, one-lever theming, dark mode
  • React Router 6 — workspace + public demo surfaces
  • Zero component libraries — hand-built SVG charts: lines, bars, candles, donut
  • ~650 bilingual UI strings — Italian and English throughout

Engineering practice

Testing

878 tests on in-memory SQLite with a mocked LLM — no network. Coverage weighted toward the boundaries: 46 tests on the SSRF guard alone.

Configuration

47 typed settings with safe defaults: email dry-run on, content logging off, and a refusal to boot in production with the shipped JWT secret.

Documentation as a deliverable

Deployment guide, runbook, incident response, DPIA against Swiss FADP/GDPR, business continuity plan with RPO/RTO — each also produced in Italian.

Honest limitations

Named on purpose — a portfolio that only lists wins is not a portfolio, and the codebase names them itself.

  • No formal certification. Designed against Swiss FADP/nDSG and GDPR and ships a DPIA — but not ISO 27001 or SOC 2 certified, and it does not claim to be.
  • Beta, and labelled as such in the UI.
  • No frontend test suite — the production build is the only automated frontend check.
  • Single-worker by design, with the Redis migration path documented rather than built.
  • The scraper's TOCTOU window stays open by documented decision — closing it breaks SNI on the shared hosting most Swiss practices use; redirect revalidation makes a DNS rebind expensive instead.
  • The market-data source is an unofficial endpoint — fine for a demo, and the swap to a keyed provider touches exactly one module, which was the point of isolating it.

What this project demonstrates

Security architecture under a real threat model

Not "we sanitise inputs" — a designed system where the untrusted component is structurally denied authority, where each control's actual strength is written down, and where an accepted residual risk is documented next to the code that carries it.

Judgment about where a boundary belongs

The same outbound-HTTP problem is solved two different ways in the same codebase, because in one case the destination is a constant and in the other it is a stranger's input. Recognising that those need different controls is the skill.

Product thinking, not just implementation

Sector-gated features because an irrelevant dashboard is a worse first impression than none. A tour that navigates because explaining a page from a different page does not work. A funnel because a demo nobody can reach sells nothing.

Full-stack ownership

Data model through migrations, API, streaming, hand-built visualisation, design system, bilingual copy, Docker, nginx, cron, backups, incident response, DPIA and business continuity — for a product with a real deployment and a real funnel behind it.

Building something where security actually matters?

Get a custom AI platform, an on-premise deployment, or a full SaaS product engineered with the same discipline — from threat model to backup scripts.