Injection and attack matrix
Français : ../injection-matrix.md
Tests live in services/control-plane/tests/ unless stated otherwise. Notation: file::test.
| Attack | Layer | Control | Settings | Proving test | Residual risk |
|---|---|---|---|---|---|
| SSRF | Tools, MCP, connectors | eai.security.egress.EgressPolicy, check_url, address_verdict; eai.integrations.outbound.validate_url at save and call time | EAI_EGRESS_PRIVATE_ALLOWLIST, EAI_EGRESS_ALLOWED_PORTS, EAI_EGRESS_ALLOW_HTTP | test_security_egress.py::test_static_rejections, test_security_egress_wiring.py::test_tool_save_refused, test_security_egress_wiring.py::test_call_time_upstream_never_called | Too wide private allowlist; the posture flags it |
| DNS rebinding | Tools, MCP | GuardedTransport / GuardedAsyncTransport: connect to the validated address, every DNS answer checked, redirects never followed | EAI_EGRESS_DNS_TIMEOUT_SECONDS | test_security_egress.py::test_dns_rebinding_between_check_and_connect_is_blocked, test_security_egress.py::test_dns_answers_are_all_validated, test_security_egress.py::test_redirects_are_never_followed | HTTP clients outside guarded_client (forbidden in review) |
| DNS tunneling and exfiltration | Network | Cilium DNS L7 and default deny, deploy/cilium/baseline.yaml | Cilium values, network-dns-hardening | test_platform_security_manifests.py::test_default_deny_and_metadata_deny | Allowed names usable as a slow channel |
| ARP and DNS spoofing through NET_RAW | Pods | Dropped capabilities, Kyverno deploy/policies/kyverno/pod-network-security.yaml | pod-security | test_platform_security_manifests.py::test_every_pod_spec_is_hardened, kyverno test deploy/policies/kyverno/tests (cases eai-pod-security/drop-all-capabilities and disallow-added-capabilities, resource net-raw-pod) | Compromised nodes |
| Cloud metadata | Network, application | address_verdict (link-local always refused), Cilium deny policy | no override possible | test_security_egress.py::test_metadata_and_link_local_stay_blocked_even_when_allowlisted, test_platform_security_manifests.py::test_kubernetes_flavour_excludes_metadata | Low |
| SQL injection | Data | Parameterized queries, eai.db.ident, qualified, frag, set_clause, like_pattern, int_limit | none | test_security_sql_static.py::test_no_interpolated_sql_in_control_plane, test_security_sql_inputs.py::test_payloads_never_5xx_or_leak, test_security_sql_inputs.py::test_like_pattern_escapes, test_security_attack_suite.py::test_injection_fuzz | New code outside the static checker |
| ClickHouse injection | Observability | Allowlisted table (eai_finops.usage_events, eai_telemetry.spans) sent only as the typed {table:Identifier} parameter, constant query text, JSONEachRow data in the body, insert_deduplication_token, least privilege eai-writer (INSERT only, prod), read only eai-reader, TLS | EAI_CLICKHOUSE_URL, EAI_CLICKHOUSE_USER, EAI_CLICKHOUSE_TABLE | test_security_clickhouse.py::test_hostile_table_names_refused, test_security_clickhouse.py::test_hostile_env_table_refused, test_security_clickhouse.py::test_invalid_table_refused_before_any_http_request, test_security_clickhouse.py::test_query_text_constant_table_only_in_param, test_security_clickhouse.py::test_hostile_row_values_stay_json_one_line_per_row, test_security_clickhouse.py::test_hostile_dedup_token_refused, test_security_clickhouse.py::test_error_text_bounded_without_credentials, test_security_clickhouse.py::test_redirect_not_followed, test_security_clickhouse.py::test_prod_clickhouse_user_is_least_privilege_writer, test_security_clickhouse.py::test_eai_reader_is_read_only | Low |
| Command injection | Multimodal, red team | eai.integrations.safe_exec.run (argument list, no shell, minimal_env), check, media_input | none | test_security_commands.py::test_payloads_refused, test_security_commands.py::test_run_uses_list_no_shell_minimal_env, test_security_commands.py::test_ffmpeg_refuses_input_specs | ffmpeg own vulnerabilities |
| Template and format-string injection | Agents, prompts | eai.domains.agents.kit.safe_format (declared variables only, no recursion) | none | test_security_inputs.py::test_safe_format_never_evaluates, test_security_inputs.py::test_prompt_render_does_not_expand_attributes, test_security_inputs.py::test_safe_format_linear_time | Low |
| Path traversal | API (contracts, manifests, catalog), media | Name validation in routes (400, 404 or 422), safe_exec.media_input for media | none | test_security_inputs.py::test_path_traversal_rejected | Low |
| YAML and deserialization (billion laughs) | System import | eai.domains.system.safe_yaml (refuses anchors, aliases, oversized documents) | EAI_MAX_BODY_BYTES | test_security_inputs.py::test_yaml_refuses_aliases_anchors_and_big_documents | Low |
| XSS (stored, reflected, DOM) and CSP | Portal | buildCsp with makeNonce in portal/src/middleware.ts, safeHref, escaped React rendering | none | portal/tests/security.test.ts::buildCsp > carries the nonce and strict-dynamic, portal/tests/security.test.ts::buildCsp > never allows eval or inline scripts, portal/tests/security.test.ts::safeHref > refuses %s, portal/tests/zod-csp.test.ts::zod under a no-eval CSP > parses objects without calling the Function constructor | Third party libraries in the portal |
| CSRF | API | Bearer token or API key authentication, no API cookie | EAI_CORS_ORIGINS | test_deploy_ci_evidence.py::test_api_ignores_cookies_no_csrf_surface | None as long as no cookie authenticates the API |
| Clickjacking | Portal, API | frame-ancestors / X-Frame-Options headers (security_headers, SECURITY_HEADERS) | EAI_SECURITY_HEADERS_ENABLED | test_security_http.py::test_headers_on_json_and_error_responses | Low |
| Open redirect | Portal | safeReturnTo | none | portal/tests/security.test.ts::safeReturnTo > falls back for %s, portal/tests/security.test.ts::safeReturnTo > keeps internal paths | Low |
| CRLF and header injection | API | ASGI server refusing CR/LF, header caps (LimitsMiddleware), TrustedHostMiddleware (eai.main) | EAI_ALLOWED_HOSTS | test_security_http.py::test_header_and_query_caps, test_security_http.py::test_trusted_host | Low |
| Log injection | API | Access log as one escaped JSON line, security_event (_one_line) | none | test_security_http.py::test_access_log_is_one_escaped_json_line | Low |
| CSV and formula injection | FinOps, exports | eai.domains.finops.importers.base.neutralize_formula | none | test_security_inputs.py::test_csv_formulas_neutralized, test_security_inputs.py::test_csv_import_keeps_numbers_and_quotes_formulas | Low |
| ReDoS | Injection detection | Bounded input and linear patterns in eai.domains.privacy.injection | none | test_security_prompt_injection.py::test_bounded_input_is_linear | Low |
| Mass assignment | API | Pydantic patch models, set_clause with allowed columns | none | test_security_inputs.py::test_patch_rejects_non_updatable_columns, test_security_inputs.py::test_role_escalation_refused, test_security_attack_suite.py::test_mass_assignment, test_security_attack_suite.py::test_viewer_cannot_mutate | Low |
| IDOR and BOLA | Data | PostgreSQL RLS through eai.db.tx, 404 for another tenant's resource | none | test_security_inputs.py::test_cross_tenant_access_is_404, test_security_attack_suite.py::test_cross_tenant_idor | Admin queries outside RLS |
| Authentication attacks (JWT alg none, HS/RS confusion, kid, expiry) | API | eai.auth: algorithm list, kid required with several keys, rate limited JWKS refresh, leeway; startup guard refusing HS* and none | EAI_JWT_ALGORITHMS, EAI_JWT_LEEWAY_SECONDS | test_security_auth.py::test_alg_none_refused, test_security_auth.py::test_hs256_signed_with_public_key_refused, test_security_auth.py::test_kid_required_with_several_keys, test_security_auth.py::test_claims_enforced, test_security_auth.py::test_unknown_kid_refresh_is_rate_limited | IdP compromise |
| Brute force and rate limiting | API | eai.security.ratelimit: check_principal, record_auth_failure, check_auth_lockout, AnonymousRateLimitMiddleware; WAF | EAI_RATE_LIMIT_ENABLED, EAI_RATE_LIMIT_PER_MINUTE, EAI_RATE_LIMIT_GATEWAY_PER_MINUTE, EAI_RATE_LIMIT_AUTH_FAILURES_PER_MINUTE, EAI_REDIS_URL | test_security_http.py::test_auth_failure_lockout_per_ip, test_security_http.py::test_rate_limit_per_principal, test_security_http.py::test_anonymous_per_ip, test_security_auth.py::test_api_key_format_checked_before_db | Attacks distributed over many IPs |
| Oversized and deeply nested bodies | API | eai.security.limits.LimitsMiddleware, json_depth_exceeds | EAI_MAX_BODY_BYTES, EAI_MAX_UPLOAD_BYTES, EAI_MAX_JSON_DEPTH | test_security_http.py::test_body_cap_content_length_and_chunked, test_security_http.py::test_json_depth, test_security_http.py::test_upload_routes_use_the_upload_cap, test_security_auth.py::test_token_size_cap | Low |
| Prompt injection (direct and indirect) | LLM, agents, RAG | eai.domains.privacy.injection.detect, guard.screen, guard.wrap_messages, guard.blocked_error | EAI_PROMPT_INJECTION_DEFAULT_MODE, tenant prompt_injection_mode, untrusted_content_wrapping | test_security_prompt_injection.py::test_detects_direct_injection_fr_en, test_security_prompt_injection.py::test_detects_obfuscated_injection, test_security_prompt_injection.py::test_indirect_injection_tool_message_and_wrapping, test_security_prompt_injection.py::test_indirect_injection_rag_part, test_security_prompt_injection.py::test_mode_off_detect_block | Heuristic detection can be bypassed; complement with garak and promptfoo |
| Exfiltration through model output (markdown images and links) | LLM | guard.OutputGuard, guard.StreamGuard, guard.host_allowed | tenant output_links, output_link_allowlist | test_security_prompt_injection.py::test_output_guard_rules, test_security_prompt_injection.py::test_stream_guard_split_link, test_security_prompt_injection.py::test_output_links_non_stream_and_stream, test_security_prompt_injection.py::test_detects_exfiltration | allow mode chosen by the tenant |
| System prompt leakage (canary) | LLM | guard.new_canary, guard.add_canary | tenant prompt_injection_mode | test_security_prompt_injection.py::test_canary_leak_detect_and_block | Paraphrase of the prompt without the canary |
| Secret leakage (SDK masking, logs) | SDK, API | eai_*** masking in SDKs (python, go, java), errors without internal details (eai.errors), redacted URLs (outbound._redact) | none | test_security_http.py::test_errors_never_leak_internals, test_security_inputs.py::test_errors_do_not_leak_sql | Secrets pasted by users in a prompt (DLP) |
| Terminal injection in the CLI | CLI | chat and logs strip ANSI sequences and escape control characters (cli) | none | test_security.py::test_terminal_output_is_neutralized, test_security.py::test_cli_chat_neutralizes_escape_sequences (Python SDK) | Low |
| Webhook forgery and replay | Connectors, channels | HMAC signature and timestamp (connectors.service._parse_signature, channels.service._check_signature) | connector secrets | test_capabilities_gateways.py::test_webhook_signature_replay_and_dedupe, test_security_attack_suite.py::test_webhook_signature_required | Leaked webhook secret: rotate it |
| Indirect injection through a tool, MCP or connector result, a memory recall or an A2A message | Tool Gateway, MCP Gateway, connector webhooks, memory, A2A | guard.screen_untrusted before reinjection: detection (injection.detect), tenant prompt_injection_mode (detect or block) and untrusted_content_wrapping, like RAG; events security.prompt_injection.detected (medium or high risk) and security.prompt_injection.blocked | security.prompt_injection_mode, security.untrusted_content_wrapping | test_gateway_pep_untrusted.py::test_untrusted_tool_result, test_gateway_pep_untrusted.py::test_untrusted_mcp_result, test_gateway_pep_untrusted.py::test_untrusted_connector_webhook, test_gateway_pep_untrusted.py::test_untrusted_memory_recall, test_gateway_pep_untrusted.py::test_untrusted_a2a_message | Heuristics: a rephrased injection can pass in detect mode, wrapping stays the defence in depth |
| Gateway action without a policy decision | MCP, knowledge, memory, realtime, connectors, channels, multimodal, A2A, Model Gateway | PEP policy.gate before any outbound call, decision in eai.policy_decisions, 403 policy_denied; OPA package eai.gateway_access (deploy/policies/opa/gateway_access.rego) | tenant policies, data.eai.config.gateway_access | test_gateway_pep_untrusted.py::test_pep_model_gateway_denied, test_gateway_pep_untrusted.py::test_pep_mcp_denied, test_gateway_pep_untrusted.py::test_pep_knowledge_and_memory_denied, test_gateway_pep_untrusted.py::test_pep_realtime_multimodal_denied, test_gateway_pep_untrusted.py::test_pep_connector_channel_a2a_denied, gateway_access_test.rego (opa test) | An approval obligation is refused at synchronous entry points |
| Supply chain (signed images, SBOM, dependency audits) | CI/CD | Jobs trivy-image, sbom-sign, pip-audit, npm-audit (.gitlab-ci.yml), security-deps-python, security-deps-node, security-manifests (.forgejo/workflows/ci.yml), Kyverno | ci-security-gates | test_deploy_ci_evidence.py::test_kyverno_platform_images_need_ci_key | Unknown vulnerabilities (0-day) |