diff --git a/workspaces/x2a/.changeset/fix-policy-lock-token-leak.md b/workspaces/x2a/.changeset/fix-policy-lock-token-leak.md new file mode 100644 index 0000000000..b3a09f7309 --- /dev/null +++ b/workspaces/x2a/.changeset/fix-policy-lock-token-leak.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-x2a-backend': patch +--- + +Fix SCM token leak in committed files: improve URL credential sanitization regex to handle user:token@host patterns and extend file type coverage to include markdown files diff --git a/workspaces/x2a/plugins/x2a-backend/templates/x2a-job-script.sh b/workspaces/x2a/plugins/x2a-backend/templates/x2a-job-script.sh index 39f9e22bec..10cc7bb7d3 100644 --- a/workspaces/x2a/plugins/x2a-backend/templates/x2a-job-script.sh +++ b/workspaces/x2a/plugins/x2a-backend/templates/x2a-job-script.sh @@ -56,16 +56,16 @@ sanitize_secrets() { local dir="$1" echo "=== Sanitizing secrets from output files ===" - # Match GitHub PATs (ghp_, gho_, github_pat_) and generic token@host patterns in URLs + # Match credentials embedded in URLs: https://token@host or https://user:token@host local count=0 while IFS= read -r -d '' file; do - if grep -qE 'https?://[^@/:[:space:]]+@' "$file" 2>/dev/null; then - # Strip token from URLs: https://ghp_xxx@github.com/... → https://github.com/... - sed -i 's|https\?://[^@/:[:space:]]*@|https://|g' "$file" + if grep -qE 'https?://[^@/[:space:]]+@' "$file" 2>/dev/null; then + # Strip credentials from URLs: https://user:token@host/... → https://host/... + sed -i 's|https\?://[^@/[:space:]]*@|https://|g' "$file" echo " Sanitized: ${file#/workspace/target/}" count=$((count + 1)) fi - done < <(find "$dir" -type f \( -name '*.json' -o -name '*.yaml' -o -name '*.yml' -o -name '*.lock' \) -print0 2>/dev/null) + done < <(find "$dir" -type f \( -name '*.json' -o -name '*.yaml' -o -name '*.yml' -o -name '*.lock' -o -name '*.md' \) -print0 2>/dev/null) if [ "$count" -eq 0 ]; then echo " No secrets found in output files" @@ -123,6 +123,12 @@ git_clone_repos() { "https://${SOURCE_REPO_TOKEN}@${SOURCE_REPO_URL#https://}" \ /workspace/source + # Strip the token from the git remote URL so that tools like Chef's + # CookbookProfiler::Git (which reads `git config --get remote.origin.url`) + # never see the credential. This prevents tokens from leaking into + # generated files such as Policyfile.lock.json. + git -C /workspace/source remote set-url origin "${SOURCE_REPO_URL}" + echo "=== Cloning target repository ===" local target_auth_url="https://${TARGET_REPO_TOKEN}@${TARGET_REPO_URL#https://}" @@ -299,8 +305,12 @@ case "${PHASE}" in # Note: x2a tool produces migration-plan-{module_name}.md (spaces replaced with underscores) echo "Copying output to ${OUTPUT_DIR}/" cp -v "${SOURCE_BASE}/migration-plan-${MODULE_NAME_SANITIZED}.md" "${OUTPUT_DIR}/" - cp -v "${SOURCE_BASE}"/*.json "${OUTPUT_DIR}/" 2>/dev/null || true cp -v "${SOURCE_BASE}"/*.yaml "${OUTPUT_DIR}/" 2>/dev/null || true + cp -rv "${SOURCE_BASE}/migration-dependencies" "${OUTPUT_DIR}/" 2>/dev/null || true + + # Update project-level Policyfile.lock.json — chef-cli may have updated it + # during dependency resolution. Keep it at project root only, not per-module. + cp -v "${SOURCE_BASE}/Policyfile.lock.json" "${PROJECT_PATH}/" 2>/dev/null || true echo "" echo "=== Output directory contents ===" @@ -334,6 +344,14 @@ case "${PHASE}" in echo "Copying migration-plan.md from target to source directory..." cp -v "${PROJECT_PATH}/migration-plan.md" "${SOURCE_BASE}/migration-plan.md" + # Copy migration-dependencies from target repo back to source dir. + # The analyze phase created this directory and committed it to the target repo. + # The migrate phase runs in a separate pod, so we need to restore it. + if [ -d "${PROJECT_PATH}/migration-dependencies" ]; then + echo "Copying migration-dependencies from target to source directory..." + cp -rv "${PROJECT_PATH}/migration-dependencies" "${SOURCE_BASE}/" + fi + # Check if x2a tool is available (required) if [ ! -d /app ] || [ ! -f /app/app.py ]; then ERROR_MESSAGE="/app/app.py not found - x2a tool is required" @@ -361,7 +379,6 @@ case "${PHASE}" in # Note: x2a tool writes to ansible/roles/{module}/ in the source directory echo "Copying output to ${OUTPUT_DIR}/" cp -rv "${SOURCE_BASE}/ansible" "${OUTPUT_DIR}/" 2>/dev/null || true - cp -v "${SOURCE_BASE}"/*.json "${OUTPUT_DIR}/" 2>/dev/null || true cp -v "${SOURCE_BASE}"/*.yaml "${OUTPUT_DIR}/" 2>/dev/null || true echo ""