BUG_Author: Zy0t1
Affected Version: flow-ci / flow-core-x all versions (since 2019 to master branch, never patched)
Vendor: FlowCI/flow-core-x
Software: flow-ci
Vulnerability Files: - core/src/main/java/com/flowci/core/common/manager/ConditionManagerImpl.java (L39-L72, run method)
CWE: CWE-94 - Improper Control of Generation of Code (Code Injection) - The software constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
CVSS 3.1: 9.9 (Critical) - AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
flow-ci evaluates step-level condition fields from .flowci.yml pipeline configurations using GroovyShell without any sandbox restrictions. ConditionManagerImpl.run() creates a new GroovyShell(binding) and calls groovy.evaluate(groovyScript) directly — there is no SecureASTCustomizer, no CompilerConfiguration whitelist, and no SecurityManager fallback. The script content is entirely user-controlled through the step-level condition field.
The condition is evaluated in the flow-ci core container, inside the flowci-core.jar JVM process. It is not evaluated in an agent container or in a temporary job container created for a pipeline step. In JobActionServiceImpl.executeJob(), runCondition() is called before the code attempts to locate an agent, dispatch the bash step, or set the step to WAITING_AGENT. A condition-only browser PoC therefore succeeds even when no agent is online.
Runtime verification confirms that the executing process has PID 1, the command line java ... -jar flowci-core.jar, working directory /flow.ci, and the core container hostname. This proves that Groovy code executes in the same root process that serves the flow-ci core API, not in a job/agent environment where arbitrary commands might already be expected.
Key details:
GroovyShell is instantiated without a SecureASTCustomizer or CompilerConfiguration, allowing arbitrary Java method calls including Runtime.exec(), ProcessBuilder, File I/O, and ClassLoader access.JobActionServiceImpl.runCondition() skips root-level conditions for API-triggered jobs (if (node.getPath().isRoot()) return true).executeJob() calls runCondition() before fetchAgentFromJob(), fetchAgentFromPool(), and dispatch(). No agent is required for the Groovy code to execute.DefaultTimeout = 2 only attempts to cancel the Future. File writes and process creation can complete before cancellation.http://localhost:2015 and core API at http://localhost:8080.[email protected] / test@flowci.