admina-framework 0.11.0__tar.gz → 0.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {admina_framework-0.11.0/admina_framework.egg-info → admina_framework-0.12.0}/PKG-INFO +14 -7
- {admina_framework-0.11.0 → admina_framework-0.12.0}/README.md +9 -2
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/__init__.py +1 -1
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/main.py +166 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/core/config.py +34 -3
- admina_framework-0.12.0/admina/domains/agent_security/coordination.py +822 -0
- admina_framework-0.12.0/admina/domains/agent_security/egress.py +574 -0
- admina_framework-0.12.0/admina/domains/agent_security/fingerprint.py +211 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/governance.py +43 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/engines/__init__.py +35 -1
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/api/dashboard.py +7 -2
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/api/gateway.py +5 -1
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/api/integration.py +26 -1
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/config.py +3 -1
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/main.py +197 -4
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/state.py +10 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/corpora/SHA256SUMS +1 -0
- admina_framework-0.12.0/admina/redteam/corpora/coordination.jsonl +7 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/governed_model.py +29 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0/admina_framework.egg-info}/PKG-INFO +14 -7
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina_framework.egg-info/SOURCES.txt +17 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina_framework.egg-info/requires.txt +4 -4
- {admina_framework-0.11.0 → admina_framework-0.12.0}/pyproject.toml +7 -7
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_benchmark_14us.py +38 -0
- admina_framework-0.12.0/tests/test_coordination.py +1240 -0
- admina_framework-0.12.0/tests/test_coordination_cli.py +117 -0
- admina_framework-0.12.0/tests/test_coordination_corpus.py +147 -0
- admina_framework-0.12.0/tests/test_coordination_wiring.py +889 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_core_config.py +102 -0
- admina_framework-0.12.0/tests/test_egress_analyze.py +401 -0
- admina_framework-0.12.0/tests/test_egress_cli.py +257 -0
- admina_framework-0.12.0/tests/test_egress_pipeline.py +285 -0
- admina_framework-0.12.0/tests/test_egress_policy.py +137 -0
- admina_framework-0.12.0/tests/test_egress_surfaces.py +498 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_engines.py +132 -0
- admina_framework-0.12.0/tests/test_error_disclosure.py +72 -0
- admina_framework-0.12.0/tests/test_fingerprint.py +217 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_gateway_endpoints.py +1 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_gateway_guard_fail_mode.py +1 -0
- admina_framework-0.12.0/tests/test_proxy_governance_mode_config.py +56 -0
- admina_framework-0.12.0/tests/test_proxy_metrics_endpoint.py +143 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/LICENSE +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/NOTICE +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/commands/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/admina.yaml.j2 +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/docker-compose.yml.j2 +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/env.j2 +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/main.py.j2 +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/plugin.py.j2 +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/plugin_pyproject.toml.j2 +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/plugin_readme.md.j2 +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/plugin_test.py.j2 +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/core/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/core/event_bus.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/core/secrets.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/core/types.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/dashboard/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/dashboard/static/heimdall.png +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/dashboard/static/index.html +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/dashboard/static/vendor/alpinejs.min.js +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/agent_security/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/agent_security/firewall.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/agent_security/loop_breaker.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/ai_infra/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/ai_infra/llm_engine.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/ai_infra/rag.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/ai_infra/webui.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/cross_regulation.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/eu_ai_act.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/forensic.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/gdpr.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/nis2.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/oisg.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/otel.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/data_sovereignty/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/data_sovereignty/classification.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/data_sovereignty/pii.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/data_sovereignty/residency.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/engines/presidio.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/_engines.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/cheshirecat/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/cheshirecat/admina-plugin/admina_governance.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/crewai/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/crewai/callbacks.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/langchain/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/langchain/callbacks.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/n8n/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/openclaw/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/base.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/_streaming.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/anthropic.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/bedrock.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/gemini.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/mistral.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/ollama.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/openai.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/vllm.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/alerts/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/alerts/log.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/alerts/webhook.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/auth/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/auth/apikey.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/compliance/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/compliance/eu_ai_act.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/connectors/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/connectors/chromadb.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/connectors/filesystem.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/forensic/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/forensic/filesystem.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/guards/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/guards/guardrailsai_guard.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/pii/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/pii/spacy_regex.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/transports/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/transports/http_rest.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/transports/mcp.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/registry.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/api/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/engine_bridge.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/governance.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/multi_upstream.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/py.typed +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/baselines/baseline.json +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/corpora/injection.jsonl +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/corpora/loop.jsonl +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/corpora/pii.jsonl +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/corpora.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/detectors.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/gate.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/metrics.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/report.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/runner.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/__init__.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/_compat.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/compliance_kit.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/errors.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/governed_agent.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/governed_data.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/retry.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/streaming.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina_framework.egg-info/dependency_links.txt +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina_framework.egg-info/entry_points.txt +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/admina_framework.egg-info/top_level.txt +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/setup.cfg +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_domains.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_domains_governance.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_firewall_parity.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_forensic_blackbox.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_gateway_config.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_gateway_helpers.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_gateway_mount.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_governance_pipeline.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_guard_fail_mode.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_mcp_transport.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_presidio_engine.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_proxy_forensic_would_action.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_proxy_guard_fail_mode.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_proxy_mcp_response_pii.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_proxy_plugins.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_proxy_security.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_redteam_efficacy.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_redteam_lib.py +0 -0
- {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: admina-framework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Admina — governed AI development framework
|
|
5
5
|
Author-email: Stefano Noferi <info@admina.org>
|
|
6
6
|
Maintainer-email: Stefano Noferi <info@admina.org>
|
|
@@ -30,7 +30,7 @@ License-File: NOTICE
|
|
|
30
30
|
Requires-Dist: pyyaml<7,>=6.0
|
|
31
31
|
Requires-Dist: click<9,>=8.1
|
|
32
32
|
Requires-Dist: jinja2<4,>=3.1
|
|
33
|
-
Requires-Dist: cryptography<
|
|
33
|
+
Requires-Dist: cryptography<51,>=41.0
|
|
34
34
|
Provides-Extra: proxy
|
|
35
35
|
Requires-Dist: fastapi<1,>=0.104; extra == "proxy"
|
|
36
36
|
Requires-Dist: uvicorn[standard]<1,>=0.24; extra == "proxy"
|
|
@@ -52,15 +52,15 @@ Requires-Dist: opentelemetry-sdk<2,>=1.20; extra == "telemetry"
|
|
|
52
52
|
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc<2,>=1.20; extra == "telemetry"
|
|
53
53
|
Requires-Dist: opentelemetry-instrumentation-fastapi<1,>=0.40b0; extra == "telemetry"
|
|
54
54
|
Provides-Extra: rust
|
|
55
|
-
Requires-Dist: admina-core<0.
|
|
55
|
+
Requires-Dist: admina-core<0.13,>=0.9.3; extra == "rust"
|
|
56
56
|
Provides-Extra: full
|
|
57
57
|
Requires-Dist: admina-framework[nlp,proxy,telemetry]; extra == "full"
|
|
58
58
|
Provides-Extra: openai
|
|
59
|
-
Requires-Dist: openai<
|
|
59
|
+
Requires-Dist: openai<4,>=1.0; extra == "openai"
|
|
60
60
|
Provides-Extra: ollama
|
|
61
61
|
Requires-Dist: ollama<1,>=0.3; extra == "ollama"
|
|
62
62
|
Provides-Extra: anthropic
|
|
63
|
-
Requires-Dist: anthropic<
|
|
63
|
+
Requires-Dist: anthropic<2,>=0.39; extra == "anthropic"
|
|
64
64
|
Provides-Extra: mistral
|
|
65
65
|
Requires-Dist: mistralai<3,>=1.0; extra == "mistral"
|
|
66
66
|
Provides-Extra: gemini
|
|
@@ -331,16 +331,23 @@ from admina import GovernedModel, GovernedData, GovernedAgent, ComplianceKit
|
|
|
331
331
|
|
|
332
332
|
| Interface | Builtin implementations |
|
|
333
333
|
|-----------|------------------------|
|
|
334
|
-
| Model Adapter | Ollama, OpenAI |
|
|
334
|
+
| Model Adapter | Ollama, OpenAI, Anthropic, Gemini, Mistral, Bedrock, vLLM |
|
|
335
335
|
| Data Connector | ChromaDB, Filesystem |
|
|
336
336
|
| Governance Domain | GuardrailsAI (toxic, jailbreak, bias, PII) |
|
|
337
337
|
| Compliance Template | EU AI Act |
|
|
338
338
|
| Transport Adapter | MCP, HTTP REST |
|
|
339
339
|
| Forensic Store | Filesystem, S3-compatible (boto3 — AWS S3, MinIO, R2, …) |
|
|
340
340
|
| Auth Provider | API Key |
|
|
341
|
-
| PII Engine | spaCy + Regex |
|
|
341
|
+
| PII Engine | spaCy + Regex (default), Microsoft Presidio (`pip install admina-framework[presidio]`, `ADMINA_PII_ENGINE=presidio`) |
|
|
342
342
|
| Alert Channel | Log, Webhook |
|
|
343
343
|
|
|
344
|
+
Model adapters lazy-import their provider SDK, so install the ones you
|
|
345
|
+
use: `pip install admina-framework[adapters]` for all of them, or a
|
|
346
|
+
single provider (`[openai]`, `[ollama]`, `[anthropic]`, `[mistral]`,
|
|
347
|
+
`[gemini]`, `[bedrock]`). vLLM has no extra of its own — it serves an
|
|
348
|
+
OpenAI-compatible API and subclasses the OpenAI adapter, so install
|
|
349
|
+
`[openai]` for it.
|
|
350
|
+
|
|
344
351
|
```bash
|
|
345
352
|
admina plugin list # List registered plugins
|
|
346
353
|
admina plugin install ./my-plugin # Install a custom plugin
|
|
@@ -254,16 +254,23 @@ from admina import GovernedModel, GovernedData, GovernedAgent, ComplianceKit
|
|
|
254
254
|
|
|
255
255
|
| Interface | Builtin implementations |
|
|
256
256
|
|-----------|------------------------|
|
|
257
|
-
| Model Adapter | Ollama, OpenAI |
|
|
257
|
+
| Model Adapter | Ollama, OpenAI, Anthropic, Gemini, Mistral, Bedrock, vLLM |
|
|
258
258
|
| Data Connector | ChromaDB, Filesystem |
|
|
259
259
|
| Governance Domain | GuardrailsAI (toxic, jailbreak, bias, PII) |
|
|
260
260
|
| Compliance Template | EU AI Act |
|
|
261
261
|
| Transport Adapter | MCP, HTTP REST |
|
|
262
262
|
| Forensic Store | Filesystem, S3-compatible (boto3 — AWS S3, MinIO, R2, …) |
|
|
263
263
|
| Auth Provider | API Key |
|
|
264
|
-
| PII Engine | spaCy + Regex |
|
|
264
|
+
| PII Engine | spaCy + Regex (default), Microsoft Presidio (`pip install admina-framework[presidio]`, `ADMINA_PII_ENGINE=presidio`) |
|
|
265
265
|
| Alert Channel | Log, Webhook |
|
|
266
266
|
|
|
267
|
+
Model adapters lazy-import their provider SDK, so install the ones you
|
|
268
|
+
use: `pip install admina-framework[adapters]` for all of them, or a
|
|
269
|
+
single provider (`[openai]`, `[ollama]`, `[anthropic]`, `[mistral]`,
|
|
270
|
+
`[gemini]`, `[bedrock]`). vLLM has no extra of its own — it serves an
|
|
271
|
+
OpenAI-compatible API and subclasses the OpenAI adapter, so install
|
|
272
|
+
`[openai]` for it.
|
|
273
|
+
|
|
267
274
|
```bash
|
|
268
275
|
admina plugin list # List registered plugins
|
|
269
276
|
admina plugin install ./my-plugin # Install a custom plugin
|
|
@@ -20,6 +20,7 @@ Entry point: ``admina = "cli.main:app"`` in pyproject.toml.
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
22
|
import hashlib
|
|
23
|
+
import json
|
|
23
24
|
import logging
|
|
24
25
|
import os
|
|
25
26
|
import shutil
|
|
@@ -1485,6 +1486,171 @@ def password_set(new_password: str) -> None:
|
|
|
1485
1486
|
click.echo(" Restart services to apply: docker compose up --build -d\n")
|
|
1486
1487
|
|
|
1487
1488
|
|
|
1489
|
+
# ── admina egress commands ───────────────────────────────────
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
@app.group()
|
|
1493
|
+
def egress() -> None:
|
|
1494
|
+
"""Inspect and manage the egress policy."""
|
|
1495
|
+
|
|
1496
|
+
|
|
1497
|
+
@egress.command("suggest-allowlist")
|
|
1498
|
+
@click.option(
|
|
1499
|
+
"--since",
|
|
1500
|
+
type=click.IntRange(min=0),
|
|
1501
|
+
default=7,
|
|
1502
|
+
help="Only consider records from the last N days.",
|
|
1503
|
+
)
|
|
1504
|
+
@click.option(
|
|
1505
|
+
"--forensic-dir",
|
|
1506
|
+
default=None,
|
|
1507
|
+
help=(
|
|
1508
|
+
"Directory holding forensic records (the filesystem store's base_dir). "
|
|
1509
|
+
"Defaults to $FORENSIC_BASE_DIR, then .admina/forensic."
|
|
1510
|
+
),
|
|
1511
|
+
)
|
|
1512
|
+
def suggest_allowlist(since: int, forensic_dir: str | None) -> None:
|
|
1513
|
+
"""Print a candidate allowlist from destinations seen in observe mode."""
|
|
1514
|
+
# Same source `admina doctor` reads for the filesystem store; the flag
|
|
1515
|
+
# stays an explicit override.
|
|
1516
|
+
base = Path(forensic_dir or os.environ.get("FORENSIC_BASE_DIR") or ".admina/forensic")
|
|
1517
|
+
cutoff = time.time() - since * 86400
|
|
1518
|
+
seen: set[str] = set()
|
|
1519
|
+
for path in sorted(base.rglob("*.json")) if base.is_dir() else []:
|
|
1520
|
+
try:
|
|
1521
|
+
if path.stat().st_mtime < cutoff:
|
|
1522
|
+
continue
|
|
1523
|
+
record = json.loads(path.read_text())
|
|
1524
|
+
except (OSError, ValueError):
|
|
1525
|
+
continue
|
|
1526
|
+
# Handle both wrapped (ForensicBlackBox) and flat (test) record shapes
|
|
1527
|
+
if not isinstance(record, dict):
|
|
1528
|
+
continue
|
|
1529
|
+
event = record.get("event", record)
|
|
1530
|
+
if not isinstance(event, dict):
|
|
1531
|
+
continue
|
|
1532
|
+
check = event.get("checks") or {}
|
|
1533
|
+
if not isinstance(check, dict):
|
|
1534
|
+
continue
|
|
1535
|
+
egress = check.get("egress") or {}
|
|
1536
|
+
if not isinstance(egress, dict):
|
|
1537
|
+
continue
|
|
1538
|
+
destinations = egress.get("destinations") or []
|
|
1539
|
+
if not isinstance(destinations, list):
|
|
1540
|
+
continue
|
|
1541
|
+
for dest in destinations:
|
|
1542
|
+
if isinstance(dest, str) and dest:
|
|
1543
|
+
seen.add(dest)
|
|
1544
|
+
|
|
1545
|
+
if not seen:
|
|
1546
|
+
click.echo(f"No egress observations found in {base} for the last {since} day(s).")
|
|
1547
|
+
click.echo("Run with ADMINA_EGRESS_MODE=observe to collect them.")
|
|
1548
|
+
click.echo(
|
|
1549
|
+
"Observations are read back from forensic records, and FORENSIC_BACKEND "
|
|
1550
|
+
"defaults to 'memory' — set FORENSIC_BACKEND=filesystem and "
|
|
1551
|
+
"FORENSIC_BASE_DIR for anything to be written."
|
|
1552
|
+
)
|
|
1553
|
+
return
|
|
1554
|
+
|
|
1555
|
+
click.echo("# Candidate allowlist from observed traffic.")
|
|
1556
|
+
click.echo("# Review every entry before promoting it: observation records what")
|
|
1557
|
+
click.echo("# happened, not what should be allowed.")
|
|
1558
|
+
click.echo("domains:")
|
|
1559
|
+
click.echo(" agent_security:")
|
|
1560
|
+
click.echo(" egress:")
|
|
1561
|
+
click.echo(" allow:")
|
|
1562
|
+
for dest in sorted(seen):
|
|
1563
|
+
click.echo(f" - {dest}")
|
|
1564
|
+
|
|
1565
|
+
|
|
1566
|
+
def _quarantine_redis(url: str):
|
|
1567
|
+
"""Open an async Redis client. Isolated so tests can substitute it."""
|
|
1568
|
+
import redis.asyncio as aioredis
|
|
1569
|
+
|
|
1570
|
+
return aioredis.from_url(url, decode_responses=True)
|
|
1571
|
+
|
|
1572
|
+
|
|
1573
|
+
@egress.group()
|
|
1574
|
+
def quarantine() -> None:
|
|
1575
|
+
"""Inspect and clear quarantined destinations."""
|
|
1576
|
+
|
|
1577
|
+
|
|
1578
|
+
@quarantine.command("list")
|
|
1579
|
+
@click.option(
|
|
1580
|
+
"--redis-url",
|
|
1581
|
+
default=lambda: os.environ.get("REDIS_URL", "redis://localhost:6379/0"),
|
|
1582
|
+
)
|
|
1583
|
+
def quarantine_list(redis_url: str) -> None:
|
|
1584
|
+
"""Show destinations currently refused for payload-bearing calls."""
|
|
1585
|
+
import asyncio
|
|
1586
|
+
import time
|
|
1587
|
+
|
|
1588
|
+
from admina.domains.agent_security.coordination import QuarantineStore
|
|
1589
|
+
|
|
1590
|
+
async def _run():
|
|
1591
|
+
client = _quarantine_redis(redis_url)
|
|
1592
|
+
try:
|
|
1593
|
+
store = QuarantineStore(client, ttl_seconds=1)
|
|
1594
|
+
return await store.current_raising(time.time())
|
|
1595
|
+
finally:
|
|
1596
|
+
await client.close()
|
|
1597
|
+
|
|
1598
|
+
try:
|
|
1599
|
+
live = sorted(asyncio.run(_run()))
|
|
1600
|
+
except Exception as exc: # noqa: BLE001
|
|
1601
|
+
click.echo(f"Could not read quarantine set: {exc}")
|
|
1602
|
+
raise SystemExit(1)
|
|
1603
|
+
|
|
1604
|
+
if not live:
|
|
1605
|
+
click.echo("No destination is currently quarantined.")
|
|
1606
|
+
return
|
|
1607
|
+
click.echo("Quarantined for writes (reads are unaffected):")
|
|
1608
|
+
for destination in live:
|
|
1609
|
+
click.echo(f" - {destination}")
|
|
1610
|
+
click.echo("")
|
|
1611
|
+
click.echo(
|
|
1612
|
+
"Write-shaped calls to these are refused only under ADMINA_EGRESS_MODE=enforce; "
|
|
1613
|
+
"under the default observe mode they are recorded and allowed."
|
|
1614
|
+
)
|
|
1615
|
+
|
|
1616
|
+
|
|
1617
|
+
@quarantine.command("lift")
|
|
1618
|
+
@click.argument("destination")
|
|
1619
|
+
@click.option(
|
|
1620
|
+
"--redis-url",
|
|
1621
|
+
default=lambda: os.environ.get("REDIS_URL", "redis://localhost:6379/0"),
|
|
1622
|
+
)
|
|
1623
|
+
def quarantine_lift(destination: str, redis_url: str) -> None:
|
|
1624
|
+
"""Clear one destination's quarantine."""
|
|
1625
|
+
import asyncio
|
|
1626
|
+
|
|
1627
|
+
from admina.domains.agent_security.coordination import QuarantineStore
|
|
1628
|
+
|
|
1629
|
+
# Destinations are stored as the egress analyser normalised them, which
|
|
1630
|
+
# lower-cases every host. An operator reading WIKI.CORP off a dashboard
|
|
1631
|
+
# would otherwise be told it was not quarantined.
|
|
1632
|
+
destination = destination.strip().lower()
|
|
1633
|
+
|
|
1634
|
+
async def _run():
|
|
1635
|
+
client = _quarantine_redis(redis_url)
|
|
1636
|
+
try:
|
|
1637
|
+
store = QuarantineStore(client, ttl_seconds=1)
|
|
1638
|
+
return await store.lift_raising(destination)
|
|
1639
|
+
finally:
|
|
1640
|
+
await client.close()
|
|
1641
|
+
|
|
1642
|
+
try:
|
|
1643
|
+
lifted = asyncio.run(_run())
|
|
1644
|
+
except Exception as exc: # noqa: BLE001
|
|
1645
|
+
click.echo(f"Could not reach quarantine store: {exc}")
|
|
1646
|
+
raise SystemExit(1)
|
|
1647
|
+
|
|
1648
|
+
if lifted:
|
|
1649
|
+
click.echo(f"Lifted quarantine on {destination}.")
|
|
1650
|
+
else:
|
|
1651
|
+
click.echo(f"{destination} was not quarantined.")
|
|
1652
|
+
|
|
1653
|
+
|
|
1488
1654
|
@app.command()
|
|
1489
1655
|
@click.option(
|
|
1490
1656
|
"--output",
|
|
@@ -152,6 +152,20 @@ class LoopBreakerConfig:
|
|
|
152
152
|
max_consecutive: int = 3
|
|
153
153
|
|
|
154
154
|
|
|
155
|
+
@dataclass
|
|
156
|
+
class EgressConfig:
|
|
157
|
+
"""Destination-based egress control settings."""
|
|
158
|
+
|
|
159
|
+
enabled: bool = True
|
|
160
|
+
allow: list[str] = field(default_factory=list)
|
|
161
|
+
read_only_tools: list[str] = field(default_factory=list)
|
|
162
|
+
# Parsed now, consumed by the coordination detector.
|
|
163
|
+
coordination_declared: list[str] = field(default_factory=list)
|
|
164
|
+
fanin_window_seconds: int = 3600
|
|
165
|
+
fanin_min_agents: int = 5
|
|
166
|
+
quarantine_ttl_seconds: int = 86400
|
|
167
|
+
|
|
168
|
+
|
|
155
169
|
@dataclass
|
|
156
170
|
class AgentSecurityConfig:
|
|
157
171
|
"""Agent-security domain."""
|
|
@@ -160,6 +174,7 @@ class AgentSecurityConfig:
|
|
|
160
174
|
proxy: ProxyConfig = field(default_factory=ProxyConfig)
|
|
161
175
|
firewall: FirewallConfig = field(default_factory=FirewallConfig)
|
|
162
176
|
loop_breaker: LoopBreakerConfig = field(default_factory=LoopBreakerConfig)
|
|
177
|
+
egress: EgressConfig = field(default_factory=EgressConfig)
|
|
163
178
|
|
|
164
179
|
|
|
165
180
|
@dataclass
|
|
@@ -318,9 +333,16 @@ def _build_from_yaml(data: dict[str, Any]) -> AdminaConfig:
|
|
|
318
333
|
|
|
319
334
|
# agent_security
|
|
320
335
|
as_raw = domains.get("agent_security", {})
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
336
|
+
# ``or {}``: a section written with nothing under it ("egress:") parses
|
|
337
|
+
# as None, and the next .get() would raise. For egress that exception is
|
|
338
|
+
# swallowed by the factory's broad except and downgraded to an empty
|
|
339
|
+
# allowlist, which under enforce blocks every destination with no sign of
|
|
340
|
+
# why; for the other three it is an outright crash.
|
|
341
|
+
px_raw = as_raw.get("proxy") or {}
|
|
342
|
+
fw_raw = as_raw.get("firewall") or {}
|
|
343
|
+
lb_raw = as_raw.get("loop_breaker") or {}
|
|
344
|
+
eg_raw = as_raw.get("egress") or {}
|
|
345
|
+
eg_fanin = eg_raw.get("fanin", {}) or {}
|
|
324
346
|
agent_sec = AgentSecurityConfig(
|
|
325
347
|
enabled=as_raw.get("enabled", True),
|
|
326
348
|
proxy=ProxyConfig(
|
|
@@ -339,6 +361,15 @@ def _build_from_yaml(data: dict[str, Any]) -> AdminaConfig:
|
|
|
339
361
|
similarity_threshold=lb_raw.get("similarity_threshold", 0.85),
|
|
340
362
|
max_consecutive=lb_raw.get("max_consecutive", 3),
|
|
341
363
|
),
|
|
364
|
+
egress=EgressConfig(
|
|
365
|
+
enabled=eg_raw.get("enabled", True),
|
|
366
|
+
allow=list(eg_raw.get("allow") or []),
|
|
367
|
+
read_only_tools=list(eg_raw.get("read_only_tools") or []),
|
|
368
|
+
coordination_declared=list(eg_raw.get("coordination_declared") or []),
|
|
369
|
+
fanin_window_seconds=int(eg_fanin.get("window_seconds", 3600)),
|
|
370
|
+
fanin_min_agents=int(eg_fanin.get("min_agents", 5)),
|
|
371
|
+
quarantine_ttl_seconds=int(eg_raw.get("quarantine_ttl_seconds", 86400)),
|
|
372
|
+
),
|
|
342
373
|
)
|
|
343
374
|
|
|
344
375
|
# compliance
|