WIP: Add threat model - #734
Conversation
| @@ -0,0 +1,141 @@ | |||
| # Threat Model: Ymir (ai-workflows) — AI-Automated RHEL/CentOS Stream Packaging | |||
|
|
|||
| ## 1. System context | |||
There was a problem hiding this comment.
Hmm...this has the potential to be subject to drift; isn't there an existing architecture doc?
There was a problem hiding this comment.
we have a bunch of markdown documents in the root of this repo; these would definitely benefit from an overhaul and unification
| |---|---|---|---|---|---|---|---|---|---| | ||
| | T1 | Unauthenticated public route grants direct read/write access to live automation task queues | remote_unauth | `redis-commander` OpenShift Route | Redis/Valkey task queues | medium | unlikely | unmitigated | no `HTTP_USER`/`HTTP_PASSWORD` configured; only Red Hat employees can tamper with it | none | | ||
| | T2 | Indirect prompt injection via untrusted Jira issue or GitLab MR comment content causes an agent to misuse privileged tools (unauthorized push, credential exfiltration, SSRF via `patch_url`) | remote_auth | Jira issue content; GitLab MR comments; `GetPatchFromUrlTool` | Dist-git write access, GitLab PAT, Kerberos keytabs, source/patch integrity | critical | possible | partially_mitigated | credential redaction (`_REDACT_PATTERNS` in `gateway.py`); privileged/unprivileged tool split (LLM can still call both); network egress allow-list bounds SSRF blast radius; mandatory human review before merge | commit `c5db93b3` (credential leakage), commit `8b181341`/`b294407e` (path traversal) | | ||
| | T3 | Supply-chain compromise of a third-party Python dependency executes arbitrary code inside agent/build containers, exposing all mounted credentials | supply_chain | PyPI dependency resolution during container build | Container images, Kerberos keytabs, GitLab PAT, GCP Vertex AI keys | critical | possible | mitigated | version pins excluding known-compromised `litellm` releases; build-time `litellm_init.pth` malicious-file detection; enforced Log Detective MCP version | commit `e7422175` and related `litellm` version pins (real PyPI supply-chain compromise of `litellm` 1.82.7/1.82.8) | |
There was a problem hiding this comment.
Personally I think supply chain stuff is a threat to literally all software and hence I'd just put it in a different bucket.
| | id | threat | actor | surface | asset | impact | likelihood | status | controls | evidence | | ||
| |---|---|---|---|---|---|---|---|---|---| | ||
| | T1 | Unauthenticated public route grants direct read/write access to live automation task queues | remote_unauth | `redis-commander` OpenShift Route | Redis/Valkey task queues | medium | unlikely | unmitigated | no `HTTP_USER`/`HTTP_PASSWORD` configured; only Red Hat employees can tamper with it | none | | ||
| | T2 | Indirect prompt injection via untrusted Jira issue or GitLab MR comment content causes an agent to misuse privileged tools (unauthorized push, credential exfiltration, SSRF via `patch_url`) | remote_auth | Jira issue content; GitLab MR comments; `GetPatchFromUrlTool` | Dist-git write access, GitLab PAT, Kerberos keytabs, source/patch integrity | critical | possible | partially_mitigated | credential redaction (`_REDACT_PATTERNS` in `gateway.py`); privileged/unprivileged tool split (LLM can still call both); network egress allow-list bounds SSRF blast radius; mandatory human review before merge | commit `c5db93b3` (credential leakage), commit `8b181341`/`b294407e` (path traversal) | |
There was a problem hiding this comment.
I think prompt injection is often the primary risk to anything related to LLMs, and may actually be deserving of its own dedicated doc or section even.
| | T7 | Attacker-influenced or malformed `jira_issue` string reaches a privileged filesystem operation (`shutil.rmtree`) without validation, deleting arbitrary directories on the shared clone volume | remote_auth | `clone_and_prep_sources`, `fork_and_prepare_dist_git` | Shared PVC `mcp-server-git-repos` (RWX, mounted by 6+ agent pods) | high | rare | mitigated | input validation rejects empty, absolute, or `..`-containing `jira_issue` values | commit `8b181341`, `b294407e`, `3dad71ea`/`39116842` | | ||
| | T8 | Non-employee or compromised Jira account forces automated processing of an arbitrary issue via the `ymir_todo` label | remote_auth | `ymir_todo` Jira label | Dist-git write access, Jira write access, compute/API budget | high | rare | mitigated | fetcher verifies label-adder is a Red Hat Employee Jira-group member via changelog walk (not JQL); atomic label flip before enqueue (fail-closed) | none | | ||
| | T9 | Unbounded retention of Redis/Valkey queue data and Phoenix traces stores workflow state and tool inputs/outputs indefinitely | insider | Valkey queue storage; Phoenix trace PVC | Redis/Valkey task queues, Splunk logs / OTEL traces | medium | likely | unmitigated | none — `data_retention_policy.md` explicitly flags this as unresolved; the 7-day cleanup job only covers git clones, not queues or traces | `data_retention_policy.md` self-identifies the gap | | ||
| | T10 | Runtime users in the highest-privilege images (`beeai`, `mcp`, `supervisor`) are members of the `wheel` group; if the base image ships a sudoers rule, a compromised process (via T2/T3) could escalate to root inside the container | local_user | `Containerfile.c9s`/`.c10s`, `Containerfile.mcp`, `Containerfile.supervisor` | Pod filesystem/process, mounted credentials | medium | rare | unmitigated | `runAsNonRoot: true` and `seccompProfile: RuntimeDefault` enforced by OpenShift SCC; sudoers presence on the base image has not been verified | none | |
There was a problem hiding this comment.
I think root-in-container is an elevated risk primarily because the containers are stateful/persistent.
There was a problem hiding this comment.
but at the same time in openshift those are not a real threat due to openshift's restrictive SCC
| | OpenShift platform/cluster-level compromise (node escape, cluster-admin compromise, storage-class or admission-webhook bugs) | Platform-team responsibility; this project treats the OpenShift control plane as trusted infrastructure. | | ||
| | Self-inflicted availability incidents (RollingUpdate/PVC deadlock, resource-quota deadlock, stale AWS `nodeSelector`s from cluster migration) | Already fixed (all Deployments use `Recreate` strategy); these were operational reliability bugs, not adversarial threats — tracked as ops incidents. | | ||
|
|
||
| ## 6. Open questions |
There was a problem hiding this comment.
At least Claude foundation models love leaving an "open questions" section in docs by default, but I think this doc should be answering questions, not asking them
There was a problem hiding this comment.
Actually, this is expected part of the THREAT_MODEL.md as outlined in the original guide.
There was a problem hiding this comment.
Thanks! I'll suggest a fix there
| | T1 | Unauthenticated public route grants direct read/write access to live automation task queues | remote_unauth | `redis-commander` OpenShift Route | Redis/Valkey task queues | medium | unlikely | unmitigated | no `HTTP_USER`/`HTTP_PASSWORD` configured; only Red Hat employees can tamper with it | none | | ||
| | T2 | Indirect prompt injection via untrusted Jira issue or GitLab MR comment content causes an agent to misuse privileged tools (unauthorized push, credential exfiltration, SSRF via `patch_url`) | remote_auth | Jira issue content; GitLab MR comments; `GetPatchFromUrlTool` | Dist-git write access, GitLab PAT, Kerberos keytabs, source/patch integrity | critical | possible | partially_mitigated | credential redaction (`_REDACT_PATTERNS` in `gateway.py`); privileged/unprivileged tool split (LLM can still call both); network egress allow-list bounds SSRF blast radius; mandatory human review before merge | commit `c5db93b3` (credential leakage), commit `8b181341`/`b294407e` (path traversal) | | ||
| | T3 | Supply-chain compromise of a third-party Python dependency executes arbitrary code inside agent/build containers, exposing all mounted credentials | supply_chain | PyPI dependency resolution during container build | Container images, Kerberos keytabs, GitLab PAT, GCP Vertex AI keys | critical | possible | mitigated | version pins excluding known-compromised `litellm` releases; build-time `litellm_init.pth` malicious-file detection; enforced Log Detective MCP version | commit `e7422175` and related `litellm` version pins (real PyPI supply-chain compromise of `litellm` 1.82.7/1.82.8) | | ||
| | T4 | Credential material leaks into LLM agent context or centralized logs via tool error/stderr output | insider | privileged GitLab/dist-git tool error handling; Splunk-forwarded stdout/stderr | GitLab PAT, Kerberos keytabs | critical | rare | mitigated | `_sanitize_git_stderr()` and `_REDACT_PATTERNS` strip credential-shaped strings before they reach agent context or logs | commit `c5db93b3` | |
There was a problem hiding this comment.
I can't find the c5db93b3 nor _sanitize_git_stderr function. There is a redact_credentials however.
| | T1 | Unauthenticated public route grants direct read/write access to live automation task queues | remote_unauth | `redis-commander` OpenShift Route | Redis/Valkey task queues | medium | unlikely | unmitigated | no `HTTP_USER`/`HTTP_PASSWORD` configured; only Red Hat employees can tamper with it | none | | ||
| | T2 | Indirect prompt injection via untrusted Jira issue or GitLab MR comment content causes an agent to misuse privileged tools (unauthorized push, credential exfiltration, SSRF via `patch_url`) | remote_auth | Jira issue content; GitLab MR comments; `GetPatchFromUrlTool` | Dist-git write access, GitLab PAT, Kerberos keytabs, source/patch integrity | critical | possible | partially_mitigated | credential redaction (`_REDACT_PATTERNS` in `gateway.py`); privileged/unprivileged tool split (LLM can still call both); network egress allow-list bounds SSRF blast radius; mandatory human review before merge | commit `c5db93b3` (credential leakage), commit `8b181341`/`b294407e` (path traversal) | | ||
| | T3 | Supply-chain compromise of a third-party Python dependency executes arbitrary code inside agent/build containers, exposing all mounted credentials | supply_chain | PyPI dependency resolution during container build | Container images, Kerberos keytabs, GitLab PAT, GCP Vertex AI keys | critical | possible | mitigated | version pins excluding known-compromised `litellm` releases; build-time `litellm_init.pth` malicious-file detection; enforced Log Detective MCP version | commit `e7422175` and related `litellm` version pins (real PyPI supply-chain compromise of `litellm` 1.82.7/1.82.8) | | ||
| | T4 | Credential material leaks into LLM agent context or centralized logs via tool error/stderr output | insider | privileged GitLab/dist-git tool error handling; Splunk-forwarded stdout/stderr | GitLab PAT, Kerberos keytabs | critical | rare | mitigated | `_sanitize_git_stderr()` and `_REDACT_PATTERNS` strip credential-shaped strings before they reach agent context or logs | commit `c5db93b3` | |
There was a problem hiding this comment.
I can't find the c5db93b3 nor _sanitize_git_stderr function. There is a redact_credentials however.
| | T2 | Indirect prompt injection via untrusted Jira issue or GitLab MR comment content causes an agent to misuse privileged tools (unauthorized push, credential exfiltration, SSRF via `patch_url`) | remote_auth | Jira issue content; GitLab MR comments; `GetPatchFromUrlTool` | Dist-git write access, GitLab PAT, Kerberos keytabs, source/patch integrity | critical | possible | partially_mitigated | credential redaction (`_REDACT_PATTERNS` in `gateway.py`); privileged/unprivileged tool split (LLM can still call both); network egress allow-list bounds SSRF blast radius; mandatory human review before merge | commit `c5db93b3` (credential leakage), commit `8b181341`/`b294407e` (path traversal) | | ||
| | T3 | Supply-chain compromise of a third-party Python dependency executes arbitrary code inside agent/build containers, exposing all mounted credentials | supply_chain | PyPI dependency resolution during container build | Container images, Kerberos keytabs, GitLab PAT, GCP Vertex AI keys | critical | possible | mitigated | version pins excluding known-compromised `litellm` releases; build-time `litellm_init.pth` malicious-file detection; enforced Log Detective MCP version | commit `e7422175` and related `litellm` version pins (real PyPI supply-chain compromise of `litellm` 1.82.7/1.82.8) | | ||
| | T4 | Credential material leaks into LLM agent context or centralized logs via tool error/stderr output | insider | privileged GitLab/dist-git tool error handling; Splunk-forwarded stdout/stderr | GitLab PAT, Kerberos keytabs | critical | rare | mitigated | `_sanitize_git_stderr()` and `_REDACT_PATTERNS` strip credential-shaped strings before they reach agent context or logs | commit `c5db93b3` | | ||
| | T5 | Operator (or anyone with `oc exec`/`oc rsh` RBAC into the `valkey` pod) directly injects or tampers with queue entries, controlling which package/branch/issue privileged agents act on | local_admin | `oc exec`/`oc rsh` into `valkey` pod | Redis/Valkey task queues | high | possible | partially_mitigated | OpenShift namespace RBAC restricts who can `oc exec`; no application-level audit trail for direct queue mutation | `investigating-issues.md` documents this as routine operational practice | |
There was a problem hiding this comment.
I can't find any investigating-issues.md
|
|
||
| | threat | reason | | ||
| |---|---| | ||
| | Direct compromise of Brew/Konflux build triggering | Agents never call build-trigger APIs directly; the actual trigger is GitLab CI acting on a human-applied MR label. Threat belongs to the CI/build-system's own threat model, not this repo's. | |
There was a problem hiding this comment.
I'm not so sure. Is it impossible craft an issue that would force multiple reproducer runs using ReproduceTestingFarmRequestTool, or spam copr builds using BuildPackageTool?
| | OpenShift platform/cluster-level compromise (node escape, cluster-admin compromise, storage-class or admission-webhook bugs) | Platform-team responsibility; this project treats the OpenShift control plane as trusted infrastructure. | | ||
| | Self-inflicted availability incidents (RollingUpdate/PVC deadlock, resource-quota deadlock, stale AWS `nodeSelector`s from cluster migration) | Already fixed (all Deployments use `Recreate` strategy); these were operational reliability bugs, not adversarial threats — tracked as ops incidents. | | ||
|
|
||
| ## 6. Open questions |
There was a problem hiding this comment.
Actually, this is expected part of the THREAT_MODEL.md as outlined in the original guide.
| | id | threat | actor | surface | asset | impact | likelihood | status | controls | evidence | | ||
| |---|---|---|---|---|---|---|---|---|---| | ||
| | T1 | Unauthenticated public route grants direct read/write access to live automation task queues | remote_unauth | `redis-commander` OpenShift Route | Redis/Valkey task queues | medium | unlikely | unmitigated | no `HTTP_USER`/`HTTP_PASSWORD` configured; only Red Hat employees can tamper with it | none | | ||
| | T2 | Indirect prompt injection via untrusted Jira issue or GitLab MR comment content causes an agent to misuse privileged tools (unauthorized push, credential exfiltration, SSRF via `patch_url`) | remote_auth | Jira issue content; GitLab MR comments; `GetPatchFromUrlTool` | Dist-git write access, GitLab PAT, Kerberos keytabs, source/patch integrity | critical | possible | partially_mitigated | credential redaction (`_REDACT_PATTERNS` in `gateway.py`); privileged/unprivileged tool split (LLM can still call both); network egress allow-list bounds SSRF blast radius; mandatory human review before merge | commit `c5db93b3` (credential leakage), commit `8b181341`/`b294407e` (path traversal) | |
There was a problem hiding this comment.
I can't find commit b294407e .
jpodivin
left a comment
There was a problem hiding this comment.
I also couldn't find these files:
- network-egress-compliance.md
- deployment_adventures.md
- deployment_adventures_2.md
- access_control.md
| | T4 | Credential material leaks into LLM agent context or centralized logs via tool error/stderr output | insider | privileged GitLab/dist-git tool error handling; Splunk-forwarded stdout/stderr | GitLab PAT, Kerberos keytabs | critical | rare | mitigated | `_sanitize_git_stderr()` and `_REDACT_PATTERNS` strip credential-shaped strings before they reach agent context or logs | commit `c5db93b3` | | ||
| | T5 | Operator (or anyone with `oc exec`/`oc rsh` RBAC into the `valkey` pod) directly injects or tampers with queue entries, controlling which package/branch/issue privileged agents act on | local_admin | `oc exec`/`oc rsh` into `valkey` pod | Redis/Valkey task queues | high | possible | partially_mitigated | OpenShift namespace RBAC restricts who can `oc exec`; no application-level audit trail for direct queue mutation | `investigating-issues.md` documents this as routine operational practice | | ||
| | T6 | SSRF-shaped fetch of an attacker-supplied `patch_url` reaches internal network endpoints reachable from the agent pod | remote_auth | `GetPatchFromUrlTool` `patch_url` parameter | Internal network reachability, GCP Vertex AI endpoints, other RH internal services within the egress allow-list | high | possible | partially_mitigated | OpenShift `TenantEgress` default-deny egress allow-list (network-level only; no in-app URL validation) | none | | ||
| | T7 | Attacker-influenced or malformed `jira_issue` string reaches a privileged filesystem operation (`shutil.rmtree`) without validation, deleting arbitrary directories on the shared clone volume | remote_auth | `clone_and_prep_sources`, `fork_and_prepare_dist_git` | Shared PVC `mcp-server-git-repos` (RWX, mounted by 6+ agent pods) | high | rare | mitigated | input validation rejects empty, absolute, or `..`-containing `jira_issue` values | commit `8b181341`, `b294407e`, `3dad71ea`/`39116842` | |
There was a problem hiding this comment.
I can't find commit 39116842.
I store these files locally as additional context for Claude; probably time to remove them |
| sets** (`ymir/tools/privileged/` vs `ymir/tools/unprivileged/`), enforced | ||
| by a pre-push static check. This separates *which tools exist with | ||
| secrets*, but the LLM agent itself has runtime access to call both sets | ||
| — it does not sandbox the model away from privileged tools. |
There was a problem hiding this comment.
it does not sandbox the model away from privileged tools
Excuse me?
There was a problem hiding this comment.
I wonder what would count as sandbox?
| human-applied MR label (`feature::draft-builds::enabled`); agents only | ||
| manipulate MR labels and metadata, never call build-trigger APIs. | ||
| - **All AI-authored changes require human review before merge** — the | ||
| `ymir_needs_attention` label is applied to every new MR, and this is |
There was a problem hiding this comment.
the
ymir_needs_attentionlabel is applied to every new MR
I'm pretty sure it isn't.
| | Processing of embargoed CVEs by agents | Explicitly out of scope by policy — agents do not handle embargoed issues (`monitoring.md`). Enforced procedurally, not by this codebase. | | ||
| | Compromise of GitLab CI build infrastructure (`gitlab.cee.redhat.com`) or the `quay.io` robot account | Owned and secured by central Red Hat CI/PSI infrastructure teams, outside this project's control. | | ||
| | OpenShift platform/cluster-level compromise (node escape, cluster-admin compromise, storage-class or admission-webhook bugs) | Platform-team responsibility; this project treats the OpenShift control plane as trusted infrastructure. | | ||
| | Self-inflicted availability incidents (RollingUpdate/PVC deadlock, resource-quota deadlock, stale AWS `nodeSelector`s from cluster migration) | Already fixed (all Deployments use `Recreate` strategy); these were operational reliability bugs, not adversarial threats — tracked as ops incidents. | |
There was a problem hiding this comment.
Already fixed (all Deployments use
Recreatestrategy)
This has just been changed: 9cd8c25
| by a pre-push static check. This separates *which tools exist with | ||
| secrets*, but the LLM agent itself has runtime access to call both sets | ||
| — it does not sandbox the model away from privileged tools. | ||
| - **`DRY_RUN` and `JIRA_ALLOW_STATUS_CHANGES` are safety gates** that must |
There was a problem hiding this comment.
The JIRA_ALLOW_STATUS_CHANGES being unset is auditable. It being set to true is not. It is also incorrect to call DRY_RUN==true a safe value, if anything, it's opposite of safe.
We should probably log JIRA_ALLOW_STATUS_CHANGES being set to true.
dc79cde to
6e98611
Compare
| - **Agents never trigger RHEL/CentOS Stream builds directly.** Build | ||
| triggering (Brew/Konflux) is delegated to GitLab CI via a | ||
| human-applied MR label (`feature::draft-builds::enabled`); agents only | ||
| manipulate MR labels and metadata, never call build-trigger APIs. |
There was a problem hiding this comment.
I think this is incorrect, some of the labels are applied automatically and the builds are triggered for open MRs
| `ymir_rebuild`). Whatever actually blocks an AI-authored MR from | ||
| merging unreviewed (branch protection / required approvals) lives in | ||
| GitLab project settings, outside this repo — see open questions. | ||
| - **Automated processing can only be maintainer-triggered.** Adding the |
There was a problem hiding this comment.
the only is misleading in my opinion, as the batch processing based on filter doesnt fit in here
| | T6 | SSRF-shaped fetch of an attacker-supplied `patch_url` reaches internal network endpoints reachable from the agent pod | remote_auth | `GetPatchFromUrlTool` `patch_url` parameter | Internal network reachability, GCP Vertex AI endpoints, other RH internal services within the egress allow-list | high | possible | partially_mitigated | OpenShift `TenantEgress` default-deny egress allow-list (network-level only; no in-app URL validation) | none | | ||
| | T12 | Prompt-injected instructions in Jira issue or MR comment content drive an agent to repeatedly invoke costly privileged tools (`ReproduceTestingFarmRequestTool`, `BuildPackageTool`) with varying arguments, exhausting the Testing Farm API quota or spamming internal Copr builds under the automation's real identity | remote_auth | `ReproduceTestingFarmRequestTool`, `BuildPackageTool` | Testing Farm API token, Copr build capacity/quota, Kerberos-authenticated build identity | high | possible | unmitigated | `ToolCallChecker` only blocks exact-duplicate calls (identical tool name + args) within a window of 10, so varying one argument bypasses it; `max_iterations` (default 20-255) caps total agent steps but not per-tool invocation count | none | | ||
| | T7 | Attacker-influenced or malformed `jira_issue` string reaches a privileged filesystem operation (`shutil.rmtree`) without validation, deleting arbitrary directories on the shared clone volume | remote_auth | `clone_and_prep_sources`, `fork_and_prepare_dist_git` | Shared PVC `mcp-server-git-repos` (RWX, mounted by 6+ agent pods) | high | rare | mitigated | input validation rejects empty, absolute, or `..`-containing `jira_issue` values | commit `8b181341` (validate `jira_issue`), commit `3dad71ea` (don't ignore `shutil.rmtree` errors) | | ||
| | T8 | Non-employee or compromised Jira account forces automated processing of an arbitrary issue via the `ymir_todo` label (see T12 for what a legitimately-triggered issue's *content* can still drive an agent to do) | remote_auth | `ymir_todo` Jira label | Dist-git write access, Jira write access, compute/API budget | high | rare | mitigated | fetcher verifies label-adder is a Red Hat Employee Jira-group member via changelog walk (not JQL); atomic label flip before enqueue (fail-closed) | none | |
There was a problem hiding this comment.
I think we are checking the Red Hat Employee group for these, worth double checking
Signed-off-by: Tomas Tomecek <ttomecek@redhat.com> Assisted-by: Claude
Signed-off-by: Tomas Tomecek <ttomecek@redhat.com> Assisted-by: Claude
6e98611 to
3d2a3b9
Compare
The threat model is mostly Claude generated with only a few of my own changes.
I especially want to discuss severity of these findings.