7.5 KiB
Architectural Facts
Companion document to README.md — covers the why behind the architecture, not the how.
Core Principle: AI as Requesting Client
The gateway treats AI agents the same way secure backends treat any client: you cannot stop someone from sending a curl request, but you can control whether it succeeds. Credentials are not exposed, access is scoped, and every action must pass through policy-enforced backend services.
The objective is not to "control the AI". The objective is: no meaningful action succeeds unless it passes through controlled, policy-enforced backend systems.
This aligns with OWASP guidance for AI agents (least privilege, separation of untrusted input from trusted instructions) and MCP security expectations.
Module Architecture
The project is a modular monolith — 17 Gradle modules organized by bounded context, deployed as a single Spring Boot application.
app-monolith # Composition root
main-infrastructure # Shared kernel (BaseModel, exceptions, value objects)
main-service # Application services
main-contract-subjects # Cross-module contracts
adapter-http-security-infra # JWT auth, Spring Security config
adapter-http-gateway # HTTP orchestration layer
gateway-module-policy/ # Policy engine (api, shared, contract-subjects, domain, model-and-data)
gateway-module-authorization/ # Authorization (api, shared, contract-subjects, domain, model-and-data)
gateway-module-audit/ # Audit trail (api, contract-subjects, domain)
gateway-module-fraud-detection/ # Fraud detection (api, domain)
gateway-module-tool-registry/ # Tool registry (api, contract-subjects, domain)
gateway-providers/ # Tool implementations (jira, jenkins, database)
Each module follows a consistent sub-module split: api (interfaces), domain (logic), model-and-data (persistence/config), contract-subjects (cross-module DTOs), shared (module-internal utilities).
Policy Engine: Deny-Overrides Strategy
All policy evaluation uses deny-overrides: any single rule can block an action, regardless of other rules that might allow it. The default decision is deny.
Six typed rule evaluators handle distinct concerns:
| Rule Type | Purpose |
|---|---|
scope |
Verifies the agent holds the required scope for the tool+action |
environment |
Per-environment decisions (allow staging, require approval for prod) |
argument_allowlist |
Validates specific arguments against an allowlist |
time_window |
Restricts execution to business hours or defined windows |
rate_limit |
Caps requests per agent per tool within a time window |
block |
Unconditional deny for decommissioned or dangerous tools |
Design choice: Typed evaluators over a generic DSL (like OPA/Rego). Each rule type is self-documenting and domain-specific. This trades extensibility for clarity, which is appropriate for the current scope of the project.
Future evolution: If rule types grow beyond ~10 or policies require complex cross-rule logic (e.g., "allow if scope matches AND time is within window AND the last 3 requests were not denied"), migrate to Open Policy Agent (OPA) with Rego policies. OPA provides a mature ecosystem: policy bundles, decision logging, partial evaluation, and a test framework. The migration path is straightforward — the PolicyEvaluator interface stays, the typed evaluators are replaced by an OPA client that sends the request context as input and receives allow/deny decisions. Policy configuration moves from policies.yaml to .rego files, gaining full logical expressiveness.
Policy configuration lives in files/config/policies.yaml with a Kubernetes-style manifest format (apiVersion, kind, metadata, spec). Rules support $data references for shared values (allowlists, rate limits, time windows).
Tool Registry: Autodiscovery
Tool providers register via @Component implementing ToolProvider. The gateway discovers them at startup through Spring's component scanning — no central registration file. Adding a new tool provider is a single-class operation.
Current providers: Jira (read/create), Jenkins (deploy), Database (query, migrate, execute).
Audit: Non-Optional, TSDB-Ready
Every gateway request produces an AuditEntry — both allowed and denied actions are logged. The data model separates tags (indexed dimensions: agentId, tool, action, decision) from fields (payload: arguments, reason, scopes, policyRuleId), making it ready for time-series database ingestion.
Current storage: NDJSON append-only file. The tag/field separation allows migration to a TSDB without changing the domain model.
Future evolution: Replace the NDJSON file with a time-series database such as Prometheus (for metrics-oriented queries and alerting integration) or InfluxDB (for high-cardinality event storage with native retention policies). The existing tags/fields data model maps directly to TSDB concepts — tags become indexed labels, fields become measurement values. This swap is a storage-layer change only; the AuditStore interface and domain model remain untouched.
Fraud Detection
A dedicated module analyzes request patterns for anomalies: SQL injection attempts in arguments, prompt injection patterns, and repeated denial sequences (an agent hammering a denied tool). Fraud signals are recorded in audit entries alongside policy decisions.
Future evolution: Current detection uses deterministic pattern matching (regex, counters, heuristics). A natural next step is integrating LLM-based verification — feeding suspicious requests to a language model that can assess intent with semantic understanding rather than pattern matching. This enables detection of obfuscated injection attempts, novel attack vectors, and context-dependent anomalies that rule-based systems miss. The FraudDetector interface already supports this: add an LLM-backed implementation alongside the existing rule-based one, and compose them (rule-based as fast first pass, LLM as deeper analysis for flagged requests).
Security Model
- JWT authentication with an RSA key pair generated at startup
- Stateless sessions — no server-side session storage
- Credentials never exposed to tools — the gateway holds credentials; providers receive pre-authenticated clients
- All policy enforcement is server-side — clients stay thin and contain no policy logic
Client Authentication
Client credentials should be supplied through environment variables or another secret source outside the repository. The important property is simple: clients authenticate, receive scoped JWTs, and never embed downstream system credentials.
This keeps the trust boundary where it belongs:
- The client proves identity to the gateway
- The gateway issues scoped tokens
- All protected operations still require server-side evaluation
Client Integration
Any HTTP-capable client can integrate with the gateway: an AI assistant, a CLI, a backend worker, or a small frontend. The integration model is intentionally simple: authenticate, obtain a JWT, call /api/gateway/execute, and handle one of three outcomes: ALLOW, DENY, or REQUIRE_APPROVAL.
Demo scenarios in scenes/ cover the full policy matrix: allowed operations, scope mismatches, blocked tools, rate limits, time windows, argument validation, fraud detection (SQL injection, prompt injection, repeated denials), audit trail queries, and hot-reload of policies.