Security
Version: 6.0.0
This document describes the security measures applied to EDDI's AI Agent Tooling system, particularly for tools that execute in response to LLM-generated arguments, as well as the Keycloak-based authentication layer.
Authentication — Keycloak OIDC
Version: ≥6.0.0
EDDI supports optional authentication via Keycloak using the Quarkus OIDC extension. Authentication is disabled by default — the system runs open (no login required) unless explicitly enabled.
Architecture
EDDI uses bearer-only (service) mode — the backend never redirects to Keycloak. The Manager SPA and Chat UI handle login via keycloak-js, then send Bearer tokens to the backend for validation.
Browser (EDDI Manager / Chat UI)
│
├── keycloak-js → Keycloak login → JWT access token
│
├── Authorization: Bearer <token> → EDDI backend
│ │
│ ├── Quarkus OIDC validates token via JWKS
│ ├── SecurityIdentity populated
│ └── RestAgentManagement checks identity
│
└── Token refresh (automatic, every 30s before expiry)Note: The backend runs with
application-type=service(bearer-only). It does not handle authorization code flows or login redirects. All login UI is handled client-side.
Quick Setup with Installer
The easiest way to enable auth is to use the installer:
This starts Keycloak alongside EDDI with pre-configured realm, clients, and test users:
eddi
eddi
admin
Full access, forced password change on first login
viewer
viewer
viewer
Read-only access, forced password change on first login
Configuration Properties
quarkus.oidc.enabled
Build-time
true
Extension active — must be true at build time
quarkus.oidc.tenant-enabled
Runtime
false
Enables/disables auth enforcement
quarkus.oidc.auth-server-url
Runtime
http://localhost:8180/realms/eddi
Keycloak realm URL
quarkus.oidc.client-id
Runtime
eddi-backend
OIDC client ID (bearer-only)
quarkus.oidc.application-type
Runtime
service
Bearer-only mode (no login redirects)
authorization.enabled
Runtime
${quarkus.oidc.tenant-enabled}
Fine-grained @RolesAllowed authorization
Important:
quarkus.oidc.enabledis a build-time property — it cannot be changed at container start. The OIDC extension must always be active in the binary. Usequarkus.oidc.tenant-enabled(runtime) to toggle auth on/off via environment variables.
Enabling Auth at Container Start
Auth Permissions
When OIDC is enabled, the following permission rules apply (see application.properties):
/q/metrics/*, /q/health/*
Permit — Infrastructure endpoints
/, /manage, /manage/*, /chat, /chat/*
Permit — SPA entry points (the SPA loads and handles Keycloak login via keycloak-js)
/agents/production/*
Permit — Production conversation endpoints (public-facing)
/scripts/*, /fonts/*, /css/*, /js/*, /img/*
Permit — Static assets for Manager SPA
/* (catch-all)
Authenticated — All other API endpoints require a valid Bearer token
RestAgentManagement Gate
RestAgentManagement.checkUserAuthIfApplicable() enforces per-request auth:
When
quarkus.oidc.tenant-enabled=false→checkForUserAuthentication=false→ all requests passWhen
quarkus.oidc.tenant-enabled=true→ only authenticated users can access production endpointsRequests to
/production/environments always pass regardless of auth status
Local Development Keycloak
The EDDI-Manager repo provides a docker-compose for local Keycloak:
This starts Keycloak 26 on port 8180 with:
Realm:
eddiClients:
eddi-manager(SPA, public),eddi-backend(bearer-only)Roles:
admin,editor,viewerTest users:
eddi/eddi(admin),viewer/viewer(read-only)
Threat Model
When an LLM is given access to tools, every argument it supplies must be treated as untrusted input. An attacker can craft prompts that cause the LLM to pass malicious arguments to tools — a class of attacks known as prompt injection. EDDI mitigates these risks at the tool-execution layer so that individual tools do not need to implement their own defences.
SSRF Protection — UrlValidationUtils
UrlValidationUtilsApplies to: PDF Reader, Web Scraper, and any future tool that fetches remote resources.
Server-Side Request Forgery (SSRF) occurs when an attacker tricks a server-side application into making requests to internal services. EDDI prevents this with UrlValidationUtils.validateUrl(url):
Scheme Allowlist
Only http and https URLs are accepted. All other schemes are rejected:
file://
file:///etc/passwd
ftp://
ftp://internal-server/data
jar://
jar:file:///app.jar!/secret
gopher://
gopher://127.0.0.1:25/...
Private / Internal IP Blocking
DNS resolution is performed and the resolved address is checked before any connection is made:
127.0.0.0/8
Loopback addresses
10.0.0.0/8
Private network (Class A)
172.16.0.0/12
Private network (Class B)
192.168.0.0/16
Private network (Class C)
169.254.0.0/16
Link-local (AWS/GCP metadata)
fd00::/8
IPv6 unique-local
fe80::/10
IPv6 link-local
::1
IPv6 loopback
Cloud Metadata Endpoint Blocking
Cloud provider metadata services are explicitly blocked by IP and hostname:
169.254.169.254(AWS, GCP, Azure metadata)metadata.google.internal(GCP)
Internal Hostname Blocking
Hostnames that indicate internal services are rejected:
localhostAny hostname ending in
.localAny hostname ending in
.internal
Usage
Sandboxed Math Evaluation — SafeMathParser
SafeMathParserApplies to: Calculator tool.
Problem
The original implementation used Java's ScriptEngine (Nashorn/Rhino) to evaluate math expressions. A malicious expression could execute arbitrary JavaScript:
Solution
The Calculator tool now uses SafeMathParser, a recursive-descent parser written in pure Java. It:
Recognises only numeric literals, arithmetic operators (
+,-,*,/,%,^), and parenthesesSupports a fixed allowlist of math functions (
sqrt,pow,abs,sin,cos,log,exp, etc.)Supports only two constants (
PI,E)Has no code execution capability — unrecognised tokens cause an immediate parse error
Requires no external dependencies (no Rhino/Nashorn/GraalJS)
Allowed Grammar
Supported Functions
sqrt, pow, abs, ceil, floor, round, sin, cos, tan, asin, acos, atan, atan2, log, log10, exp, signum/sign, toRadians, toDegrees, cbrt, min, max
Tool Execution Pipeline
All tool invocations — both built-in and HTTP-call-based — are routed through ToolExecutionService.executeToolWrapped(). This ensures consistent security and operational controls:
Rate Limiting
Algorithm: Token-bucket per tool name
Configuration:
enableRateLimiting(defaulttrue),defaultRateLimit(default100),toolRateLimits(per-tool overrides)Behaviour: Requests exceeding the limit receive a "Rate limit exceeded" error message returned to the LLM, which can then retry or use a different approach
Smart Caching
Key: SHA-256 hash of
toolName + argumentsConfiguration:
enableToolCaching(defaulttrue)Behaviour: Identical tool calls within the same conversation return cached results, reducing redundant API calls and cost
Cost Tracking
Configuration:
enableCostTracking(defaulttrue),maxBudgetPerConversation(no default — unlimited)Eviction: To prevent unbounded memory growth, the tracker caps per-conversation entries at 10 000 and evicts the oldest ~10% when the limit is reached
Behaviour: When the budget is exceeded, tools return a "Budget exceeded" message to the LLM
Configuration Example
Conversation Coordinator — Sequential Processing
The ConversationCoordinator ensures that messages for the same conversation are processed sequentially, preventing race conditions in conversation state. The isEmpty() → offer() → submit() sequence is wrapped in a synchronized block to prevent two concurrent requests from both being submitted to the thread pool simultaneously.
Different conversations are processed concurrently — only same-conversation messages are serialised.
HTTP Call Content-Type Handling
The HttpCallExecutor uses strict equality (equals) rather than prefix matching (startsWith) when checking the Content-Type header against application/json. This prevents content types like application/json-patch+json from being incorrectly deserialised as standard JSON.
Recommendations for New Tools
When adding a new tool to EDDI:
Validate all URLs with
UrlValidationUtils.validateUrl()before making any outbound requestNever use
ScriptEngineor any form of dynamic code evaluationAdd
@Toolannotations with clear descriptions so the LLM understands the tool's purpose and constraintsWrite unit tests that specifically verify rejection of malicious inputs (SSRF URLs, injection strings)
Route execution through
ToolExecutionServiceto inherit rate limiting, caching, and cost tracking
TLS Requirements
EDDI does not enforce TLS directly — it is designed to run behind a reverse proxy (nginx, Traefik, Caddy, cloud load balancer) that handles TLS termination.
For regulated deployments (HIPAA, EU AI Act), all traffic to and from EDDI must be encrypted in transit. A compliance startup warning is logged if no TLS certificate is detected.
Option 1: TLS at Reverse Proxy (Recommended)
Configure your reverse proxy to terminate TLS and forward traffic to EDDI on localhost:7070. This is the standard production pattern.
Option 2: TLS Directly in Quarkus
Internal Traffic
If EDDI and its database run on the same host or within a private network, internal traffic may be unencrypted. However, HIPAA deployments should evaluate whether this meets their security requirements.
Supply Chain & CI/CD Security
EDDI's CI/CD pipeline enforces multiple automated security gates before any code reaches production. All GitHub Actions are SHA-pinned to immutable commit hashes to prevent supply-chain attacks via tag hijacking.
Security Scanning Pipeline
CodeQL
SAST
Java source code
Blocking (PR) + weekly deep scan
N/A
Trivy
CVE scanning
Filesystem deps + Docker image
Blocking (CRITICAL/HIGH)
.trivyignore
Gitleaks
Secret scanning
Full git history
Blocking
.gitleaksignore
ZAP
DAST
Live API (OpenAPI spec)
Report-only
fail_action in workflow
CycloneDX
SBOM
Maven dependency tree
Artifact generation
N/A
Jazzer
Fuzz testing
PathNavigator, MatchingUtilities
JUnit integration
N/A
Override Files
For audited false positives, EDDI provides override files at the repository root:
.trivyignore— Suppress specific CVEs with mandatory justification comments.gitleaksignore— Suppress specific Gitleaks fingerprints with justification
Both files should be reviewed periodically to ensure suppressions remain valid.
Fuzz Testing
Security-critical input parsers are tested with Jazzer coverage-guided fuzzing:
PathNavigator— Safe path navigation (replaced OGNL). Fuzz targets:getValue,setValue, arithmetic pathsMatchingUtilities— Condition evaluation for DynamicValueMatcher
In CI, fuzz tests run as standard JUnit regression tests. For deep coverage-guided fuzzing locally:
Docker Image Security
Trivy scans the built Docker image for CRITICAL/HIGH CVEs before pushing to Docker Hub
Red Hat Preflight checks verify container certification compliance (labels, licenses)
Security headers are validated against the running container in the smoke test
See Also
LangChain Integration — Full agent configuration reference
Agent Father LangChain Tools Guide — Guided tool setup
Architecture — EDDI's lifecycle pipeline and concurrency model
Metrics — Monitoring tool execution performance
HIPAA Compliance — HIPAA deployment guide
EU AI Act Compliance — EU AI Act compliance
Compliance Data Flow — Data flow diagram for auditors
Last updated
Was this helpful?