100 Commits

Author SHA1 Message Date
Rene Fichtmueller
c5801a8925 fix: prevent subscription header spoofing
All checks were successful
changelog-draft / changelog-draft (push) Successful in 32s
2026-07-19 00:24:36 +02:00
changelog-bot
453d580f69 chore(changelog): draft pending entries [skip ci] 2026-07-18 22:17:28 +00:00
Rene Fichtmueller
b8543ec191 feat: proxy subscription clients through gateway
All checks were successful
changelog-draft / changelog-draft (push) Successful in 31s
2026-07-18 18:39:18 +02:00
Rene Fichtmueller
f720096d3b ci: add deterministic changelog-draft workflow
All checks were successful
changelog-draft / changelog-draft (push) Successful in 29s
Drafts CHANGELOG_PENDING.md entries from Conventional Commit subjects on
every push to main -- feat/fix/refactor/perf/security bucketed into
Added/Fixed/Changed, chore/ci/test/docs/style/merge skipped, anything
mentioning internal infra hostnames dropped. No LLM call, no network
access. Never touches CHANGELOG.md -- still needs a human to fold an
entry in. Rolled out fleet-wide from the PeerCortex pilot (2026-07-18).
2026-07-18 07:20:42 +00:00
Rene Fichtmueller
6275c25e67 fix(gateway): redact outbound bridge prompts 2026-07-18 01:04:54 +02:00
Rene Fichtmueller
d265320819 feat(gateway): expose subscription bridges as gateway APIs 2026-07-17 19:32:05 +02:00
Rene Fichtmueller
812b695e1a fix(gateway): compress subscription bridge requests 2026-07-17 19:22:22 +02:00
Rene Fichtmueller
a5f07a9ef8 docs(security): avoid credential-like api key example 2026-07-17 19:09:25 +02:00
Rene Fichtmueller
a6547eda2f chore(security): scrub subscription bridge placeholders 2026-07-17 19:06:22 +02:00
Rene Fichtmueller
421926768a chore(security): remove credential-like defaults 2026-07-17 18:54:25 +02:00
Rene Fichtmueller
92a2d41fb1 chore(data): redact training secrets for github mirror 2026-07-17 18:37:51 +02:00
Rene Fichtmueller
eb7525e97b Revert "fix(gateway): run aider bridge non-interactively"
This reverts commit 861457719b32644a450cc414f2f07ba522b65a62.
2026-07-17 17:53:32 +02:00
Rene Fichtmueller
861457719b fix(gateway): run aider bridge non-interactively 2026-07-17 17:49:35 +02:00
Rene Fichtmueller
1f0a2a3966 fix(deploy): load gateway env through pm2 wrapper 2026-07-17 17:46:06 +02:00
Rene Fichtmueller
be8eda6430 fix(gateway): return honest subscription bridge API status 2026-07-17 17:38:26 +02:00
Rene Fichtmueller
496975ed94 fix(gateway): keep reasoning trace split compatible 2026-07-17 17:22:40 +02:00
Rene Fichtmueller
60529d7752 feat(gateway): add subscription bridge access 2026-07-17 17:14:47 +02:00
Rene Fichtmueller
bc340da1f9 feat(gateway): backport Magatama injection-defense hardening (RU fix, normalization, patterns)
Backport of the hardened injection-defense from magatama core
(packages/core/src/security/injection-scan.ts) so both layers stay in
lockstep. Pattern ids are identical across layers for cross-layer telemetry.

- fix latent dead ignore-ru pattern: JS \b is ASCII-only and never matches
  around Cyrillic, so the Russian "ignore previous instructions" pattern
  never fired. Removed the leading/trailing \b.
- add normalizeForScan de-obfuscation pre-pass (NFKC; strip zero-width/bidi/
  unicode-tag/combining; map Cyrillic+Greek homoglyphs to ASCII; collapse
  spaced-out letters; de-leet). scanForInjection now scans BOTH raw and
  normalized input and unions matches by id; new viaNormalization flag.
- add excessive-letter-spacing (high) + broaden repeat-words-above (stacked
  determiners), ignore-zh (mid-phrase) and ignore-ko (before-verb determiners).

Block-mode FP guard: the gateway BLOCKS (INJECTION_DEFENSE_MODE=block), so
excessive-letter-spacing diverges from the Magatama regex by a letter-guard
lookahead (>=3 consecutive spaced letters). The raw Magatama form would 422
legit numeric traffic ("what comes next: 1 2 3 4 5 6 7 8 9 10"). Same id +
severity so telemetry stays in lockstep.

Verified: 76/76 red-team + benign dev/ops FP battery against built dist; 10 new
vitest cases; live /v1/guard/scan before/after (RU clear->blocked,
homoglyph/spacing/ko blocked, numeric sequence stays clear); gateway rebuilt +
restarted, health 200, 0 new false positives.
2026-07-07 21:49:08 +00:00
root
2ad363d7e8 sec(injection-defense): +9 patterns closing red-team bypass classes
Internal red-team showed the pattern layer caught only ~33% of injection-
shaped prompts. Adds 9 high-severity, FP-tuned patterns for the bypassed
classes (system-prompt extraction via repeat/read, self-referential secret
exfil, authority-spoof, base64 decode-exec, unfiltered persona, jailbreak-
confirm). Result: 10/10 attacks blocked on the red-team set, 0 false-
positives from the new patterns (verified against benign dev/ops prompts).
2026-07-07 19:36:15 +00:00
root
ddb8779ec7 fix: track bridge tokens in request_tracking for dashboard totals
codex-bridge passthrough previously hardcoded tokens_in=0 and used
text.length (char count) for tokens_out, writing only to llm_calls.
request_tracking never received these entries so all Codex/Claude/
Copilot bridge traffic was invisible in dashboard token totals.

Fix:
- Extract real token counts from upstreamJson.usage (prompt_tokens /
  completion_tokens) with char/4 estimation as fallback
- Call requestLogger.logRequest() to write to request_tracking
- Pass real totals to recordSubscriptionUsage() (was 0)
- Both audit log (llm_calls) and dashboard log (request_tracking) now
  receive consistent bridgeCaller + real token counts
2026-07-06 21:07:11 +00:00
root
73da9d7391 sec(redteam): gateway injection-defense ~33% catch rate (confirms ShieldX landmine) 2026-06-28 22:04:04 +00:00
root
912f796e1f fix(pii): presidio sidecar language misconfig — stops silent name-redaction fail-open 2026-06-23 01:09:06 +00:00
root
88dec91d09 sec(pii): REDACT_PII_MODE=always — anonymize all gateway traffic incl. local Ollama 2026-06-23 00:56:40 +00:00
root
40b53ea625 sec: fix injection-defense bypass — 2 new patterns, 0x08 byte corruption
Added safety-test-ruse and filters-disabled-claim patterns to block
social-engineering probes that claim safety filters are disabled.

Root cause: Python str.replace() wrote literal backspace bytes (0x08)
instead of regex word-boundary markers (\b), making both patterns
silently fail against the probe text.

Fixes persona-bypass pentest probe (Magatama MIND/COMPLY findings).
2026-06-20 17:27:18 +00:00
root
f5a9160133 feat: Presidio+GLiNER anonymizer + auto-discovery + periodic re-scan
- pii-redaction.ts: add redactPersonNamesAsync() — async Presidio/GLiNER NER
  for PERSON/ORGANIZATION/LOCATION (95% German accuracy, <3s timeout, non-fatal)
- completion.ts: wire Presidio async call after regex PII block (PRESIDIO_URL env)
- auto-discovery.ts: add schedulePeriodicDiscovery() — re-scan all bridges every 5 min
- server.ts: call schedulePeriodicDiscovery(300000) on boot
- dashboard.ts: POST /api/discovery/client-report + GET /api/discovery/client-reports
  — MacBook/MacStudio push their AI inventory (Ollama models, tools, ports) every 5 min
- .env: REDACT_PII_MODE=cloud_only, AUTO_SPAWN_BRIDGES=1, PRESIDIO_URL=:8701, DISCOVERY_INTERVAL_MS=300000

Presidio sidecar: /opt/presidio-sidecar/ — FastAPI + spaCy de_core_news_lg + GLiNER
multi-v2.1, PM2 id 28 on :8701. Compression already active; anonymization now live
on all cloud provider calls.
2026-06-19 23:29:40 +00:00
Rene Fichtmueller
45e296a8ae docs: changelog entry for /v1/guard scan API 2026-06-12 19:45:11 +02:00
Rene Fichtmueller
18b23d3c58 feat: expose MAGATAMA guard scan API at /v1/guard (scan + health)
Standalone injection-defense endpoint for Flightdeck and other internal
clients: pattern scan + optional LLM judge, independent of the inline
completion-pipeline defense.
2026-06-12 19:38:05 +02:00
Rene Fichtmueller
9ee75c0bc1 chore: gitignore ecosystem.config.cjs and backup files 2026-06-11 15:10:09 +00:00
Rene Fichtmueller
c7c457ae2a feat: merge Gitea main (injection-defense, bridges, dashboard) + Erik WIP features
Reconcile 6-week divergence: Gitea main (injection-defense, output-defense,
prompt-guard-client, admin-auth, start-with-env, dashboard-v2, savings-calculator,
race-mode, gamification + 13 more modules) merged with Erik's deployed features
(usage-report endpoint, per-device entries, CEST timezone, cost-panel, bridge routing).
ecosystem.config.cjs excluded (live token, never commit).
2026-06-05 21:07:57 +00:00
Rene Fichtmueller
c53e0d2165 docs: rename handovers to human-friendly "Handover 17.05.2026 - <Typ>.md"
HANDOVER-2026-05-17-pointer.md → Handover 17.05.2026 - Gateway.md
  HANDOVER-AGENTS-2026-05-17.md  → Handover 17.05.2026 - Agents Pointer.md

Cross-references in beiden Files aktualisiert auf neue magatama-Filenames.
2026-05-17 16:44:59 +02:00
Rene Fichtmueller
a77995abd3 docs: 2026-05-17 agent handover pointer for Codex + Claude Code
Points at magatama/HANDOVER-AGENTS-2026-05-17.md as the canonical
cross-project agent handover. Keeps the repo-local TL;DR (Layer-3 status,
PM2 env-reload caveat, smoke test, rollback, /proc env verification) so an
agent can act in this repo without leaving it.
2026-05-17 16:18:35 +02:00
Rene Fichtmueller
3f8abc7152 docs: 2026-05-17 handover pointer
Today's only LLM-gateway change was an env edit on Erik:
  INJECTION_DEFENSE_MODE: block → llm_judge
  LLM_JUDGE_MODEL: magatama-coder:judge-r1 → qwen2.5:14b

Backup .bak-<ts>-pre-mode-switch + .bak-<ts>-pre-qwen-judge on Erik.

Master handover lives in the magatama repo. This file is a pointer + smoke
test + rollback recipe for the Layer-3 activation specifically. Includes
the /proc/PID/environ verification step (because pm2 env shows cached
ecosystem.config.js values, not the actual node-process env from the
durable start-with-env.sh wrapper).
2026-05-17 15:20:26 +02:00
Rene Fichtmueller
aa5911bfdf sec(gateway): start-with-env.sh shell wrapper — durable env fix for PM2 quirk
Recurring problem: PM2 ecosystem env vars get dropped on KeepAlive
auto-restart. Has bitten us 3× in one session — defense silently turns
OFF without visible cause.

Fix: PM2 script changed from `./packages/gateway/dist/server.js` to
`./start-with-env.sh` which:
  set -a; source .env.defense; source .env; set +a
  exec node packages/gateway/dist/server.js

Defense env now persists across ANY restart mechanism (manual reload,
KeepAlive crash-restart, pm2 resurrect, system reboot, ...) because
it's loaded at the shell level on every process spawn — independent of
PM2's internal env state.

Verified end-to-end:
  - 4 smoke tests (Layer-1 EN/FR, Layer-2 Roleplay, legit) → all pass
  - kill -9 → KeepAlive respawns → env STILL present → injection STILL
    blocks (HTTP 422)

.env.defense lives at /opt/llm-gateway/.env.defense (chmod 600, not in repo).
.env.defense.example added to repo as template.
2026-05-17 00:51:51 +02:00
Rene Fichtmueller
c731900a90 sec(gateway): Layer-3 llm_judge model now configurable via LLM_JUDGE_MODEL env
Was hardcoded to qwen2.5:3b. Now reads from process.env LLM_JUDGE_MODEL
with qwen2.5:3b fallback.

Production env updated to magatama-coder:judge-r1 — a snapshot of the
magatamallm post-chunk-4 LoRA adapter exported via train.py --export-only.
Chunk-4 picked because it had the best val_loss (0.861) of the 5 balanced
chunks; chunk-5 spiked back to val=2.531.

Sanity test on the new judge model:
  injection prompt -> "INFORMATIONAL"  (not the strict INJECTION word
                                         we'd want — judge needs Phase-2
                                         dedicated fine-tune on binary
                                         classification format)
  safe prompt      -> "SAFE"           (correct)

Implication: INJECTION_DEFENSE_MODE is staying at 'block' for now —
switching to 'llm_judge' mode with this provisional judge would actually
weaken defense because magatamallm's training tilts toward operator-task
output ("here's the fix") rather than binary INJECTION/SAFE classification.

Follow-up (Phase 2): train a dedicated `magatama-judge` model — small base
(Qwen 2.5:1.5b or Phi-3-mini), trained purely on injection-classification
SFT pairs extracted from our existing:
  - llm-security-prompt-injection-2026-05-12.train.jsonl
  - pulso-magatama-injection-guard-2026-05-13.train.jsonl
  - guard-exposure-firewall-verified-2026-05-16.train.jsonl
  - jailbreak-corpus-candidates.jsonl (L1B3RT4S gaps)
  - benign samples from train.jsonl labeled SAFE

Architecture rationale: separation of concerns. Even if attacker manipulates
the primary backbone model, judge stays independent. ~5-10k pairs should
be enough for a focused 1.5B classifier. Training ~2-3h on Mac Studio MPS.
2026-05-16 23:36:26 +02:00
Rene Fichtmueller
f399999e62 sec(gateway): Layer-2 ML classifier — Prompt-Guard sidecar integration
Adds a second defense layer between Layer-1 regex (62 patterns) and the
existing Layer-3 llm_judge. Calls a FastAPI sidecar running on the Mac
Studio (port 9091, MPS) that wraps protectai/deberta-v3-base-prompt-
injection-v2 — public model, no auth needed, ~50-400ms inference.

modules/prompt-guard-client.ts:
  - callPromptGuard(input)        opportunistic, never throws
  - isPromptGuardConfigured()     true if PROMPT_GUARD_URL is set
  - getPromptGuardThreshold()     default 0.85
  - getPromptGuardMinLen()        default 16 chars (skip tiny inputs)

routes/completion.ts:
  - New Layer-2 block between regex scan and llm_judge: when Layer-1
    didn't detect and input is long enough, ask the sidecar. If sidecar
    returns INJECTION with score >= threshold, return HTTP 422 with
    error.prompt_guard payload (score + latency).
  - Fail-open: sidecar timeout/error logs a warning and the request
    falls through to llm_judge / cache / model — never blocks legitimate
    traffic due to sidecar issues.

Env (set in ecosystem.config.js):
  PROMPT_GUARD_URL       http://192.168.178.213:9091
  PROMPT_GUARD_THRESHOLD 0.70  (lowered from 0.85 after empirical testing)
  PROMPT_GUARD_TIMEOUT   1500 ms

Sidecar code lives at:
  ~/magatama-llm/prompt-guard-sidecar/server.py  (Mac Studio)
  launched via ~/Library/LaunchAgents/org.fichtmueller.prompt-guard-sidecar.plist

Smoke tests after deploy:
  Layer-1 caught: German "ignoriere..."          -> HTTP 422
  Layer-2 caught: English "pretend no restrict.."-> HTTP 422 (pg_score 0.9999)
  Layer-2 caught: Bangla-romanized               -> HTTP 422 (Layer-1 actually)
  Benign:        "Explain DNS in 2 sentences"    -> HTTP 200
2026-05-16 23:14:16 +02:00
Rene Fichtmueller
6f5dd81d7a sec(gateway): +15 languages + non-Latin script detector (62 patterns total)
Closes the multilingual bypass gap. Previously covered EN/DE/FR/ES/IT/RU/ZH/JA.
Now also: Bangla, Hindi, Arabic, Hebrew, Persian, Turkish, Vietnamese, Thai,
Korean, Polish, Dutch, Indonesian, Tagalog, Swahili.

Plus a universal non-Latin-script soft-flag pattern (severity=medium) that
catches ≥20 chars of Arabic/Bengali/Devanagari/Hebrew/Thai/Hangul/Han/
Hiragana/Katakana/Cyrillic/Tamil/Telugu/Gujarati/Gurmukhi/Myanmar/Khmer/
Lao/Tibetan/Georgian/Armenian/Sinhala — surfaces in scan result without
auto-blocking, so legitimate non-Latin prompts pass while the operator
can route them to llm_judge for deep inspection.

Pattern-engineering notes:
  - Devanagari / Bengali / Hebrew need optional matra/suffix tolerance
  - Turkish needs \p{L} instead of \w because ı/ş/ç fall outside ASCII \w
  - Persian (SOV) needs both VSO and SOV order alternation
  - Hebrew needs מ/ב/כ/ל preposition prefix tolerance
  - Tagalog needs optional ang/sa article between verb and noun

Smoke-tested 14/14 languages → all HTTP 422 blocked.
Negative-tested 3 benign non-Latin prompts (jp-weather, ar-greeting,
th-thanks) → all HTTP 200 pass. Zero false positives.

Total active patterns: 62 across 6 categories.
2026-05-16 23:02:01 +02:00
Rene Fichtmueller
ac887ab052 sec(gateway): commit injection/output defense modules to git (44 patterns)
These modules have been running in production on Erik but were never
committed to the repo. Recovering + extending after CVE-2026-45321 review.

injection-defense.ts: 44 patterns across 6 categories
  jailbreak (15)    — ignore/disregard/override + DAN/AIM/EvilBOT/Omega
                      personas + fiction-framing + goal-hijack + 5 langs
                      (en, de, fr, es, it, ru, zh, ja) + DAN-style
                      "do anything now"
  role_bypass (3)   — DAN persona, new-system-prompt, pretend-rolemix
  system_prompt_leak (5) — direct reveal, repeat verbatim, starts-with,
                      debug/admin mode invocation, translate-system
  indirect (8)      — fake role tags, embedded user msg, instruction-in-data,
                      ChatML/INST control-token smuggle, RAG-poisoning,
                      Unicode tag chars (E0000-E007F), zero-width chars,
                      fake-section-header override
  exfiltration (6)  — markdown-image, send-data-to, base64-instruction,
                      DNS-exfil, webhook canaries, templated image URLs
  policy (2)        — no-refusal, illegal-content-demand

Activated via INJECTION_DEFENSE_MODE=block in ecosystem.config.js (also
fixed: was overridden by stale pm2 set module_conf.json entry).

Smoke tests:
  - "Ignore all previous instructions"  → 422 blocked
  - "You are now AIM"                   → 422 blocked
  - "Ignorez les instructions"          → 422 blocked
  - "What is 2+2?"                      → 200 passes

output-defense.ts: existing stream-time output filter, kept as-is.
2026-05-16 22:55:08 +02:00
Rene Fichtmueller
b15b8da963 sync: record gateway final hardening 2026-05-12 23:31:02 +02:00
Rene Fichtmueller
c7491406d1 sync: note claude tool adapter 2026-05-12 23:09:58 +02:00
Rene Fichtmueller
ee9c1715ae sync: note claude code gateway fix 2026-05-12 22:56:24 +02:00
Rene Fichtmueller
ebafb99645 sync: note claude alias correction 2026-05-12 22:20:04 +02:00
Rene Fichtmueller
9027484e3d sync: record secure gateway tracking setup 2026-05-12 22:17:33 +02:00
Rene Fichtmueller
c4056701b3 sync: record gateway health check 2026-05-12 21:42:43 +02:00
Rene Fichtmueller
5afc79ea52 fix(gateway): localhost exempt from HTTPS redirect; magatama-infra-health routing
- tls-config.ts: skip HTTP→HTTPS redirect for localhost/127.0.0.1 callers
  so internal services (infra-health, fix-engine) can call via plain HTTP
- routing-rules.yaml: add magatama-infra-health + infra-health to
  ctx_health_diagnose allowed callers; add qwen2.5:3b to fallback chain
2026-05-09 10:33:07 +02:00
Rene Fichtmueller
09165b9bf7 feat: restore workbench v1 and publish wired v2 2026-05-03 09:53:40 +02:00
Rene Fichtmueller
060b846d9b feat: publish llm gateway v2 dashboard alongside restored workbench 2026-05-01 17:43:32 +02:00
Rene Fichtmueller
e272105bcf sync: add chat handoff + context scaffolding for Codex integration (2026-04-29) 2026-04-29 22:48:23 +02:00
Rene Fichtmueller
91384dbb2a fix: SSE stream endpoint with proper HTTP/2 stream handling and heartbeat
- Fixed /api/stream/requests endpoint HTTP/2 INTERNAL_ERROR
- Use reply.raw.writeHead() instead of Fastify headers API for SSE
- Added 30s heartbeat to keep connection alive
- Proper event format with 'event:' and 'data:' fields
- Comprehensive error handling and cleanup on disconnect
- Mirrors working pattern from /api/stream/costs endpoint
- Resolves dashboard perpetual 'Loading...' state
2026-04-26 23:52:13 +02:00
Rene Fichtmueller
200cc7f2dc fix: Correct Cloudflare tunnel and setup script to use port 3103
The LLM Gateway is configured to run on port 3103 in ecosystem.config.cjs,
but the Cloudflare tunnel configuration and setup script were referencing port
3100, causing 502 Bad Gateway errors.

Updates:
- cloudflare-tunnel.md: Changed tunnel ingress from localhost:3100 to localhost:3103
- setup-erik.sh: Updated health check URL and output messages to port 3103
- This fixes the Cloudflare tunnel connection that was causing public HTTPS access to fail

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-04-26 21:04:36 +02:00
Rene Fichtmueller
255bd90e7e fix: add missing jose dependency for JWT validation 2026-04-26 20:45:05 +02:00