Skip to content

Bicep/ARM federation bundle deploy.sh embeds the user's email without shell escaping (command injection) #958

Closed

Description

@IAmJSD

Summary

The Azure Bicep and ARM federation bundles (GET /api/federation/iac?format=bicep|arm) include a deploy.sh. It is rendered from internal/iacfiles/templates/azure-wif-bicep-deploy.sh.tmpl without shell escaping, and it embeds the requesting user's email inside a double-quoted string:

CONTACT_EMAIL="${CUDLY_CONTACT_EMAIL:-{{.ContactEmail}}}"

The value is used as-is, so a command substitution inside that string, such as `...` or $(...), runs when someone executes deploy.sh.

The other shell outputs are escaped. The cli format and the CloudFormation deploy.sh both go through shellEscapeData (internal/api/handler_federation.go, around lines 274 and 640). The Bicep/ARM path renders the deploy script with the raw data (around line 532: renderTemplate("templates/azure-wif-bicep-deploy.sh.tmpl", data)).

Found by the agent working on #952 (PR #955) and confirmed on main at the time of filing.

Why the value can be hostile

ContactEmail is session.Email, the signed-in user's own address. Every email write path validates only with mail.ParseAddress: SetupAdmin, validateCreateUserRequest, and the profile update updateUserEmail in internal/auth/service_user.go. Go's net/mail accepts addresses that contain shell metacharacters:

a`id`b@example.com       ACCEPT  (Address = a`id`b@example.com)
"a$(id)b"@example.com    ACCEPT  (Address = a$(id)b@example.com)

Impact

A user who can change their own profile email can set it to an address like the ones above, download a Bicep or ARM bundle, and pass it to whoever deploys into the Azure tenant. That is a normal hand-off, since the bundle exists to be run by an Azure admin. When the admin runs deploy.sh, the embedded command runs on the admin's machine with their Azure CLI session.

This needs a signed-in user with view:accounts and a victim who runs the script, so it isn't remote or unauthenticated. But it is a cross-user code-execution path through a generated artifact.

Expected

The Bicep/ARM deploy.sh is rendered through the same shellEscapeData as the other shell outputs. A regression test renders the bundle with a hostile contact email and asserts the script contains no live command substitution.

Possibly also worth doing (judgment call for the fix PR): tighten email validation so stored addresses can't contain shell or HTML metacharacters. That would be defense in depth, not a substitute for escaping.

Suggested labels (I can't apply them): type/security, type/bug, priority/p2, severity/medium.

🤖 Generated with Claude Code

Activity

  1. self-assigned this
    on Oct 16, 2026
  2. IAmJSD commented on Oct 16, 2026

    @IAmJSD
    CollaboratorAuthor

    Claiming: working on this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions