4 Governance Domains
Admina organises its governance capabilities into 4 domains. Each domain groups related controls under a single engine with clear boundaries, so you always know what runs, where, and at what latency cost.
Data Sovereignty
Rust + spaCy NER ยท Presidio optionalEnsures that sensitive data never leaves the boundaries you define. PII is automatically detected and redacted, data residency rules are enforced in the SDK, and every data type is classified for compliance tracking.
PII Redaction
Detects and redacts email addresses, phone numbers, credit cards, SSNs, IBANs, IP addresses, and person names (via spaCy NER + regex). The Rust engine processes each scan in 0.62µs median.
Data Residency Enforcement
Restricts data to allowed geographic or logical zones โ EU, local, or custom-defined regions.
This is an SDK feature: zones are checked by
GovernedData(residency_zone=โฆ, allowed_zones=โฆ), which raises
PermissionError and emits a BLOCK audit event on every
ingest or query that would leave the allowed zones. The governance proxy does not
run residency checks on proxied traffic.
Data Classification
Categorizes data types automatically so downstream compliance checks (Domain 4) can match each payload against the correct regulatory requirements.
Patterns detected
EMAILuser@example.com → [EMAIL]PHONE+39 055 123456 → [PHONE]CREDIT_CARD4111 1111 1111 1111 → [CREDIT_CARD]SSN123-45-6789 → [SSN]IBANIT60X0542811101000000123456 → [IBAN]IP_ADDRESS192.168.1.1 → [IP_ADDR]PERSONJohn Smith → [PERSON] (NER)ORGAcme Corp → [ORG] (NER)GPE / LOCPisa, Italy → [LOCATION] (NER)
NER (Named Entity Recognition) in the default engine uses spaCy en_core_web_sm.
Regex-based patterns work for all languages, and the default engine already covers the
Italian codice fiscale and Spanish DNI/NIE by regex. Since v0.11.0 the optional Presidio
engine adds Italian NER via it_core_news_sm and maps its own IT/ES
identifier recognisers onto the same categories โ see
Selectable PII engine below.
Graceful fallback (v0.9.2+). spaCy is imported lazily, so the
proxy boots without the [nlp] extra. In that case PII redaction
runs in regex-only mode (email, phone, credit card, SSN, IBAN,
IP address, EU national IDs). Install admina-framework[nlp] and run
python -m spacy download en_core_web_sm to enable the NER entities
above (PERSON, ORG, GPE/LOC).
Selectable PII engine (v0.11.0)
Since v0.11.0 the PII engine is selectable. The default is unchanged โ
spacy-regex (spaCy NER + regex, optionally Rust-accelerated).
The alternative is Microsoft Presidio, selected with
ADMINA_PII_ENGINE=presidio or the top-level pii_engine: presidio
key in admina.yaml โ a sibling of forensic_store: and
auth_provider:, not nested under domains.
Presidio is used analyzer-only: it performs detection, while Admina
keeps its own masking. Both engines read their mask tokens from the same table, so the
output mask format is identical โ [EMAIL], [PERSON],
[IBAN] and so on โ which gives drop-in mask compatibility between them.
pip install "admina-framework[presidio]" python -m spacy download en_core_web_sm it_core_news_sm
The extra pulls presidio-analyzer only โ Admina does its own masking, so
presidio-anonymizer is not installed.
Languages supported are EN and IT, each active only if its spaCy model
is actually installed. Install only en_core_web_sm and you silently get an
EN-only Presidio engine, with the Italian fiscal-code recogniser absent.
The Presidio โ Admina category mapping covers EMAIL, PHONE, CREDIT_CARD, IBAN, IP_ADDRESS, SSN, PERSON, LOCATION/GPE, ORG, Italian codice fiscale and Spanish DNI/NIE. Categories the default engine has but Presidio cannot emit: date of birth, LOC, and the German Personalausweis (the latter off by default in both engines).
ADMINA_ENGINE=rust, which warns and falls back.
Both engines read the built-in category defaults under the proxy โ neither path is
handed a config object, so a per-deployment narrowing under
domains.data_sovereignty.pii.categories in admina.yaml is
not applied to proxied traffic; it only takes effect when you construct
PIIRedactor(config=โฆ) yourself in the SDK. With Presidio
ADMINA_SPACY_MODEL is also ignored
(its models are fixed). Selecting a named PII engine also bypasses Rust acceleration,
which only ever applies to spacy-regex. For the measured detection
comparison between the two engines see
the Rust engine guide.
AI Infrastructure
Python (opt-in)
An opt-in domain that provides a fully governed AI stack โ
LLM serving, RAG pipelines, and a Web UI โ all enabled via
admina.yaml configuration.
LLM Engine
Abstracts Ollama and vLLM backends with automatic GPU detection (NVIDIA and AMD). Supports hot model switching without downtime โ swap models while live traffic continues to be served.
RAG Pipeline
ChromaDB vector store with recursive character and semantic chunking. Ingests multiple formats: PDF, DOCX, HTML, CSV, XML.
Web UI
Open WebUI container with built-in OIDC and LDAP authentication. Provides a chat interface for end-users while all traffic flows through the Admina governance proxy.
admina.yaml under the domains.ai_infra section.
Agent Security
Rust RegexSetProtects the full agent lifecycle โ from prompt injection attacks to runaway loops โ with microsecond-latency checks that apply to every agent-to-agent call transiting the proxy.
Anti-Injection Firewall
The default Python engine compiles 33 regex patterns across 9 categories,
matched against the raw text and against an evasion-normalised copy
(homoglyph, leetspeak, char-by-char, base64, ROT13), plus heuristic scoring โ
7.79µs median. With admina-core installed the
optional Rust engine runs its own narrower 15-pattern RegexSet in a single pass at
2.08µs median; see
the Rust engine guide for the detection trade-off.
The 9 categories โ these are the only labels that can appear in
detections_by_type, in Prometheus label values, or as valid
disabled_categories entries (see
MODEL_CARD.md
for definitions and known limitations):
instruction_override role_hijack prompt_extraction jailbreak delimiter_injection data_exfiltration tool_abuse obfuscation multilang_evasion
Each category groups several pattern families, which are not reported separately:
developer-mode and DAN-mode toggles both report jailbreak, and
"what are your instructions" reports prompt_extraction. The Rust
engine carries its own coarser 15-label set โ it appears only in that engine's
matched_patterns, never in the stats API, the Prometheus series or
disabled_categories, and two of its labels
(new_instructions, roleplay_escape) have no Python
equivalent at all. Setting disabled_categories or
custom_patterns forces the Python engine so operator rules are
actually enforced.
Patterns target English with an explicit multilang_evasion subset
for French, Italian, Spanish, German. Contributions for additional locales
are welcome.
Custom domain-specific patterns
Add deployment-specific patterns under
domains.agent_security.firewall.custom_patterns
in admina.yaml โ no fork required:
domains:
agent_security:
firewall:
custom_patterns:
- regex: "delete\\s+user\\s+\\d+"
category: "destructive_user_op"
risk_level: high
- regex: "(production|prod)\\s+(database|db)\\s+drop"
category: "prod_db_destructive"
risk_level: critical Governance reaction mode
Switch how the firewall reacts to flagged traffic, useful for the first 1โ2 weeks of a new deployment:
enforceDefault โ block every flagged request, at whatever severityobserveNever block; log "would have blocked" for review and FP tuningdry-runLike observe + tag the response so downstream tools know the request was analysedLoop Breaker
TF-IDF + cosine similarity on a sliding window of recent requests
(configurable threshold 0.85).
Latency: 2.38µs.
Automatically circuit-breaks sessions before runaway costs or deadlocks occur.
Proxy governance
The full security pipeline applies to all agent-to-agent calls transiting the proxy โ not just user-facing requests. Every hop is inspected.
When a pluggable guard raises an exception, the default is to skip it and
record an ERROR check; setting ADMINA_GUARD_FAIL_MODE=closed
(values open | closed, default open) turns that
exception into a BLOCK โ see
Configuration for the exact scope.
Risk levels
risk_level is not a block threshold. In
enforce mode the pipeline branches on whether the firewall matched
anything โ a single MEDIUM obfuscation pattern blocks exactly like a
CRITICAL jailbreak. What the level is for is triage and alert routing: it is the
highest severity among the patterns that matched, and it travels on the response,
the audit event and the forensic record.
LOWReported when nothing matched โ and for any custom_patterns entry you declare at that level, which still blocksMEDIUM3 patterns โ base64 and ROT13 obfuscation markers, plus the "what are your instructions" phrasing of prompt extractionHIGH15 patterns โ role hijack, prompt extraction, data exfiltration, <system>-style delimiter injection, hex-escape obfuscation, internal-API tool abuseCRITICAL15 patterns โ direct instruction override and its IT/FR/ES/DE equivalents, named jailbreaks (DAN & co.), special-token delimiter injection, destructive or shell tool abuse
There is no session-flagging mechanism: a block is per-request, and the session is
only interrupted by the loop breaker's circuit breaker. A forensic record is written
for every governed request โ both /mcp and the
OpenAI-compatible gateway โ not only for flagged or critical ones.
Compliance
Python + Rust (sha2)Multi-regulation compliance tooling โ EU AI Act classification & gap analysis, NIS2 Art. 21(2) self-assessment, GDPR Art. 30 RoPA registry & Art. 35 DPIA scaffold, plus a hand-curated cross-regulation matrix. Backed by a SHA-256 hash-chained forensic black box and native OpenTelemetry spans.
gap_analysis() does
not constitute legal compliance and cannot replace conformity assessment
under EU AI Act Art. 43, designated NIS2 authority audit, or GDPR DPO review.
See the
MODEL_CARD
for scope, limitations, and known failure modes of every component.
EU AI Act
Automated risk classification under Article 6 and gap analysis against Articles 9โ15. Risk categories: unacceptable, high, limited, minimal.
UNACCEPTABLEBanned systems (social scoring, real-time biometrics in public, non-consensual deepfakes / synthetic CSAM โ added by Omnibus VII, effective 2 Dec 2026)HIGHCritical infrastructure, employment, education, law enforcementLIMITEDChatbots, emotion recognition โ transparency obligations applyMINIMALSpam filters, games โ no additional requirementsTimeline โ Omnibus VII (Council & Parliament agreement, 7 May 2026)
The Omnibus VII agreement postponed several high-risk deadlines, reduced the Art. 50
transparency grace period, and added a new Art. 5 prohibition. Admina's
EU_AI_ACT_DEADLINES table mirrors the agreed timeline.
Source: Council press release, 7 May 2026. Formal adoption expected before 2 August 2026.
NIS2 โ Article 21(2) self-assessment
Deterministic checklist of 10 measure areas ร 4 controls = 40 checks mirroring the technical and organisational measures required by NIS2 Art. 21(2): policies on risk analysis and information system security; incident handling; business continuity (backup, disaster recovery, crisis management); supply chain security; security in network and IS acquisition, development, and maintenance; policies and procedures to assess effectiveness of measures; basic cyber hygiene practices and cybersecurity training; policies and procedures regarding the use of cryptography; human resources security, access control policies, asset management; multi-factor authentication and secure communications. Gap analysis surfaces unmet controls.
Endpoints: GET /api/compliance/nis2/areas ยท
POST /api/compliance/nis2/assess.
GDPR โ RoPA & DPIA
Two GDPR components ship with Admina:
- Article 30 RoPA registry โ typed CRUD over Records of
Processing Activities. In-memory by default: the config loader has no
gdprsection, so the commentedropa_pathkey inadmina.yaml.exampleis inert. Persistence to a JSON file on disk is opt-in via theADMINA_GDPR_ROPA_PATHenvironment variable, or by passingstorage_path=when you constructProcessingActivitiesRegistryyourself. If the path is not writable the registry logs a warning and stays in-memory. - Article 35 DPIA template โ generates a Markdown scaffold from operator-supplied facts, ready for legal review.
Endpoints: GET / POST / PUT / DELETE /api/compliance/gdpr/records[โฆ] ยท
POST /api/compliance/gdpr/dpia/template.
Cross-regulation matrix
Hand-curated mapping of 12 operational controls across EU AI Act,
NIS2, and GDPR โ single source of truth for controls that satisfy multiple
frameworks at once. Useful to avoid duplicate audit work; constant, no
configuration. Surfaced at GET /api/compliance/matrix.
Forensic Black Box
SHA-256 hash chain with WORM semantics. Each record links to the previous hash, making any modification immediately detectable. Three backends are available โ see Configuration โ Forensic backends for the comparison and the production recommendation.
_chain_state.json file (or bucket-root object), so a restart
does not have to re-read every record. Set ADMINA_FORENSIC_STATE_KEY to
have that file signed with HMAC-SHA256 into a _chain_state.json.sig
sidecar. On restore a valid signature is trusted as a fast path; a missing or invalid
signature is treated as untrusted โ a CRITICAL event is logged and the state is
reconstructed from the stored records instead of the potentially rewritten state
file. What is signed is the exact byte payload written to the state file โ the chain
head, the record count and the update timestamp โ and the signature is stored as a hex
digest in the sidecar, next to the records on disk or as its own object in the same bucket.
This covers ForensicBlackBox on both its filesystem and its S3 backend, and
the FilesystemForensicStore plugin as well. With no key set the state file is
simply unsigned and the baseline truncation protection (reconstruction from persisted
records) is retained โ signing is recommended in production, not required. Note that a
missing sidecar and a forged one produce the same log line, so the system does not
distinguish tampering from an absent signature.
Embedding Admina directly rather than running the proxy? Both classes accept the key as
the constructor keyword argument state_signing_key: str | None = None, which
takes precedence over ADMINA_FORENSIC_STATE_KEY โ the environment variable is
consulted only when the argument is omitted or empty:
from admina.domains.compliance.forensic import ForensicBlackBox # signing_key comes from your own secret manager โ never hardcode it box = ForensicBlackBox(filesystem_dir="/var/lib/admina/forensic", state_signing_key=signing_key)
Record structure
{
"sequence_number": 1024,
"timestamp_utc": "2026-05-21T14:23:01.442331+00:00",
"timestamp_unix_ms": 1779373381442,
"previous_hash": "9c12...",
"event": {
"event_id": "3f2a9c14-5b7e-4d61-9a03-2c8f1e4b7d55",
"event_type": "mcp_request",
"agent_id": "openclaw-agent",
"session_id": "sess_abc123",
"method": "tools/call",
"action": "allow",
"risk_level": "low",
"governance_latency_ms": 0.42,
"checks": {"loop_breaker": {...}, "firewall": {...}, "pii_redaction": {...}},
"would_action": null
},
"record_hash": "a3f8..."
}
Everything the proxy submits lives under event; the surrounding keys are the
chain metadata added by ForensicBlackBox.record(). Hashes are bare SHA-256 hex
digests โ no sha256: prefix.
would_action is the shadow decision recorded when
ADMINA_GOVERNANCE_MODE is observe or dry-run โ
the action the pipeline would have taken before the mode downgraded it to ALLOW. The key
is always present in the event object of the /mcp record; its
value is "block" or
"circuit_break" in those modes, and null under
enforce mode and on clean traffic. Before v0.11.0 the shadow decision reached only the ClickHouse analytics
record, leaving the hash-chained audit trail without it. Caveat: the OpenAI-compatible
gateway's forensic record does not carry would_action โ the fix landed on
/mcp only.
Chain verification
Chain integrity is checked programmatically via
ForensicBlackBox.verify_chain(last_n=0) (in
domains/compliance/forensic.py). It is a coroutine, so it must be
awaited; it reads the records back from the configured backend itself rather than
taking them as an argument. Pass last_n > 0 to verify only the tail.
The same check backs admina doctor and the
GET /api/v1/forensic/verify endpoint.
import asyncio from admina.domains.compliance.forensic import ForensicBlackBox box = ForensicBlackBox(filesystem_dir="/var/lib/admina/forensic") result = asyncio.run(box.verify_chain()) print(result) # {'valid': True, 'records': 1024, 'last_hash': 'a3f8...'}
To verify a list of records you already hold, use the synchronous
verify_records(records), which returns
{"valid": bool, "checked": int} (plus "error"
when invalid).
OpenTelemetry Integration
Native OTEL spans for all governance decisions.
Exports to OTLP gRPC on port 4317.
Every domain action is recorded as a span attribute โ no code changes required in your agent.
OISG Adequacy Score
The full Docker Compose stack ships OISG adequate
out of the box; a bare pip install scores 75, in the Good coverage band.
The adequacy score (Open ยท Intelligent ยท Secure ยท Governed, 0โ100) is computed
automatically from the live runtime state โ no manual checkboxes โ and is surfaced
on the dashboard alongside the Admina Score.
curl http://localhost:8080/api/dashboard/oisg \ -H "X-API-Key: $ADMINA_API_KEY" # {"total": 85, "level": "OISG adequate", "pillars": {...}}
โ Full paradigm, pillars, and Admina โ OISG mapping: OISG Adequacy.