Skip to content

fix(iac): keep '}' and JSON metacharacters in the Bicep/ARM contact email intact (#963 follow-ups) - #992

Merged
cristim merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/963-followups
Oct 16, 2026
Merged

cristim merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/963-followups

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Closes #991. This follows up #963 (issue #958), which merged at 7a71710 before this commit landed. The commit is cherry-picked unchanged onto main (adcd80c).

Changes (azure-wif-bicep-deploy.sh.tmpl and its tests)

  1. } in the email.
    • The template now does CONTACT_EMAIL="${CUDLY_CONTACT_EMAIL:-}" and then [ -n "$CONTACT_EMAIL" ] || CONTACT_EMAIL="{{.ContactEmail}}". So the rendered value is never inside a ${...:-default}.
    • The override behaves as before: a set, non-empty CUDLY_CONTACT_EMAIL wins.
    • shellEscape is unchanged. Adding } there would corrupt every other template, because \} stays literal inside double quotes.
  2. JSON in the heredoc.
    • A json_escape helper (printf | sed, no jq) escapes \ and ", and turns a tab into \t. net/mail accepts a tab in a quoted local part and rejects every other control character.
    • The heredoc now uses the escaped contact_email, and also account_name, since CUDLY_ACCOUNT_NAME comes from the operator. The other heredoc values are IDs.

Tests

These run in a bare bash: PATH only, an empty temp directory, set -euo pipefail, and az stubbed. Each runs the block from CONTACT_EMAIL= through the end of the heredoc and stops before curl.

  • Hostile emails: nine of them, for both bicep and arm, each checked first to pass mail.ParseAddress. They include a}b, "a}b", "a}$(touch pwned)\touch pwned`}b", "{a}${b}", a tab, and the earlier $(), backtick and "/\` cases.
  • Assertions for each: CONTACT_EMAIL comes back exactly; the payload parses with json.Unmarshal and has the exact contact_email; no marker file is created; and bash -n passes on the whole script.
  • RegistrationEnvOverrides: covers the override being unset, empty and set, plus a hostile CUDLY_ACCOUNT_NAME.

Verification

All tests ran with AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null. Everything here is local; no az, curl or network calls.

  • Pass-after: go test ./internal/api/ -run 'Federation|AzureTemplate|Azure' -count=1 -v gave 310 PASS, 0 FAIL, 1 SKIP. The skip is the existing TfvarsAutoLoadedByTerraform/aws-cross-account. It also passes on this branch.
  • Fail-before: with the template restored to the sec(api): shell-escape the Bicep/ARM deploy script render (#958) #963 version, the hostile-email subtests fail (all except a\id`b, which contains no }`, quote or tab), as do the three env-override subtests.
  • Mutation probes: none survived.
Mutation Result
Raw CONTACT_EMAIL in the heredoc Every " and tab case fails
Tab escape dropped Only the tab cases fail
account_name unescaped The account-name test fails
Old ${VAR:-default} form restored Every } case fails
Backslash escape dropped The \ case fails
Override inverted Broad failures
  • Other checks:
    • go vet ./internal/api/ is clean, golangci-lint v2.10.1 reports 0 issues, and gofmt is clean.
    • shellcheck 0.11.0 on a script rendered with a hostile email exits 0.
    • git-secrets (AWS patterns plus the repo's custom ones) is clean, and bash scripts/test-git-secrets-allowlist.sh gives 42 PASS, 0 FAIL.

Out of scope

Maintainer: please apply labels to #991 and this PR. #958's suggested set is type/security, type/bug, priority/p2, severity/medium.

🤖 Generated with Claude Code

…mail intact (LeanerCloud#958)

deploy-azure.sh pre-filled the contact email as the default of
${CUDLY_CONTACT_EMAIL:-...}. A "}" in the email (net/mail accepts it)
closed the expansion early and reordered the value, e.g. a}b@example.com
became ab}@example.com. Assign the env value first and fall back to the
pre-filled email on its own line; the override semantics are unchanged
(a set, non-empty CUDLY_CONTACT_EMAIL wins).

The self-registration heredoc also put CONTACT_EMAIL and ACCOUNT_NAME
into the JSON raw, so a '"', '\' or tab (all accepted by net/mail in a
quoted local part) produced a malformed payload. Add a json_escape
helper (printf + sed, portable to macOS bash 3.2 and Linux, no jq) and
use the escaped values in the heredoc. The other heredoc values are the
subscription, tenant and client GUIDs.

Tests run the registration block in the sandboxed bash with az stubbed,
for hostile emails with "}", "$", backticks, quotes, backslashes and a
tab, and check that CONTACT_EMAIL round-trips, that the payload parses as
JSON with the exact email, and that nothing executes. They also pin the
env-override cases and JSON-escaping of CUDLY_ACCOUNT_NAME.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 3 billable files and costs up to $0.75.

Or wait 59 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: LeanerCloud/cloud-commitments-platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c67a0b4b-e9ed-402e-aa5d-9a3e700192f2
📥 Commits

Reviewing files that changed from the base of the PR and between adcd80c and df91517.

📒 Files selected for processing: 3
  • internal/api/handler_federation_azure_escape_test.go
  • internal/api/handler_federation_test.go
  • internal/iacfiles/templates/azure-wif-bicep-deploy.sh.tmpl
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@IAmJSD IAmJSD added urgency/this-sprint Within the current sprint triaged Item has been triaged priority/p1 Next up; this sprint severity/high Significant harm impact/few Limited audience effort/xs Trivial / one-liner type/security Security finding labels Oct 16, 2026

@cristim cristim left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Independent review of df91517: no actionable findings

Findings

None BLOCKING. One NON-BLOCKING note on #964 below.

NON-BLOCKING, issue bookkeeping: open issue #964 (unescaped deploy-azure.sh render) is already fixed on main by #963. internal/api/handler_federation.go:561 renders the template with shellEscapeData(data). This PR does not change that. What it adds is the residual } case (#991) and JSON escaping of the payload. Suggest closing #964 as done, since #992 does not close it either (the PR closes only #991).

What I attacked

  • Rendered deploy-azure.sh for both bicep and arm with 11 emails, each valid for the auth service: a}b, "a\"b\\c$HOME", backtick and $(...) substitutions, a } combined with $(...) and backticks, ', tab, &|;, trailing backslash, %s, !#. Checked bash -n on each (all pass).
  • Ran each script end to end in env -i with only a stub az and a stub curl first on PATH, an empty cwd, and dummy template files. All 22 runs exited 0. The captured curl -d body parsed as JSON, and contact_email equaled the input byte for byte. No marker file was created (no command ran).
  • Consistency with the other escapers: shellEscape (handler_federation.go:399) is unchanged and still correct for the other templates. } is not added to it, which is right because \} is literal inside double quotes. The ${VAR:-default} form is gone from this template, so no escaper has to handle } here. hclEscape (#955) targets a different format and is not touched.
  • json_escape (template lines 74-78): the sed expressions are POSIX, the tab pattern comes from printf '\t', and the replacement has no &. CR/LF is not handled, and the helper comment says so honestly. The email cannot hold them (net/mail), and CUDLY_ACCOUNT_NAME is operator-supplied. A newline in that env var would give invalid JSON and a failed registration, not an injection.
  • Override semantics: unset, empty and set CUDLY_CONTACT_EMAIL behave as ${VAR:-default} did (tests plus my runs).
  • Fail-before: restored the parent template with the new tests. TestGetFederationIaC_AzureDeployScriptEscapesSessionEmail fails for 16 of 18 subtests (the aidb case has no }/quote/tab, and the first backtick case fails on the JSON escape), and RegistrationEnvOverrides fails. At the SHA, go test ./internal/api -run 'Azure|Federation' gives 314 passed. Reverted, worktree clean.
  • Scope: 3 files, +120/-28, no other call site of ContactEmail in the template. The 8 sibling templates are explicitly deferred to #991.

Gate status (origin/main Review gate)

  • Independent adversarial review naming the SHA: this comment.
  • CI on the SHA: not complete at review time. 8 pass, 13 pending, 0 failed, 0 action_required.
  • CodeRabbit: no actionable threads from me to count (not pinged).
  • Labels: match #991 (triaged, priority/p1, severity/high, urgency/this-sprint, impact/few, effort/xs, type/security).
  • Local verification: real bash runs of the rendered script with stubbed az/curl (macOS), fail-before/pass-after shown. No real Azure account was used.
  • Merge condition: all CI green on this SHA.

@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member

Gate record for df91517 (origin/main CLAUDE.md "Review gate"):

Merging at df91517 with --match-head-commit.

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

Labels

effort/xs Trivial / one-liner impact/few Limited audience priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/security Security finding urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow-ups from #963 review: '}' and JSON metacharacters in the Bicep/ARM contact email

2 participants