Volver a la documentacióndocs/security/THREAT_MODEL.mdRepositorio privado
Threat model / Modelo de amenazas
Scope: the Centinela platform itself (ingestion, analysis, decision, execution planes, UI, deployment and supply chain). Method: STRIDE per component plus an agent-specific analysis. Every threat lists its mitigations and the test or control that verifies it (test names are real; CI runs them as blocking suites: security.yml → injection-suite, ci.yml).
Assets to protect: customer inventories (metadata), findings and plans, signing keys, approval attestations, the executor's GitHub App credentials, the audit chain, tenant isolation, and — above all — the guarantee that nothing executes without a valid human approval.
1. Agent-specific threats
| # | Threat | Mitigations | Verified by |
|---|---|---|---|
| A1 | Prompt injection via CVE text, advisories, READMEs, resource names, tags, commit messages, tool results | Untrusted wrapper cannot be concatenated into prompts (core); render_untrusted() wraps content in per-request random-boundary blocks and escapes breakouts; system prompts declare block content as data; heuristic + classifier detector logs/alerts; deterministic post-LLM validation independent of the model (closed catalog, tenant asset existence, fixed-version existence, path patterns, no shell text) | packages/core/tests/test_untrusted.py; packages/agent/tests/test_prompting_injection_redaction.py::test_render_untrusted_wraps_and_neutralises_breakouts, ::test_injection_heuristics_flag_known_patterns; packages/agent/tests/test_validator.py::test_validator_rejects_out_of_scope_content, ::test_bump_without_known_fixed_version_rejected; evals injection suite (100 % no out-of-catalog actions) |
| A2 | Tool abuse — model invokes a mutating tool or reads another tenant | the tool registry contains read-only tools only and is frozen; every tool is tenant-scoped; the agent process holds no write credentials (IAM analysis boundary denies the executor queue, KMS Sign restricted to plan-signer) | packages/agent/tests/test_tools_schemas.py; Terraform iam-workload-identities boundaries; Checkov in CI |
| A3 | Exfiltration through the model (secrets/PII in prompts, model output leaking to the world) | inventory holds metadata only (data reads denied by IAM); secret/PII redactor before every prompt; Bedrock via VPC endpoint with no training retention; egress allowlist | test_redaction_removes_secrets_and_pii; packages/connectors IAM simulator tests (policy_sim.py) asserting s3:GetObject, secretsmanager:GetSecretValue, kms:Decrypt, logs:GetLogEvents denied; Network Firewall drop_rest + blocked-egress alarm |
| A4 | Confused deputy — a plan references assets of another tenant or a repository not linked to the finding | validator checks node ownership by tenant and repo↔asset linkage; Postgres RLS; executor re-checks via freshness endpoint scoped to the plan's tenant | test_validator_rejects_out_of_scope_content; packages/graph/tests::test_cross_tenant_isolation; apps/api/tests cross-tenant 404s |
| A5 | Model produces invalid or hallucinated output (versions that do not exist, free-form commands) | structured output with JSON Schema; up to 3 retries then needs_manual_analysis; target versions must exist as fixed versions in the vulnerability source | test_bump_without_known_fixed_version_rejected; packages/agent/tests/test_pipeline.py |
| A6 | Feed poisoning — a tampered or malicious advisory | raw records stored; integrity verification where providers offer it; the matcher is deterministic and never changes affectedness based on text; VEX statements only suppress with recorded evidence; KEV/EPSS used as signals, never as instructions | feed connector tests (fixtures); packages/matcher golden precision/recall ≥ 0.95 |
| A7 | Approval abuse (self-approval, machine approval, chat approvals, replay, approving a different plan version) | SoD in OPA and API; machine identities never approvers; no approval buttons outside the UI; WebAuthn challenge bound to the plan hash, ≤ 5 min; attestation bound to content_hash; any plan change voids attestations; two-person rule for P0 prod; executor re-verifies everything | Rego: test_approval_by_requester_is_invalid, test_actor_cannot_approve_twice, test_approval_without_webauthn_is_invalid, test_approval_with_hash_mismatch_is_invalid, test_comment_required_for_p0_p1; API: test_approver_cannot_be_requester, test_machine_identity_never_approves, test_platform_admin_cannot_approve, test_modifier_cannot_approve_new_version; executor: TestSecurity_AttestationBoundToPlan, TestSecurity_ModifiedPlanFails, TestSecurity_P0ProdTwoPersonWithLeadIsAccepted |
| A8 | Insider with platform access tries to execute without approval or to silence the audit trail | platform_admin cannot approve; executor only consumes the queue whose only producer is approval-service; append-only audit with hash chain + Object Lock; break-glass needs double activation and alerts | test_platform_admin_cannot_approve; TestSecurity_ExecutionGateDenials; packages/audit/tests::test_detects_modified_event, ::test_segment_verification_detects_segment_tampering; test_break_glass_needs_tenant_flag_and_two_activations |
2. STRIDE per component
Ingestion plane (feeds, collectors, repo scanner)
| STRIDE | Threat | Mitigation | Verified by |
|---|---|---|---|
| S | Spoofed feed/webhook | TLS + allowlisted hosts; GitHub webhook HMAC (X-Hub-Signature-256); provider integrity checks | connectors webhook signature tests |
| T | Tampered inventory (fake resources via tags) | tags/labels are Untrusted; provenance recorded per fact; reconciliation declared-vs-real | graph reconcile tests |
| R | Unknown origin of a fact | Provenance(source, observed_at, reference) on every node/edge | core model tests |
| I | Collector reads customer data | custom read-only policy + permissions boundary with explicit denies; separate SBOM roles | policy_sim.py tests; CFN/Terraform policies |
| D | Feed outage / rate limits | cursors, backoff, DLQs, SLO monitors | connector backoff tests; Datadog monitors |
| E | Repo scanner executes repository code (sandbox escape) | static analysis only; allowlisted analyzers; no network except GitHub; no cloud env; read-only FS; destroyed after | sandbox tests (non-allowlisted command rejected; env has no cloud credentials) |
Analysis plane (matcher, risk, agent, plan-signer)
| STRIDE | Threat | Mitigation | Verified by |
|---|---|---|---|
| S | Forged plan | KMS plan-signer key: Sign only by the plan-signer role; verify everywhere | packages/signing/tests::test_wrong_key_and_wrong_role_fail; TestSecurity_WrongKeyAndCrossSignerRejected |
| T | Plan modified after signing | content_hash over canonical JSON; signature | test_modified_plan_after_signing_fails; TestSecurity_ModifiedPlanFails |
| R | "Who scored this?" | policy_version, model_id, prompts/responses in audit (redacted) | risk engine tests on policy versioning |
| I | Secrets in prompts | redaction; metadata-only inventory | test_redaction_removes_secrets_and_pii |
| D | Token budget exhaustion | per-tenant/per-finding budgets, monitors | packages/agent/budget.py tests |
| E | Agent gains write path | IAM boundary denies SQS send to executor queue + KMS Sign except plan-signer; no write tools | Terraform boundaries; test_tools_schemas.py |
Decision plane (api, approval-service, OPA, UI, notifier)
| STRIDE | Threat | Mitigation | Verified by |
|---|---|---|---|
| S | Session/OIDC token forgery; WebAuthn replay | JWKS verification (iss/aud/exp/kid), short sessions, assertion counter, hash-bound challenge | apps/api/tests/test_webauthn*; test_approve_without_webauthn_is_rejected |
| T | CSRF / clickjacking on approval | double-submit CSRF, strict CSP, frame-ancestors none, hold-to-confirm + WebAuthn | API CSRF tests; UI tests (ApprovalPanel cannot confirm without diff) |
| R | Disputed approval | signed attestation with approver id, auth method, assertion id, timestamp | attestation tests |
| I | Cross-tenant data access | RLS forced + store-level filter; per-tenant keys | test_cross_tenant_isolation; API 404 tests |
| D | Approval spam / rate | per-user+tenant rate limiting; execution hourly limit; WAF rate rules | API rate-limit tests; waf-ui module |
| E | Role escalation (platform_admin approving) | RBAC matrix in Rego + API dependency | test_platform_admin_cannot_approve; test_analyst_can_triage_but_not_approve |
Execution plane (executor)
| STRIDE | Threat | Mitigation | Verified by |
|---|---|---|---|
| S | Message injected into the queue | queue policy: single producer; message type pinned; both signatures verified with pinned keys | Terraform queues; TestSecurity_UnsignedExpiredBadAlgorithm |
| T | TOCTOU — target changed after analysis | inventory_snapshot_id + expected_file_sha freshness checks → stale | executor freshness tests (TestSecurity_Rejections) |
| R | Untraceable execution | every gate step emits an audit event; idempotency key per action | executor audit sink tests |
| I | Executor reads customer data / LLM | IAM executor boundary denies Bedrock, S3 GetObject, DB; no LLM SDK linked | Terraform boundary; TestSecurity_NoShellOrExecInExecutor |
| D | Runaway executions | hourly rate limit in policy; kill switch (file/ConfigMap + epoch); Recreate strategy | test_kill_switch_blocks_queueing_but_records_approval; executor kill-switch tests |
| E | Executor merges / changes protection / runs shell | GitHub App without admin permission; client has no merge/approve/protection methods; no os/exec | TestSecurity_NoMergeApproveOrProtectionCode; TestSecurity_NoShellOrExecInExecutor; app manifest permission set |
Audit
| STRIDE | Threat | Mitigation | Verified by |
|---|---|---|---|
| T/R | Event altered, deleted or reordered | hash chain, append-only grants + trigger, Object Lock COMPLIANCE, centinela-audit verify | test_detects_modified_event, test_detects_unsealed_and_cross_tenant, test_cli_verify_ok_and_tampered |
Supply chain of Centinela itself
| Threat | Mitigation | Verified by |
|---|---|---|
| Malicious dependency / action | lockfiles; Dependabot; actions pinned by SHA with weekly drift check; dependency review | scripts/verify-action-pins.sh; verify-pins.yml; security.yml |
| Tampered image | cosign keyless signing + SBOM attestation + SLSA L3 provenance; Kyverno admission requires both | release.yml; deploy/helm/cluster/kyverno-verify-images.yaml |
| Secrets in repo | gitleaks in CI; no static credentials (OIDC, Pod Identity, WIF) | security.yml → gitleaks |
| Vulnerable code | CodeQL + Semgrep + Trivy + ZAP baseline | security.yml |
3. Residual risks and assumptions
- GitHub branch protection on target repositories must require CODEOWNERS review; Centinela cannot enforce it on customers' repositories (it detects and reports missing protection as a posture finding).
- The quality of ownership attribution depends on customer tags/CODEOWNERS; ownerless assets escalate to
security_leadby policy. - Bedrock Guardrails are optional; the deterministic validator is the primary control.
- Checkov/HCL validation ran in the authoring environment;
tofu validateand real-cloud integration tests run in CI/trusted environments (seedocs/STATUS.md).
ES: Modelo STRIDE por componente más amenazas específicas de agentes (inyección de prompts, abuso de herramientas, exfiltración, confused deputy, envenenamiento de feeds, cadena de suministro, abuso de aprobaciones, insider). Cada amenaza indica su mitigación y la prueba que la verifica.