1
0
simple-ai-gateway-tool/ARCHITECTURAL-FACTS.md
Nilton Constantino d48172539a
first commit
2026-04-22 14:55:58 +01:00

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.