admina 0.14.0__tar.gz → 0.14.2__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-0.14.0/admina.egg-info → admina-0.14.2}/PKG-INFO +1 -1
- {admina-0.14.0 → admina-0.14.2}/admina/__init__.py +1 -1
- admina-0.14.2/admina/domains/data_sovereignty/names.py +70 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/pii.py +5 -0
- {admina-0.14.0 → admina-0.14.2}/admina/engines/presidio.py +3 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/pii/spacy_regex.py +3 -0
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/baselines/baseline.json +6 -6
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/corpora/SHA256SUMS +1 -1
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/corpora/pii.jsonl +8 -0
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/report.py +22 -2
- {admina-0.14.0 → admina-0.14.2/admina.egg-info}/PKG-INFO +1 -1
- {admina-0.14.0 → admina-0.14.2}/admina.egg-info/SOURCES.txt +2 -0
- {admina-0.14.0 → admina-0.14.2}/pyproject.toml +1 -1
- admina-0.14.2/tests/test_person_names.py +122 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_pipeline_executor.py +8 -7
- {admina-0.14.0 → admina-0.14.2}/tests/test_presidio_engine.py +14 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_redteam_lib.py +12 -0
- {admina-0.14.0 → admina-0.14.2}/LICENSE +0 -0
- {admina-0.14.0 → admina-0.14.2}/NOTICE +0 -0
- {admina-0.14.0 → admina-0.14.2}/README.md +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/commands/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/forensic.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/main.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/redteam.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/admina.yaml.j2 +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/docker-compose.yml.j2 +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/env.j2 +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/main.py.j2 +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/plugin.py.j2 +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/plugin_pyproject.toml.j2 +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/plugin_readme.md.j2 +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/cli/templates/plugin_test.py.j2 +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/core/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/core/config.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/core/config_schema.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/core/event_bus.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/core/exception_log.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/core/jcs.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/core/offline.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/core/secretfile.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/core/secrets.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/core/trace_context.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/core/types.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/dashboard/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/dashboard/static/heimdall.png +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/dashboard/static/index.html +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/dashboard/static/vendor/alpinejs.min.js +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/coordination.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/egress.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/fingerprint.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/firewall.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/loop_breaker.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/pattern_packs.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/pattern_timing.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/ruleset.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/agent_security/scan_policy.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/ai_infra/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/ai_infra/llm_engine.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/ai_infra/rag.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/ai_infra/webui.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/ai_act_terms.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/cross_regulation.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/eu_ai_act.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/forensic.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/forensic_files.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/forensic_integrity.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/gdpr.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/nis2.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/oisg.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/oisg_evidence.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/otel.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/compliance/schemas/oisg-evidence.schema.json +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/classification.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/email_matching.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/iban.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/masking.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/data_sovereignty/residency.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/domains/governance.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/engines/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/engines/pii_plugins.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/integrations/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/integrations/_engines.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/integrations/cheshirecat/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/integrations/cheshirecat/admina-plugin/admina_governance.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/integrations/crewai/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/integrations/crewai/callbacks.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/integrations/langchain/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/integrations/langchain/callbacks.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/integrations/n8n/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/integrations/openclaw/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/base.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/_streaming.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/anthropic.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/bedrock.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/gemini.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/mistral.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/ollama.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/openai.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/adapters/vllm.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/alerts/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/alerts/log.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/alerts/webhook.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/auth/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/auth/apikey.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/compliance/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/compliance/eu_ai_act.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/connectors/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/connectors/chromadb.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/connectors/filesystem.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/forensic/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/forensic/filesystem.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/guards/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/guards/guardrailsai_guard.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/pii/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/transports/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/transports/http_rest.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/builtin/transports/mcp.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/plugins/registry.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/api/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/api/dashboard.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/api/gateway.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/api/integration.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/body_limit.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/config.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/dashboard_session.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/decisions.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/engine_bridge.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/engine_report.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/env_check.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/forensic_backend.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/forensic_probe.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_body.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_correlation.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_outcome.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_response_scan.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_scan.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_transport.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/gateway_upstreams.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/governance.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/log_format.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/loop_lag.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/main.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/multi_upstream.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/pipeline_executor.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/request_metrics.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/state.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/proxy/surfaces.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/py.typed +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/corpora/coordination.jsonl +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/corpora/injection.jsonl +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/corpora/loop.jsonl +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/corpora.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/detectors.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/gate.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/metrics.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/redteam/runner.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/sdk/__init__.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/sdk/_compat.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/sdk/compliance_kit.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/sdk/errors.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/sdk/governed_agent.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/sdk/governed_data.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/sdk/governed_model.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/sdk/retry.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina/sdk/streaming.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina.egg-info/dependency_links.txt +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina.egg-info/entry_points.txt +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina.egg-info/requires.txt +0 -0
- {admina-0.14.0 → admina-0.14.2}/admina.egg-info/top_level.txt +0 -0
- {admina-0.14.0 → admina-0.14.2}/setup.cfg +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_admina_config_path.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_admina_framework_sdist.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_ai_act_terms.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_audit_api_source.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_auth_middleware_errors.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_banner_engine_info.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_benchmark_14us.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_bus_event_metadata.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_canary_no_content.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_check_versions.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_cli_forensic_export.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_cli_redteam.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_config_schema_validation.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_coordination.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_coordination_cli.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_coordination_corpus.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_coordination_wiring.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_core_config.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_dashboard_container.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_data_classifier_restricted.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_domains.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_domains_governance.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_egress_analyze.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_egress_cli.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_egress_pipeline.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_egress_policy.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_egress_surfaces.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_enabled_surfaces.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_engine_effective.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_engine_startup_errors.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_engines.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_entrypoint_script.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_error_disclosure.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_event_loop_lag_metric.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_fingerprint.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_heuristic_config.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_it_baseline.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_it_benign.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_parity.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_pattern_ids.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_pattern_packs.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_firewall_pattern_timing.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_atomic_write.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_blackbox.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_config_coherence.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_fail_mode.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_gateway_records.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_incremental_verify.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_integrity.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_rebuilt_and_duplicates.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_record_signature.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_restart.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_s3_reads.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_forensic_volume.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_block_status.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_config.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_correlation.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_endpoints.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_errors.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_forward_body.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_governed_stream.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_guard_fail_mode.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_helpers.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_latency_bench.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_mount.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_outcome_headers.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_passthrough.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_pii_contract.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_ruleset_endpoint.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_scan_coverage.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_upstream_auth.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_gateway_upstream_routes.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_governance_pipeline.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_guard_fail_mode.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_health_fields.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_health_forensic_probe.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_iban_it.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_jcs.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_lazy_imports.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_log_format.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_mcp_block_reason.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_mcp_transport.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_metrics_docs_auth.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_metrics_gateway.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_model_allowlist_post.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_offline_guarantee.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_oisg_evidence.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_otel_enabled_flag.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_pattern_probe.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_pii_email_matching.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_pii_mask_style.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_pii_plugin_engines.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_dashboard_session.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_forensic_would_action.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_governance_mode_config.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_guard_fail_mode.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_mcp_response_pii.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_metrics_endpoint.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_minimal_extra.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_plugins.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_proxy_security.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_query_api_key_deprecation.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_record_decision.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_redaction_values_only.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_redteam_efficacy.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_redteam_external_corpora.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_release_workflows.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_request_sha256.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_request_size_cap.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_response_scan_option.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_ruleset_parity.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_ruleset_sha256.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_scan_depth.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_scan_policy.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_secret_files.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_unknown_admina_env.py +0 -0
- {admina-0.14.0 → admina-0.14.2}/tests/test_version.py +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Copyright © 2025–2026 Stefano Noferi & Admina contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
"""Whether a PERSON entity of an NER model reads as a name.
|
|
17
|
+
|
|
18
|
+
The PII engines mask a PERSON entity only when :func:`is_name_like` holds:
|
|
19
|
+
no digit or character that a name does not hold (``@ / \\ _ # : =``, as in
|
|
20
|
+
an e-mail address or a path), and no lowercase word that is a stop word of English, Italian,
|
|
21
|
+
German, French, Spanish or Portuguese (the stop-word lists of spaCy, without
|
|
22
|
+
the words that are also first names, such as "will", "may" and "sara"). The
|
|
23
|
+
English spaCy model labels phrases as PERSON ("il codice articolo", "ci
|
|
24
|
+
vediamo domani", "das Wetter", "grab a coffee"); they hold such words.
|
|
25
|
+
Names written in lowercase pass ("mario rossi"), and capitalised words are
|
|
26
|
+
not checked ("Sara", "Il Signore").
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import functools
|
|
32
|
+
import importlib
|
|
33
|
+
|
|
34
|
+
# The stop-word lists of spaCy whose lowercase words reject a PERSON span.
|
|
35
|
+
_STOP_WORD_LANGUAGES = ("en", "it", "de", "fr", "es", "pt")
|
|
36
|
+
|
|
37
|
+
# Characters of e-mail addresses, paths and identifiers, not of names.
|
|
38
|
+
_NOT_IN_NAMES = frozenset("@/\\_#:=")
|
|
39
|
+
|
|
40
|
+
# Stripped from both ends of a word before the comparison.
|
|
41
|
+
_PUNCTUATION = ".,;:!?\"'()[]«»“”‘’"
|
|
42
|
+
|
|
43
|
+
# Words of those lists that are also first names.
|
|
44
|
+
_FIRST_NAMES = frozenset({"ali", "may", "mia", "sara", "will"})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@functools.cache
|
|
48
|
+
def _stop_words() -> frozenset[str]:
|
|
49
|
+
"""The stop words of :data:`_STOP_WORD_LANGUAGES`, without
|
|
50
|
+
:data:`_FIRST_NAMES`; empty without spaCy (no NER runs then)."""
|
|
51
|
+
words: set[str] = set()
|
|
52
|
+
for lang in _STOP_WORD_LANGUAGES:
|
|
53
|
+
try:
|
|
54
|
+
module = importlib.import_module(f"spacy.lang.{lang}.stop_words")
|
|
55
|
+
except ImportError:
|
|
56
|
+
return frozenset()
|
|
57
|
+
words |= module.STOP_WORDS
|
|
58
|
+
return frozenset(words - _FIRST_NAMES)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def is_name_like(text: str) -> bool:
|
|
62
|
+
"""True when *text* has a word, no digit nor character of
|
|
63
|
+
:data:`_NOT_IN_NAMES`, and no lowercase word that is a stop word of
|
|
64
|
+
English, Italian, German, French, Spanish or Portuguese."""
|
|
65
|
+
words = text.split()
|
|
66
|
+
if not words or any(ch.isdigit() or ch in _NOT_IN_NAMES for ch in text):
|
|
67
|
+
return False
|
|
68
|
+
stop = _stop_words()
|
|
69
|
+
bare = (word.strip(_PUNCTUATION) for word in words)
|
|
70
|
+
return not any(word in stop for word in bare if word.islower())
|
|
@@ -30,6 +30,7 @@ from admina.domains.data_sovereignty.masking import (
|
|
|
30
30
|
placeholder_spans,
|
|
31
31
|
replace_spans,
|
|
32
32
|
)
|
|
33
|
+
from admina.domains.data_sovereignty.names import is_name_like
|
|
33
34
|
|
|
34
35
|
# spaCy is part of the [nlp] extra. When absent, PIIRedactor falls back
|
|
35
36
|
# to regex-only mode (still covers EMAIL/PHONE/SSN/IBAN/IP/credit-card/EU IDs).
|
|
@@ -301,6 +302,10 @@ class PIIRedactor:
|
|
|
301
302
|
cat_config = active_categories.get(ent.label_, {})
|
|
302
303
|
if not cat_config.get("enabled", False):
|
|
303
304
|
continue
|
|
305
|
+
if ent.label_ == "PERSON" and not is_name_like(
|
|
306
|
+
redacted[ent.start_char : ent.end_char]
|
|
307
|
+
):
|
|
308
|
+
continue
|
|
304
309
|
mask = self._mask(ent.label_, cat_config)
|
|
305
310
|
parts = outside_placeholders(ent.start_char, ent.end_char, placeholders, redacted)
|
|
306
311
|
for start, end in reversed(parts):
|
|
@@ -55,6 +55,7 @@ from admina.domains.data_sovereignty.masking import (
|
|
|
55
55
|
placeholder_spans,
|
|
56
56
|
replace_spans,
|
|
57
57
|
)
|
|
58
|
+
from admina.domains.data_sovereignty.names import is_name_like
|
|
58
59
|
from admina.domains.data_sovereignty.pii import PII_CATEGORIES
|
|
59
60
|
|
|
60
61
|
logger = logging.getLogger("admina.engines.presidio")
|
|
@@ -272,6 +273,8 @@ class PresidioPIIEngine:
|
|
|
272
273
|
cfg = PII_CATEGORIES.get(category, {})
|
|
273
274
|
if not cfg.get("enabled", False):
|
|
274
275
|
continue
|
|
276
|
+
if category == "PERSON" and not is_name_like(text[r.start : r.end]):
|
|
277
|
+
continue
|
|
275
278
|
mask = OMISSIS if self.mask_style == "omissis" else cfg.get("mask", f"[{category}]")
|
|
276
279
|
for start, end in outside_placeholders(r.start, r.end, placeholders, text):
|
|
277
280
|
spans.append((start, end, category, mask))
|
|
@@ -27,6 +27,7 @@ from typing import Any
|
|
|
27
27
|
|
|
28
28
|
from admina.domains.data_sovereignty.email_matching import EMAIL_RX, iter_email_matches
|
|
29
29
|
from admina.domains.data_sovereignty.masking import replace_spans
|
|
30
|
+
from admina.domains.data_sovereignty.names import is_name_like
|
|
30
31
|
from admina.plugins.base import BasePIIEngine
|
|
31
32
|
|
|
32
33
|
logger = logging.getLogger("admina.plugins.pii.spacy_regex")
|
|
@@ -125,6 +126,8 @@ class SpaCyRegexPIIEngine(BasePIIEngine):
|
|
|
125
126
|
continue
|
|
126
127
|
if allowed and ent.label_ not in allowed:
|
|
127
128
|
continue
|
|
129
|
+
if ent.label_ == "PERSON" and not is_name_like(ent.text):
|
|
130
|
+
continue
|
|
128
131
|
# Skip if already covered by a regex match (full interval-overlap test)
|
|
129
132
|
overlap = any(
|
|
130
133
|
not (ent.end_char <= mm["start"] or ent.start_char >= mm["end"])
|
|
@@ -14,19 +14,19 @@
|
|
|
14
14
|
"pii": {
|
|
15
15
|
"python": {
|
|
16
16
|
"type_recall": 1.0,
|
|
17
|
-
"fp":
|
|
18
|
-
"fp_samples":
|
|
17
|
+
"fp": 3,
|
|
18
|
+
"fp_samples": 24,
|
|
19
19
|
"mode": "nlp:en_core_web_sm@3.8.0"
|
|
20
20
|
},
|
|
21
21
|
"rust": {
|
|
22
22
|
"type_recall": 0.6551724137931034,
|
|
23
23
|
"fp": 0,
|
|
24
|
-
"fp_samples":
|
|
24
|
+
"fp_samples": 24
|
|
25
25
|
},
|
|
26
26
|
"presidio": {
|
|
27
|
-
"type_recall": 0.
|
|
27
|
+
"type_recall": 0.5862068965517241,
|
|
28
28
|
"fp": 9,
|
|
29
|
-
"fp_samples":
|
|
29
|
+
"fp_samples": 24,
|
|
30
30
|
"mode": "presidio:2.2.363/en+it"
|
|
31
31
|
}
|
|
32
32
|
},
|
|
@@ -42,4 +42,4 @@
|
|
|
42
42
|
"fp_samples": 11
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
}
|
|
45
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
5b6904c941e71ca6fee0fcab722124ec1f9b17348648daee678d435ff940566b injection.jsonl
|
|
2
|
-
|
|
2
|
+
902f6ada48bfca0aa79cfd0a156b90f5dbfaa6930eafd0de7ee90630e24e2371 pii.jsonl
|
|
3
3
|
df6727b94272ab72897acd5f71e883ff52711038286b55dfaa026becdc193ab3 loop.jsonl
|
|
4
4
|
ece92830693b29ac5246f95754bf704092473cf2602bd970355e85a0358bc0d5 coordination.jsonl
|
|
@@ -40,3 +40,11 @@
|
|
|
40
40
|
{"id":"pii-neg-014","text":"nos vemos luego para tomar algo","expected_types":[],"lang":"es","tag":"negative"}
|
|
41
41
|
{"id":"pii-neg-015","text":"das Wetter ist heute wirklich schön","expected_types":[],"lang":"de","tag":"negative"}
|
|
42
42
|
{"id":"pii-neg-016","text":"wir sehen uns morgen im Büro","expected_types":[],"lang":"de","tag":"negative"}
|
|
43
|
+
{"id":"pii-hneg-001","text":"order 4111 1111 1111 1112 was cancelled yesterday","expected_types":[],"lang":"en","tag":"hard_negative"}
|
|
44
|
+
{"id":"pii-hneg-002","text":"upgrade the library to version 2.14.3 before Friday","expected_types":[],"lang":"en","tag":"hard_negative"}
|
|
45
|
+
{"id":"pii-hneg-003","text":"invoice INV-2026-00123 is due on 2026-10-31","expected_types":[],"lang":"en","tag":"hard_negative"}
|
|
46
|
+
{"id":"pii-hneg-004","text":"ISBN 978-3-16-148410-0 is out of print","expected_types":[],"lang":"en","tag":"hard_negative"}
|
|
47
|
+
{"id":"pii-hneg-005","text":"build 20261008.1 passed all 4554 checks","expected_types":[],"lang":"en","tag":"hard_negative"}
|
|
48
|
+
{"id":"pii-hneg-006","text":"room 4B on floor 12, chapter 3.14 of the manual","expected_types":[],"lang":"en","tag":"hard_negative"}
|
|
49
|
+
{"id":"pii-hneg-007","text":"il codice articolo AB-12345-CD è esaurito in magazzino","expected_types":[],"lang":"it","tag":"hard_negative"}
|
|
50
|
+
{"id":"pii-hneg-008","text":"la pratica n. 2026/000457 è stata chiusa il 3 ottobre","expected_types":[],"lang":"it","tag":"hard_negative"}
|
|
@@ -123,8 +123,23 @@ def _pct(x: float | None) -> str:
|
|
|
123
123
|
return "n/a" if x is None else f"{round(x * 100)}%"
|
|
124
124
|
|
|
125
125
|
|
|
126
|
+
def _hits(metric: dict) -> tuple[int, int]:
|
|
127
|
+
"""(detected, expected) behind the recall of *metric*: samples for the
|
|
128
|
+
binary detectors, PII types for the PII detector."""
|
|
129
|
+
c = metric["counts"]
|
|
130
|
+
if "tp" in c:
|
|
131
|
+
return c["tp"], c["tp"] + c["fn"]
|
|
132
|
+
return c["intersect_total"], c["expected_total"]
|
|
133
|
+
|
|
134
|
+
|
|
126
135
|
def _cell_recall(metric: dict | None) -> str:
|
|
127
|
-
|
|
136
|
+
if not metric:
|
|
137
|
+
return "-"
|
|
138
|
+
value = recall_item(metric)[1]
|
|
139
|
+
if value is None:
|
|
140
|
+
return _pct(value)
|
|
141
|
+
detected, expected = _hits(metric)
|
|
142
|
+
return f"{_pct(value)} ({detected}/{expected})"
|
|
128
143
|
|
|
129
144
|
|
|
130
145
|
def to_markdown(scorecard: dict) -> str:
|
|
@@ -144,7 +159,12 @@ def to_markdown(scorecard: dict) -> str:
|
|
|
144
159
|
lines.append(
|
|
145
160
|
f"| {det} | {tag} | {_cell_recall(py)} | {_cell_recall(rs)} | {_cell_recall(pr)} |"
|
|
146
161
|
)
|
|
147
|
-
footnotes = [
|
|
162
|
+
footnotes = [
|
|
163
|
+
"_Each cell is the recall with the detected and expected counts. A class "
|
|
164
|
+
"often has one to three samples, so the figures are indicative, not a "
|
|
165
|
+
"benchmark. The labels of the corpora are assigned in this repository, "
|
|
166
|
+
"not adjudicated by a third party._"
|
|
167
|
+
]
|
|
148
168
|
external = scorecard.get("external_corpora")
|
|
149
169
|
detector_of = external["corpora"] if external else {}
|
|
150
170
|
if any(detector_of.get(name, name) == "pii" for name in scorecard["detectors"]):
|
|
@@ -72,6 +72,7 @@ admina/domains/data_sovereignty/classification.py
|
|
|
72
72
|
admina/domains/data_sovereignty/email_matching.py
|
|
73
73
|
admina/domains/data_sovereignty/iban.py
|
|
74
74
|
admina/domains/data_sovereignty/masking.py
|
|
75
|
+
admina/domains/data_sovereignty/names.py
|
|
75
76
|
admina/domains/data_sovereignty/pii.py
|
|
76
77
|
admina/domains/data_sovereignty/residency.py
|
|
77
78
|
admina/engines/__init__.py
|
|
@@ -260,6 +261,7 @@ tests/test_offline_guarantee.py
|
|
|
260
261
|
tests/test_oisg_evidence.py
|
|
261
262
|
tests/test_otel_enabled_flag.py
|
|
262
263
|
tests/test_pattern_probe.py
|
|
264
|
+
tests/test_person_names.py
|
|
263
265
|
tests/test_pii_email_matching.py
|
|
264
266
|
tests/test_pii_mask_style.py
|
|
265
267
|
tests/test_pii_plugin_engines.py
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Copyright © 2025–2026 Stefano Noferi & Admina contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
"""A PERSON entity of an NER model is masked only when it reads as a name:
|
|
17
|
+
no digit, and no lowercase word that is a stop word of English, Italian,
|
|
18
|
+
German, French, Spanish or Portuguese.
|
|
19
|
+
|
|
20
|
+
The English spaCy model labels phrases as PERSON ("il codice articolo",
|
|
21
|
+
"ci vediamo domani", "das Wetter", "grab a coffee"); such spans are not
|
|
22
|
+
masked, by the PII redactor, the spaCy + regex engine and the Presidio
|
|
23
|
+
engine. Names written in lowercase are masked.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import asyncio
|
|
29
|
+
from types import SimpleNamespace
|
|
30
|
+
|
|
31
|
+
import pytest
|
|
32
|
+
|
|
33
|
+
from admina.domains.data_sovereignty.names import is_name_like
|
|
34
|
+
from admina.domains.data_sovereignty.pii import PIIRedactor
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.parametrize(
|
|
38
|
+
"text",
|
|
39
|
+
[
|
|
40
|
+
"Mario Rossi",
|
|
41
|
+
"mario rossi",
|
|
42
|
+
"john smith",
|
|
43
|
+
"giovanni esposito",
|
|
44
|
+
"O'Brien",
|
|
45
|
+
"JOHN",
|
|
46
|
+
"Sara Rossi",
|
|
47
|
+
"sara rossi", # "sara" is an Italian stop word, kept as a name
|
|
48
|
+
"Mia",
|
|
49
|
+
"will smith", # "will" is an English stop word, kept as a name
|
|
50
|
+
"Il Signore", # capitalised words are not checked
|
|
51
|
+
],
|
|
52
|
+
)
|
|
53
|
+
def test_names(text):
|
|
54
|
+
assert is_name_like(text)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.parametrize(
|
|
58
|
+
"text",
|
|
59
|
+
[
|
|
60
|
+
"il codice articolo",
|
|
61
|
+
"la pratica n. 2026/000457",
|
|
62
|
+
"ci vediamo domani",
|
|
63
|
+
"ci vediamo, domani",
|
|
64
|
+
"(il) progetto",
|
|
65
|
+
"ho parlato",
|
|
66
|
+
"das Wetter",
|
|
67
|
+
"il cliente",
|
|
68
|
+
"grab a coffee",
|
|
69
|
+
"email john.doe@example.org",
|
|
70
|
+
"Mario/Rossi",
|
|
71
|
+
"John 3",
|
|
72
|
+
"",
|
|
73
|
+
" ",
|
|
74
|
+
],
|
|
75
|
+
)
|
|
76
|
+
def test_not_names(text):
|
|
77
|
+
assert not is_name_like(text)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class _Nlp:
|
|
81
|
+
"""Labels each given phrase of the text as PERSON."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, *phrases: str) -> None:
|
|
84
|
+
self.phrases = phrases
|
|
85
|
+
|
|
86
|
+
def __call__(self, text: str) -> SimpleNamespace:
|
|
87
|
+
ents = []
|
|
88
|
+
for phrase in self.phrases:
|
|
89
|
+
start = text.find(phrase)
|
|
90
|
+
if start >= 0:
|
|
91
|
+
ents.append(
|
|
92
|
+
SimpleNamespace(
|
|
93
|
+
start_char=start,
|
|
94
|
+
end_char=start + len(phrase),
|
|
95
|
+
text=phrase,
|
|
96
|
+
label_="PERSON",
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
return SimpleNamespace(ents=ents)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_the_redactor_masks_names_only():
|
|
103
|
+
redactor = PIIRedactor()
|
|
104
|
+
redactor.nlp = _Nlp("il codice articolo", "Mario Rossi")
|
|
105
|
+
out = redactor.redact("il codice articolo AB-12 è di Mario Rossi")
|
|
106
|
+
assert out["redacted_text"] == "il codice articolo AB-12 è di [PERSON]"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_the_redactor_masks_lowercase_names():
|
|
110
|
+
redactor = PIIRedactor()
|
|
111
|
+
redactor.nlp = _Nlp("il cliente", "giovanni esposito")
|
|
112
|
+
out = redactor.redact("il cliente è giovanni esposito")
|
|
113
|
+
assert out["redacted_text"] == "il cliente è [PERSON]"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_the_spacy_regex_engine_reports_names_only():
|
|
117
|
+
from admina.plugins.builtin.pii.spacy_regex import SpaCyRegexPIIEngine
|
|
118
|
+
|
|
119
|
+
engine = SpaCyRegexPIIEngine()
|
|
120
|
+
engine._nlp, engine._nlp_loaded = _Nlp("il codice articolo", "Mario Rossi"), True
|
|
121
|
+
found = asyncio.run(engine.detect("il codice articolo AB-12 è di Mario Rossi"))
|
|
122
|
+
assert [(m["type"], m["text"]) for m in found] == [("PERSON", "Mario Rossi")]
|
|
@@ -123,13 +123,14 @@ def _blocked(resp: httpx.Response) -> bool:
|
|
|
123
123
|
|
|
124
124
|
# How long the slow job (a firewall check, a redaction) sleeps in the thread
|
|
125
125
|
# that runs it, and the largest event loop stall the tests accept. Run on the
|
|
126
|
-
# event loop,
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
126
|
+
# event loop, or awaited there without yielding, the job stalls the loop for
|
|
127
|
+
# at least its whole duration: a stall shorter than the job means the loop
|
|
128
|
+
# was free. The bound is the job itself, so the test fails for a blocked
|
|
129
|
+
# loop whatever the load, and passes with the loop free unless the scheduler
|
|
130
|
+
# holds it for a whole second (stalls of up to 0.51 s were seen on loaded
|
|
131
|
+
# macOS runners with the loop free).
|
|
132
|
+
SLOW_JOB = 1.0
|
|
133
|
+
MAX_STALL = SLOW_JOB
|
|
133
134
|
|
|
134
135
|
|
|
135
136
|
async def _largest_gap(stop: asyncio.Event) -> float:
|
|
@@ -215,3 +215,17 @@ def test_text_with_many_dots_is_analyzed_quickly():
|
|
|
215
215
|
# Every recognizer of Presidio took about a minute on this text; the
|
|
216
216
|
# mapped ones take a fraction of a second.
|
|
217
217
|
assert time.perf_counter() - start < 5.0
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def test_person_spans_that_are_not_names_are_not_masked(monkeypatch):
|
|
221
|
+
presidio = _presidio()
|
|
222
|
+
from presidio_analyzer import RecognizerResult
|
|
223
|
+
|
|
224
|
+
engine = presidio.PresidioPIIEngine(nlp_models={"it": "blank"})
|
|
225
|
+
text = "il codice articolo AB-12 è di Mario Rossi"
|
|
226
|
+
found = [
|
|
227
|
+
RecognizerResult("PERSON", 0, 18, 0.85), # "il codice articolo"
|
|
228
|
+
RecognizerResult("PERSON", 30, 41, 0.85), # "Mario Rossi"
|
|
229
|
+
]
|
|
230
|
+
monkeypatch.setattr(engine._analyzer, "analyze", lambda text, language, entities: list(found))
|
|
231
|
+
assert engine.redact(text)["redacted_text"] == "il codice articolo AB-12 è di [PERSON]"
|
|
@@ -227,6 +227,18 @@ def test_build_scorecard_and_markdown_have_caveat():
|
|
|
227
227
|
assert "Detector" in md
|
|
228
228
|
|
|
229
229
|
|
|
230
|
+
def test_markdown_cells_show_detected_over_expected():
|
|
231
|
+
results = {
|
|
232
|
+
"injection": {"python": _fake_eval_binary()},
|
|
233
|
+
"pii": {"python": _fake_eval_pii()},
|
|
234
|
+
}
|
|
235
|
+
md = report.to_markdown(report.build_scorecard(results, rust_version=None))
|
|
236
|
+
assert "| injection | leetspeak | 0% (0/1) |" in md
|
|
237
|
+
assert "| pii | email | 100% (4/4) |" in md
|
|
238
|
+
assert "indicative" in md
|
|
239
|
+
assert "not adjudicated by a third party" in md
|
|
240
|
+
|
|
241
|
+
|
|
230
242
|
def test_make_baseline_shape():
|
|
231
243
|
results = {"injection": {"python": _fake_eval_binary()}}
|
|
232
244
|
card = report.build_scorecard(results, rust_version=None)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|