#!/bin/bash set -euo pipefail IN_JSON="${1:-scan_results.json}" OUT_MD="${2:-report.md}" if [[ ! -s "$IN_JSON" ]]; then echo "# Security Scan Report" > "$OUT_MD" echo "_No results available (empty input)._" >> "$OUT_MD" exit 0 fi BRANCH="${BRANCH_NAME:-${GIT_BRANCH:-unknown}}" SHA="${GIT_COMMIT:-unknown}" SHORT_SHA="${SHA:0:7}" TOTAL_FILES=$(jq 'length' "$IN_JSON") TOTAL_FINDINGS=$(jq '[.[].findings] | flatten | length' "$IN_JSON") LOW=$(jq '[.[].findings] | flatten | map(select(.severity=="LOW")) | length' "$IN_JSON") MED=$(jq '[.[].findings] | flatten | map(select(.severity=="MEDIUM")) | length' "$IN_JSON") HIGH=$(jq '[.[].findings] | flatten | map(select(.severity=="HIGH")) | length' "$IN_JSON") CRIT=$(jq '[.[].findings] | flatten | map(select(.severity=="CRITICAL")) | length' "$IN_JSON") { echo "# Security Scan Report (Ollama)" echo "" echo "- **Branch**: \`${BRANCH}\`" echo "- **Commit**: \`${SHORT_SHA}\`" echo "- **Files analyzed**: ${TOTAL_FILES}" echo "- **Total findings**: ${TOTAL_FINDINGS}" echo "" echo "### Severity Summary" echo "" echo "| Severity | Count |" echo "|-----------|------:|" echo "| CRITICAL | ${CRIT} |" echo "| HIGH | ${HIGH} |" echo "| MEDIUM | ${MED} |" echo "| LOW | ${LOW} |" echo "" echo "---" echo "## Per-File Findings" echo "" } > "$OUT_MD" file_count=$(jq 'length' "$IN_JSON") if [[ "$file_count" -eq 0 ]]; then echo "_No files were scanned._" >> "$OUT_MD" exit 0 fi # Helper: safe cell formatter via jq (strip newlines and excessive whitespace) # We do not export a function; we let jq do formatting inline per row. for i in $(seq 0 $((file_count-1))); do file_path=$(jq -r ".[$i].file" "$IN_JSON") findings_len=$(jq -r ".[$i].findings | length" "$IN_JSON") echo "### \`$file_path\`" >> "$OUT_MD" echo "" >> "$OUT_MD" if [[ "$findings_len" -eq 0 ]]; then echo "_No issues found._" >> "$OUT_MD" echo "" >> "$OUT_MD" continue fi echo "| Severity | Line | Rule ID | Description | Recommendation |" >> "$OUT_MD" echo "|----------|-----:|---------|-------------|----------------|" >> "$OUT_MD" # Notes: # - Replace CRLF and LF with spaces via gsub("\r?\n"; " ") # - Collapse all whitespace via gsub("[[:space:]]+"; " ") # - Trim leading/trailing spaces using sub and gsub jq -r ".[$i].findings[] | [ (.severity // \"\"), (if (.line|type==\"number\") then (.line|tostring) else \"\" end), (.rule_id // \"\"), ((.description // \"\") | gsub(\"\\r?\\n\"; \" \") | gsub(\"[[:space:]]+\"; \" \") | sub(\"^ \"; \"\") | sub(\" $\"; \"\")), ((.recommendation // \"\") | gsub(\"\\r?\\n\"; \" \") | gsub(\"[[:space:]]+\"; \" \") | sub(\"^ \"; \"\") | sub(\" $\"; \"\")) ] | \"| \" + (.[0]) + \" | \" + (.[1]) + \" | \" + (.[2]) + \" | \" + (.[3]) + \" | \" + (.[4]) + \" |\" " "$IN_JSON" >> "$OUT_MD" # References (if present) refs_count=$(jq ".[$i].findings | map(select(.references != null and (.references|length>0))) | length" "$IN_JSON") if [[ "$refs_count" -gt 0 ]]; then echo "" >> "$OUT_MD" echo "**References**:" >> "$OUT_MD" jq -r ".[$i].findings[] | select(.references != null) | .references[]? | \"- \" + tostring" "$IN_JSON" >> "$OUT_MD" fi echo "" >> "$OUT_MD" done echo "[INFO] Markdown report generated: $OUT_MD" ``