Jul 14, 2026·6 min read·15 visits
Woodpecker CI server used an unsafe metadata append operation, allowing authenticated agents to spoof their identity and impersonate any other registered agent.
A critical authentication bypass vulnerability in Woodpecker CI allows authenticated agents to impersonate other agents by injecting spoofed agent_id values into gRPC metadata. This flaw is caused by the use of md.Append instead of md.Set on the server-side RPC authorizer.
Woodpecker CI utilizes a central server architecture that interacts with multiple execution agents to process build pipelines. These runner agents communicate with the server via gRPC bidirectional communication. The agent authorization model relies on cryptographically signed JSON Web Tokens (JWT) issued to each registered agent to establish a trusted channel.
From version 3.0.0 and prior to version 3.14.1, Woodpecker CI contains an authentication bypass flaw in its gRPC authorization interceptor. The flaw permits an authenticated agent to impersonate any other agent registered on the same instance. This weakness is categorized under CWE-290 (Authentication Bypass by Spoofing) and CWE-639 (Authorization Bypass Through User-Controlled Key).
An attacker who has compromised or legitimately obtained access to a single low-privileged agent can manipulate metadata parameters to assume the identity of more privileged agents. This cross-tenant impersonation bypasses logical isolation boundaries in multi-tenant Woodpecker deployments. The impact includes unauthorized task interception, source code leakage, and execution of arbitrary workflows within separate tenant environments.
The root cause of the vulnerability resides in the discrepancy between identity validation and transport-layer metadata serialization in the gRPC interceptor logic located at server/rpc/authorizer.go. When an agent initiates a gRPC connection, the server validates the agent's JWT. Upon successful cryptographic verification, the handler extracts the authorized agent identity value (claims.AgentID).
The server attempts to inject this verified identity into the gRPC incoming context to ensure downstream handlers can access the authenticated agent ID. To achieve this, the authorizer called md.Append("agent_id", fmt.Sprintf("%d", claims.AgentID)). The md.Append function appends values to the existing list of elements under the key agent_id rather than replacing the key's value.
Because gRPC allows clients to supply arbitrary metadata headers, a malicious agent can pre-populate the request headers with an agent_id key set to a target agent's identifier. The md.Append call appends the true, validated agent ID to the end of the metadata slice, resulting in a slice containing both the spoofed and the real IDs. When downstream processes query the context using indexing or single-value retrieval methods, they fetch the first element of the slice, which corresponds to the attacker-supplied, spoofed agent ID.
The process of metadata parsing and the resultant security bypass are illustrated in the following diagram:
The vulnerability was fixed in pull requests #6567 and #6569. The initial patch resolves the metadata collection issue by replacing the append behavior with a strict overwrite function. This ensures that any input provided by the client under the key agent_id is discarded and replaced with the cryptographically validated ID from the JWT.
Review the code changes introduced in server/rpc/authorizer.go below:
// BEFORE THE PATCH
func (a *Authorizer) authorize(ctx context.Context, fullMethod string) (context.Context, error) {
// ... validation logic ...
claims, err := a.verifyToken(token)
if err != nil {
return ctx, status.Errorf(codes.Unauthenticated, "access token is invalid: %v", err)
}
// Unsafe append action that allows client-supplied values to persist
md.Append("agent_id", fmt.Sprintf("%d", claims.AgentID))
return metadata.NewIncomingContext(ctx, md), nil
}
// AFTER THE PATCH
func (a *Authorizer) authorize(ctx context.Context, fullMethod string) (context.Context, error) {
// ... validation logic ...
claims, err := a.verifyToken(token)
if err != nil {
return ctx, status.Errorf(codes.Unauthenticated, "access token is invalid: %v", err)
}
// Overwrites any client-supplied 'agent_id' with the verified claims identity
md.Set("agent_id", fmt.Sprintf("%d", claims.AgentID))
return metadata.NewIncomingContext(ctx, md), nil
}A second pull request, #6569, established a deeper structural separation. It migrated context identity propagation away from gRPC transport metadata keys entirely. By utilizing private Go context keys, the updated code prevents any client-supplied transport metadata from influencing the authentication state parsed by downstream business logic handlers.
To execute the impersonation attack, an attacker must satisfy specific prerequisites. First, they must obtain legitimate credentials for an agent authorized to communicate with the Woodpecker server. In multi-tenant environments where organization-level or user-level agent registration is enabled, an attacker can register a restricted agent to establish this initial foothold.
Once the attacker-controlled agent is registered, the attacker modifies the agent binary or uses a custom gRPC client implementation. The custom client establishes a gRPC connection to the Woodpecker server, presenting its valid JWT in the authorization header. Simultaneously, the client inserts an HTTP/2 header containing the key agent_id and the numeric identifier of the target agent, such as 1 for the default system agent.
When the server processes the connection, the authorizer appends the client's verified agent ID to the end of the agent_id metadata slice. Because the server parses metadata using index zero or a method that returns the first available element, the server treats the request as originating from the target agent. The attacker-controlled agent then receives and executes pipelines designated for the impersonated agent, harvesting credentials and environment secrets associated with those tasks.
The security impact of CVE-2026-50141 is high because it compromises the logical isolation of the CI/CD pipeline infrastructure. In environments where multiple teams share a single Woodpecker CI control plane, a compromise of one tenant's agent leads directly to the potential compromise of all other tenants. The CVSS v4.0 base score is calculated as 7.1, indicating high severity.
An attacker who successfully impersonates a highly privileged agent can poll the server for pending execution tasks. By retrieving these build tasks, the malicious agent receives the full build context. This context includes environment variables, secret keys, repository access tokens, and deployment credentials stored within the target projects.
Additionally, the attacker can hijack build execution. The attacker can return falsified build statuses, alter generated binaries or container images during the build process, and inject backdoors into downstream deployment pipelines. This compromises the integrity of the software supply chain without triggering immediate infrastructure alerts.
The primary remediation for this vulnerability is to upgrade the Woodpecker CI deployment. Both the Woodpecker server and agent components must be updated to version 3.14.1 or higher. This release addresses the vulnerability by applying the md.Set metadata configuration and introducing context-level key propagation to prevent header injection attacks.
If immediate updates are not possible, administrators should mitigate exposure by disabling user-level agent registrations. By setting the environment variable WOODPECKER_DISABLE_USER_AGENT_REGISTRATION=true on the Woodpecker server, administrators restrict the registration of new agents to system-level administrators. This prevents untrusted users from establishing the initial authenticated session required to execute the exploit.
Additionally, security teams should review active agents within the database and remove any non-system or unauthorized registrations. Analyzing gRPC transport logs for occurrences of duplicated or client-provided agent_id headers is recommended to detect historical exploitation attempts.
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Woodpecker CI woodpecker-ci | >= 3.0.0, < 3.14.1 | 3.14.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-290, CWE-639 |
| Attack Vector | Network |
| CVSS v4.0 Score | 7.1 (High) |
| Exploit Status | PoC (Proof of Concept) |
| Affected Component | gRPC Authorization Interceptor (server/rpc/authorizer.go) |
| Fixed Version | v3.14.1 |
The software records or uses communication channel or message sender identity information that is incorrect, allowing attackers to bypass authentication controls and impersonate legitimate entities.
CVE-2026-54720 is a stored Cross-Site Scripting (XSS) vulnerability inside the Silverstripe Framework's media shortcode processor. Due to a flawed performance optimization, HTML inputs containing two or fewer opening angle brackets bypassed security sandboxing. This flaw allows authenticated or lower-privileged users to inject administrative panel payloads that execute arbitrary client-side JavaScript when viewed by system administrators.
An incomplete array comparison vulnerability in cakephp/queue version 0.1.11 through 2.3.0 allows unauthenticated attackers to cause key collisions in unique job deduplication. This is caused by standard array value sorting that discards associative keys, normalizing different payload keys to identical arrays and leading to a denial of service (DoS) by dropping legitimate jobs.
An input buffering vulnerability exists in the aiosmtplib asynchronous SMTP client library before version 5.1.2. When upgrading a plaintext connection to TLS via STARTTLS, the library processes buffered plaintext responses after transport negotiation has completed. This behavior allows a network-positioned attacker to inject spoofed server responses prior to negotiation, leading to command/response desynchronization, arbitrary capability injection, and potential credential theft.
An open redirect vulnerability exists in WebOb before version 1.8.11 due to a parser differential between WebOb's validation logic and Python's standard urllib.parse.urljoin() function. Under Python 3.10+, the urljoin function strips leading and trailing space characters and C0 control characters, which allowed specially crafted inputs to bypass WebOb's prefix checks while still resolving as off-host redirects.
Prior to version 1.0.0, the n8n-nodes-sqlite3 integration exposed the db_path parameter as an unrestricted node parameter. By default, n8n node parameters allow the evaluation of dynamic data expressions, meaning untrusted external input could be mapped to the database path. This vulnerability allows an external attacker to control which SQLite database file the n8n backend process attempts to open, leading to directory traversal outside of the intended directory context.
A client-side open redirect vulnerability has been identified in the Kargo user interface. The flaw resides in the handling of OpenID Connect (OIDC) login and token renewal flows, where the application extracts an unvalidated destination path from the redirectTo query parameter. Attackers can exploit this to redirect authenticated users to arbitrary external domains.