Skip to content

Conversation

@pahud
Copy link
Contributor

@pahud pahud commented Nov 18, 2025

Issue # (if applicable)

Closes #36076.

Reason for this change

When using CodeBuildAction with environment variables containing tokens (CloudFormation intrinsic functions like Fn.ref(), Fn.getAtt(), etc.), the JSON values are double-encoded. This causes CodeBuild to receive malformed JSON strings like {\"key\":\"value\"} instead of {"key":"value"}, breaking JSON parsing at runtime and preventing proper pipeline execution.

Description of changes

Modified the CodeBuildAction.bound() method to properly handle environment variables containing tokens:

  • Root Cause: The method was incorrectly using Stack.toJsonString() on already-serialized environment variables, causing double encoding when tokens were present
  • Solution: Replaced with Lazy.string() wrapping JSON.stringify() for proper deferred evaluation
  • Implementation: Serialize environment variables early (preserving construct-time validation for secrets) then defer final JSON stringification until synthesis time
  • Result: Token references like { Ref: 'MyParameter' } are correctly embedded in CloudFormation Fn::Join expressions without double-encoding

Before:

EnvironmentVariables: this.props.environmentVariables &&
  cdk.Stack.of(scope).toJsonString(codebuild.Project.serializeEnvVariables(...))

After:

const serializedEnvVars = this.props.environmentVariables
  ? codebuild.Project.serializeEnvVariables(...)
  : undefined;

EnvironmentVariables: serializedEnvVars &&
  cdk.Lazy.string({
    produce: () => JSON.stringify(serializedEnvVars),
  })

This fix ensures that tokens are properly resolved by CloudFormation at deployment time without double-encoding, while maintaining construct-time validation for secrets in plaintext environment variables.

why this works

  1. Early Serialization (Construct Time)
const serializedEnvVars = codebuild.Project.serializeEnvVariables(...)
  • Happens at construct time (when you define your CDK app)
  • Triggers validation (e.g., checking for secrets in plaintext variables)
  • Returns: [{ name: 'VAR', type: 'PLAINTEXT', value: <token> }]
  • The token is still a token object, not a string yet
  1. Lazy String Wrapper (Deferred Evaluation)
cdk.Lazy.string({
  produce: () => JSON.stringify(serializedEnvVars),
})
  • Lazy.string() defers execution until synthesis time (when CDK generates CloudFormation)
  • The produce function doesn't run immediately
  • It runs later when CloudFormation template is being generated
  1. When CDK synthesizes the CloudFormation template:
  • The produce function runs: JSON.stringify(serializedEnvVars)
  • CDK's token system recognizes tokens in the data structure
  • It creates a Fn::Join expression that properly embeds the token reference
  • Result CloudFormation:
{
  "EnvironmentVariables": {
    "Fn::Join": [
      "",
      [
        "[{\"name\":\"MY_VAR\",\"type\":\"PLAINTEXT\",\"value\":\"",
        { "Ref": "MyParameter" },  // ← Token properly embedded!
        "\"}]"
      ]
    ]
  }
}

The magic is that JSON.stringify() inside Lazy.string() works with CDK's token system:

  • Without Lazy: Stack.toJsonString() immediately stringifies everything, treating tokens as strings
  • With Lazy: JSON.stringify() runs at synthesis time when CDK can detect tokens and create proper Fn::Join expressions

Describe any new or updated permissions being added

N/A - No IAM permissions or resource access changes.

Description of how you validated changes

  • Unit tests: Added new test case 'environment variables with tokens are correctly serialized' that validates proper Fn::Join structure with embedded token references and confirms no double-encoding occurs. All 190 unit tests pass (189 existing + 1 new).
  • Integration tests: All 45 integration tests for aws-codepipeline-actions module pass with all snapshots UNCHANGED, confirming no breaking changes to CloudFormation template generation.
  • Manual validation: Verified synthesized CloudFormation template shows tokens properly embedded in Fn::Join expressions for CloudFormation resolution at deployment time.

Checklist


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

…o deployment time

- Move environment variable serialization outside of Stack.toJsonString() to prevent early validation
- Use cdk.Lazy.string() to defer JSON stringification until deployment time
- Add test case verifying token-based environment variables are correctly serialized without double-encoding
- Ensure CloudFormation tokens in environment variables are properly resolved at deployment time rather than construct time
- This fixes issues where token references in environment variables were being incorrectly serialized during synthesis
@aws-cdk-automation aws-cdk-automation requested a review from a team November 18, 2025 20:24
@github-actions github-actions bot added bug This issue is a bug. effort/small Small work item – less than a day of effort p2 labels Nov 18, 2025
@mergify mergify bot added the contribution/core This is a PR that came from AWS. label Nov 18, 2025
Copy link
Collaborator

@aws-cdk-automation aws-cdk-automation left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pull request linter fails with the following errors:

❌ Fixes must contain a change to an integration test file and the resulting snapshot.

If you believe this pull request should receive an exemption, please comment and provide a justification. A comment requesting an exemption should contain the text Exemption Request. Additionally, if clarification is needed, add Clarification Request to a comment.

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

Labels

bug This issue is a bug. contribution/core This is a PR that came from AWS. effort/small Small work item – less than a day of effort p2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

code-pipeline: Incorrect double JSON encoding of JSON with tokens

2 participants