From 990a72939d6790ccc702ce936792ff83683736cb Mon Sep 17 00:00:00 2001 From: "@odemolliens" Date: Wed, 26 Feb 2025 10:55:48 +0100 Subject: [PATCH 1/3] SIDI-123 - Update step.yml --- .../_audit_packages_yarn/codemagic/step.yml | 310 +++++++++++++++--- 1 file changed, 267 insertions(+), 43 deletions(-) diff --git a/SIDI/steps/_audit_packages_yarn/codemagic/step.yml b/SIDI/steps/_audit_packages_yarn/codemagic/step.yml index ae4de18..dc83df5 100644 --- a/SIDI/steps/_audit_packages_yarn/codemagic/step.yml +++ b/SIDI/steps/_audit_packages_yarn/codemagic/step.yml @@ -3,53 +3,277 @@ ignore_failure: true script: | #!/bin/sh set -ex - - # hack to fix yarn version issue (temporary!) + + # 🚀 Hack to fix yarn version issue (temporary!) brew upgrade yarn || true - - # install html report too + + # 📦 Install HTML report tool yarn global add yarn-audit-html - - # create JSON report - rm -f yarn-audit.json - yarn audit --groups dependencies --json > yarn-audit.json || true # ignore return code - - # create HTML report - rm -f yarn-audit.html - cat yarn-audit.json | yarn-audit-html -o yarn-audit.html - - AUDIT_RESUME=$(tail -1 yarn-audit.json) - - LOW_REGEX="\"low\":([0-9]+)" - if [[ ${AUDIT_RESUME} =~ ${LOW_REGEX} ]]; then - LOW_COUNT=${BASH_REMATCH[1]} - fi - MODERATE_REGEX="\"moderate\":([0-9]+)" - if [[ ${AUDIT_RESUME} =~ ${MODERATE_REGEX} ]]; then - MODERATE_COUNT=${BASH_REMATCH[1]} - fi + # 📂 Define the export directory for all reports + export CM_EXPORT_DIR="build/reports" + AUDIT_EXPORT_DIR="$CM_EXPORT_DIR/yarn-audit-reports" + mkdir -p "$AUDIT_EXPORT_DIR" - HIGH_REGEX="\"high\":([0-9]+)" - if [[ ${AUDIT_RESUME} =~ ${HIGH_REGEX} ]]; then - HIGH_COUNT=${BASH_REMATCH[1]} - fi + # 📂 Define aggregated JSON file + AGGREGATED_JSON="$AUDIT_EXPORT_DIR/yarn-audit.json" + TEMP_JSON="$AUDIT_EXPORT_DIR/temp-audit.json" + + # Initialize the temp JSON file + echo "" > "$TEMP_JSON" + + # 🔍 Function to perform audit in a given directory + auditing() { + local FILE="$1" + local DIR=$(dirname "$FILE") + local FOLDER_NAME=$(basename "$DIR") + + [ "$DIR" = "." ] && FOLDER_NAME="package" + + echo "📂 Auditing package in: $DIR ($FILE)" 1>&2 + + DIR=$(realpath "$DIR" 2>/dev/null || (cd "$DIR" && pwd)) + + if [ ! -d "$DIR" ]; then + echo "⚠️ Warning: Directory $DIR is invalid. Skipping..." 1>&2 + return + fi + + echo "🛠️ Running yarn audit in $DIR..." 1>&2 + ( + cd "$DIR" && + rm -f yarn-audit.json && + yarn audit --groups dependencies --json > yarn-audit.json || true + ) + + echo "📊 Generating HTML report..." 1>&2 + ( + cd "$DIR" && + rm -f yarn-audit.html && + cat yarn-audit.json | yarn-audit-html -o yarn-audit.html || true + ) + + AUDIT_RESUME=$(jq -c 'select(.type == "auditSummary")' "$DIR/yarn-audit.json" | tail -n 1) + if [ -z "$AUDIT_RESUME" ]; then + echo "⚠️ Warning: No audit summary found. Skipping..." 1>&2 + return + fi - CRITICAL_REGEX="\"critical\":([0-9]+)" - if [[ ${AUDIT_RESUME} =~ ${CRITICAL_REGEX} ]]; then - CRITICAL_COUNT=${BASH_REMATCH[1]} + LOW_COUNT=$(echo "$AUDIT_RESUME" | jq -r '.data.vulnerabilities.low // 0') + MODERATE_COUNT=$(echo "$AUDIT_RESUME" | jq -r '.data.vulnerabilities.moderate // 0') + HIGH_COUNT=$(echo "$AUDIT_RESUME" | jq -r '.data.vulnerabilities.high // 0') + CRITICAL_COUNT=$(echo "$AUDIT_RESUME" | jq -r '.data.vulnerabilities.critical // 0') + + LOW_COUNT=$(echo "$LOW_COUNT" | tr -d -c '0-9') + MODERATE_COUNT=$(echo "$MODERATE_COUNT" | tr -d -c '0-9') + HIGH_COUNT=$(echo "$HIGH_COUNT" | tr -d -c '0-9') + CRITICAL_COUNT=$(echo "$CRITICAL_COUNT" | tr -d -c '0-9') + + PACKAGE_NAME=$(echo "$FILE" | sed 's#./##' | sed 's#/#-#g' | sed 's#package.json##' | sed 's#-$##') + if [ -z "$PACKAGE_NAME" ]; then PACKAGE_NAME="root-package"; fi + + echo "ici+$PACKAGE_NAME" + + cp "$DIR/yarn-audit.html" "$AUDIT_EXPORT_DIR/yarn-audit-${PACKAGE_NAME}.html" + cp "$DIR/yarn-audit.json" "$AUDIT_EXPORT_DIR/yarn-audit-${PACKAGE_NAME}.json" + + echo " {" >> "$TEMP_JSON" + echo " \"package\": \"$PACKAGE_NAME\"," >> "$TEMP_JSON" + echo " \"package_json_path\": \"$FILE\"," >> "$TEMP_JSON" + echo " \"low\": ${LOW_COUNT:-0}," >> "$TEMP_JSON" + echo " \"moderate\": ${MODERATE_COUNT:-0}," >> "$TEMP_JSON" + echo " \"high\": ${HIGH_COUNT:-0}," >> "$TEMP_JSON" + echo " \"critical\": ${CRITICAL_COUNT:-0}" >> "$TEMP_JSON" + echo " }," >> "$TEMP_JSON" + } + + echo "🔎 Find and audit all package.json files in subdirectories" + find . -type f -name "package.json" ! -path "*/node_modules/*" | while read -r FILE; do + auditing "$FILE" + done + + echo "✅ Fix trailing comma issue and properly format JSON" + temp_fixed="$TEMP_JSON.fixed" + echo "[" > "$temp_fixed" + sed '$ s/,$//' "$TEMP_JSON" >> "$temp_fixed" # Remove last comma + echo "]" >> "$temp_fixed" + mv "$temp_fixed" "$TEMP_JSON" + + if [ ! -s "$TEMP_JSON" ]; then + echo "[]" > "$TEMP_JSON" # Ensure it's at least an empty JSON array fi - rm -f yarn-audit.txt - echo -e "Audit - vulnerabilities: \n\tlow: ${LOW_COUNT}\n\tmoderate: ${MODERATE_COUNT}\n\thigh: ${HIGH_COUNT}\n\tcritical: ${CRITICAL_COUNT}" > yarn-audit.txt - - cat yarn-audit.txt - - cp ./yarn-audit.html "$CM_EXPORT_DIR/yarn-audit.html" - cp ./yarn-audit.txt "$CM_EXPORT_DIR/yarn-audit.txt" - - # export vulnerability count for further use - echo "HIGH_COUNT=$HIGH_COUNT" >> $CM_ENV - echo "CRITICAL_COUNT=$CRITICAL_COUNT" >> $CM_ENV - - echo "Audit yarn packages finished successfully" \ No newline at end of file + echo "🔢 Compute totals using jq" + TOTALS=$(jq ' + reduce .[] as $pkg + ({"low": 0, "moderate": 0, "high": 0, "critical": 0}; + { + "low": (.low + ($pkg.low // 0)), + "moderate": (.moderate + ($pkg.moderate // 0)), + "high": (.high + ($pkg.high // 0)), + "critical": (.critical + ($pkg.critical // 0)) + })' $TEMP_JSON) + + echo "📂 Write aggregated JSON report" + echo "{" > "$AGGREGATED_JSON" + echo " \"type\": \"yarn audit\"," >> "$AGGREGATED_JSON" + echo " \"total\": $TOTALS," >> "$AGGREGATED_JSON" + echo " \"details\": " >> "$AGGREGATED_JSON" + + jq '.' "$TEMP_JSON" >> "$AGGREGATED_JSON" + + echo "}" >> "$AGGREGATED_JSON" + + rm "$TEMP_JSON" + + echo "🎉 Audit of all yarn packages completed successfully" + + # Extract high and critical vulnerability counts + LOW_COUNT=$(jq -r '.total.low' $AGGREGATED_JSON) + MODERATE_COUNT=$(jq -r '.total.moderate' $AGGREGATED_JSON) + HIGH_COUNT=$(jq -r '.total.high' $AGGREGATED_JSON) + CRITICAL_COUNT=$(jq -r '.total.critical' $AGGREGATED_JSON) + + # Export vulnerability count for further use + echo "LOW_COUNT=$LOW_COUNT" #>> $CM_ENV + echo "MODERATE_COUNT=$MODERATE_COUNT" #>> $CM_ENV + echo "HIGH_COUNT=$HIGH_COUNT" #>> $CM_ENV + echo "CRITICAL_COUNT=$CRITICAL_COUNT" #>> $CM_ENV + + #!/bin/bash + + # Define color codes + RESET="\033[0m" + BOLD="\033[1m" + RED="\033[31m" + YELLOW="\033[33m" + BLUE="\033[34m" + GREEN="\033[32m" + + # Function to truncate long package names + truncate_package_name() { + local name="$1" + local max_length=50 # Maximum allowed length before truncating + if [ ${#name} -gt $max_length ]; then + echo "${name:0:$((max_length-3))}..." # Truncate and add "..." + else + echo "$name" + fi + } + + # Function to display formatted table + print_table() { + local headers=("$@") # Get headers as array + printf "${BOLD}%-55s %-10s %-10s %-10s %-10s${RESET}\n" "${headers[@]}" + printf -- "----------------------------------------------------------------------\n" + + while read -r line; do + IFS=$'\t' read -r package low moderate high critical <<< "$line" + package=$(truncate_package_name "$package") # Truncate if necessary + printf "%-55s ${GREEN}%-10s${RESET} ${BLUE}%-10s${RESET} ${YELLOW}%-10s${RESET} ${RED}%-10s${RESET}\n" "$package" "$low" "$moderate" "$high" "$critical" + done + } + + # Read JSON and extract totals + TOTALS=$(jq -r '.total | ["Total", .low, .moderate, .high, .critical] | @tsv' "$AGGREGATED_JSON") + + # Display vulnerability summary + echo "${BOLD}Vulnerability Summary:${RESET}" + print_table "Package" "Low" "Moderate" "High" "Critical" <<< "$TOTALS" + + # Read JSON details and extract vulnerabilities per package + DETAILS=$(jq -r '.details[] | [.package, .low, .moderate, .high, .critical] | @tsv' "$AGGREGATED_JSON") + + # Display detailed report + echo "\n${BOLD}Detailed Vulnerability Report:${RESET}" + print_table "Package" "Low" "Moderate" "High" "Critical" <<< "$DETAILS" + + # Send a message to Microsoft Teams using a webhook URL + sendToTeams() { + local webhookUrl="$1" + local message="$2" + + # Check if required parameters are provided + if [ -z "$webhookUrl" ] || [ -z "$message" ]; then + echo "🚨 Error: Both Teams Webhook URL and message are required." 1>&2 + echo "Usage: sendToTeams " 1>&2 + return 1 + fi + + # Create JSON payload + local payload + payload=$(jq -n --arg text "$message" '{text: $text}') + + # Send POST request to Microsoft Teams webhook + local response + response=$(curl -s -o /dev/null -w "%{http_code}" -H "Content-Type: application/json" \ + -d "$payload" "$webhookUrl") + + # Check the response status + if [ "$response" -eq 200 ]; then + echo "✅ Message sent to Microsoft Teams successfully." 1>&2 + else + echo "🚨 Failed to send message to Microsoft Teams: HTTP $response." 1>&2 + return 1 + fi + } + # Function to send vulnerability report to Teams + sendVulnerabilityReportToTeams() { + local webhook_url=$1 + + # File paths + local base_path="build/reports/html-security-reports" + local html_result_temp="$base_path/security_report_temp.html" + local html_result_final="$base_path/yarn_audits.html" + + # Ensure base directory exists + mkdir -p "$base_path" + mkdir -p "$base_path/source" + + # HTML Table Structure + local html_top="" + local html_bottom="
Yarn audit - PackageLowModerateHighCritical
" + + # Helper function to write to a file + write_to_file() { + local file_path=$1 + local content=$2 + local append=$3 + + if [ "$append" = true ]; then + echo "$content" >> "$file_path" + else + echo "$content" > "$file_path" + fi + } + + # Start HTML file + write_to_file "$html_result_temp" "$html_top" + + # Extract total vulnerabilities + HIGH_COUNT=$(jq -r '.total.high' $AGGREGATED_JSON) + CRITICAL_COUNT=$(jq -r '.total.critical' $AGGREGATED_JSON) + + # Add total vulnerabilities as a summary row + local total_row="TOTAL$(jq -r '.total.low' $AGGREGATED_JSON)$(jq -r '.total.moderate' $AGGREGATED_JSON)$HIGH_COUNT$CRITICAL_COUNT" + write_to_file "$html_result_temp" "$total_row" true + + # Extract each package's vulnerabilities and add them to the table + jq -r '.details[] | "" + .package + "" + (.low|tostring) + "" + (.moderate|tostring) + "" + (.high|tostring) + "" + (.critical|tostring) + ""' $AGGREGATED_JSON >> "$html_result_temp" + + # Close the HTML table + write_to_file "$html_result_temp" "$html_bottom" true + + # Convert HTML to single-line format + local html_content=$(tr -d '\n' < "$html_result_temp") + write_to_file "$html_result_final" "$html_content" + + echo "✅ Security report generated successfully at: $html_result_final" 1>&2 + rm -f "$html_result_temp" + mv "$AGGREGATED_JSON" "$base_path/source" + + # Send to Teams + sendToTeams "$webhook_url" "$html_content" + } + + sendVulnerabilityReportToTeams "$TEAMS_SECURITY_WEBHOOCKS" From b216f77f861c163b7b5b796b8fd3f4ce117fb0ae Mon Sep 17 00:00:00 2001 From: "@odemolliens" Date: Wed, 26 Feb 2025 11:01:35 +0100 Subject: [PATCH 2/3] Update steps.md --- docs/steps.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/steps.md b/docs/steps.md index 38badf0..8a09017 100644 --- a/docs/steps.md +++ b/docs/steps.md @@ -308,6 +308,10 @@ Audit yarn packages | Bitrise | :white_check_mark:| | Codemagic|:white_check_mark:| +| 🔑 Variable | 📝 Description | Default value | Expected value | +|---------|------------------------------------------|---------------|---| +| TEAMS_SECURITY_WEBHOOCKS | Url of the webhook on Team's side | || + ## audit_export_report Will export generated files generated during audit packages steps From c2f20978f999fd3f76c564b76ccab127742a70fa Mon Sep 17 00:00:00 2001 From: "@odemolliens" Date: Wed, 26 Feb 2025 11:02:20 +0100 Subject: [PATCH 3/3] Update step.yml --- SIDI/steps/_audit_packages_yarn/codemagic/step.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/SIDI/steps/_audit_packages_yarn/codemagic/step.yml b/SIDI/steps/_audit_packages_yarn/codemagic/step.yml index dc83df5..e6e0dfd 100644 --- a/SIDI/steps/_audit_packages_yarn/codemagic/step.yml +++ b/SIDI/steps/_audit_packages_yarn/codemagic/step.yml @@ -72,8 +72,6 @@ script: | PACKAGE_NAME=$(echo "$FILE" | sed 's#./##' | sed 's#/#-#g' | sed 's#package.json##' | sed 's#-$##') if [ -z "$PACKAGE_NAME" ]; then PACKAGE_NAME="root-package"; fi - echo "ici+$PACKAGE_NAME" - cp "$DIR/yarn-audit.html" "$AUDIT_EXPORT_DIR/yarn-audit-${PACKAGE_NAME}.html" cp "$DIR/yarn-audit.json" "$AUDIT_EXPORT_DIR/yarn-audit-${PACKAGE_NAME}.json"