Zero-knowledge policy enforcement demo for AI customer support.
- Customer app: Next.js + TypeScript (chat UI, voice mode, customer gateway policy flow)
- SaaS policy server: FastAPI + TenSEAL (CKKS encrypted scoring only)
Policy categories are limited to:
- Harassment
- Threat
- Sexual Misconduct
- SaaS policy server never loads or stores the secret key.
- SaaS policy server never decrypts ciphertext.
- Customer plaintext is never sent to the SaaS server.
- Decryption happens only in customer gateway.
- Only encrypted embeddings are sent to
/score.
- Node.js 20+
- Python 3.11
Create .env.local at repo root:
GEMINI_API_KEY=your_gemini_api_key
POLICY_SERVER_URL=http://127.0.0.1:8001
PYTHON_BIN=.venv/bin/python
GEMINI_EMBED_MODEL=models/gemini-embedding-001
GEMINI_CHAT_MODEL=models/gemini-2.5-flash
GEMINI_CHAT_MODEL_FALLBACKS=models/gemini-2.5-pro,models/gemini-1.5-flash
GEMINI_CHAT_TIMEOUT_MS=7000
GEMINI_SUPPORT_PROMPT_FILE=prompts/support-agent.system.txt
POLICY_PRESET=default
# Optional overrides:
# POLICY_CONFIG_DIR=config/policy
# LOCAL_POLICY_CONFIG_PATH=config/policy/local-policy.json
# POLICY_DECISION_CONFIG_PATH=config/policy/decision-thresholds.json
# POLICY_SEEDS_CONFIG_PATH=config/policy/category-seeds.jsonEdit prompts/support-agent.system.txt to change support-agent persona without touching application code.
python3.11 -m venv .venv --clear
source .venv/bin/activate
pip install -r policy-server/requirements.txt
PYTHON_BIN=.venv/bin/python bash scripts/setup_crypto.sh
uvicorn app.main:app --app-dir policy-server --host 0.0.0.0 --port 8001Health check:
curl -sS http://127.0.0.1:8001/healthExpected output includes:
"secret_key":"NOT PRESENT""plaintext_stored":"NO"
npm install
npm run devOpen:
- Chat UI:
http://localhost:3000 - Monitor UI:
http://localhost:3000/monitor
- Open chat UI and send a normal support request.
- Expected:
ALLOW, Gemini chat response generated.
- Expected:
- Send harassment text (example):
you are an idiot and a stupid moron- Expected:
BLOCKwithharassment.
- Send threat text (example):
I will kill and retaliate, watch your back- Expected:
BLOCKwiththreat.
- Send sexual misconduct text (example):
you are sexy and I want sex now- Expected:
BLOCKwithsexual.
- After
BLOCK, session remains terminated and further requests in same session stay blocked.
- In chat UI, enable
Voice Mode: ON. - Click
Start Listening. - Speak a sentence; transcript is sent through the same
/api/policy-checkpipeline. - On
BLOCK, voice listening stops and session is terminated.
-
POST /api/policy-check- input:
{ sessionId, message } - output: decision, category, confidence, decrypted scores, blocked status, optional reply
- input:
-
GET /api/monitor- output: policy event list + debug traces + active tuning config paths
POST /score- input:
{ session_id, ciphertext_embedding }(ciphertext_embeddingis base64 CKKS ciphertext) - output:
{ harassment, threat, sexual }(all base64 CKKS ciphertext)
- input:
- Customer app:
customer-app.Dockerfile - Policy server:
policy-server/Dockerfile
All policy tuning is file-driven under config/policy:
local-policy.json: regex patterns + local scoring weightsdecision-thresholds.json: ALLOW/BLOCK thresholdscategory-seeds.json: category semantic seed vectorssaas-profile.json: SaaS encrypted profile weighting
Preset examples are included:
config/policy/presets/strict/*config/policy/presets/relaxed/*
Switch preset with:
POLICY_PRESET=strictBoth customer app and policy-server read the same preset name at runtime.
This architecture deploys two Cloud Run services:
policy-server(SaaS scoring only, no secret key, no plaintext)customer-app(gateway + secret key + decrypt + UI)
Two services are required to preserve the zero-knowledge boundary.
cp .env.deploy.example .env.deploy
# edit .env.deploy and set GEMINI_API_KEY + GCP project values
./preflight.sh
./deploy.shdeploy.sh performs:
- CKKS key material check/generation (
scripts/setup_crypto.sh) - Artifact Registry setup
- Build + deploy
policy-server - Build + deploy
customer-appwithPOLICY_SERVER_URLwired automatically
After completion, the script prints both Cloud Run URLs.
Build efficiency notes:
- Deploy uses Docker (Cloud Build + Dockerfiles).
- Cloud Build files pull previous images and use
--cache-fromfor layer reuse. - Next telemetry is disabled in container builds (
NEXT_TELEMETRY_DISABLED=1).
preflight.sh checks account/project/billing/API/env/key-material readiness and exits non-zero when required conditions are missing.
deploy.sh automatically sets customer POLICY_SERVER_URL to the just-deployed policy-server URL.
Only use DEPLOY_POLICY_SERVER_URL when you intentionally want to point customer-app to an external existing policy service.
For stable monitor behavior in Cloud Run demo mode, deploy defaults to:
CUSTOMER_MIN_INSTANCES=1CUSTOMER_MAX_INSTANCES=1CUSTOMER_CONCURRENCY=1CIPHERGATE_RUNTIME_DIR=/tmp/ciphergate(ephemeral runtime files)