Skip to main content
This bash script demonstrates the complete workflow using curl and jq. It works with any CI/CD system that supports shell scripts.

Prerequisites

  • curl and jq installed
  • Environment variables configured

Complete Script

#!/bin/bash
set -e

# Configuration (Set these as environment variables in your CI)
# BINARLY_CLIENT_ID="your-client-id"
# BINARLY_CLIENT_SECRET="your-client-secret"
# BINARLY_AUTH_URL="https://auth-{slug}.binarly.cloud/realms/BinarlyRealm/protocol/openid-connect/token"
# BINARLY_API_URL="https://dashboard-{slug}.binarly.cloud"
# BINARLY_FIRMWARE_FILE="./firmware.bin"
# BINARLY_PRODUCT_ID="prod_123456"

# 1. Authenticate (M2M)
echo "LOG: Authenticating..."
TOKEN_RES=$(curl -s --fail-with-body \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "client_id=${BINARLY_CLIENT_ID}" \
  --data-urlencode "client_secret=${BINARLY_CLIENT_SECRET}" \
  "${BINARLY_AUTH_URL}")
TOKEN=$(echo $TOKEN_RES | jq -r '.access_token')

# 2. Upload Firmware (3-Step Flow)
echo "LOG: Generating Upload URL..."
UPLOAD_RES=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
  "${BINARLY_API_URL}/api/v4/products/${BINARLY_PRODUCT_ID}/tempFiles:generateUploadUrl")
UPLOAD_URL=$(echo $UPLOAD_RES | jq -r '.uploadUrl')
TEMP_ID=$(echo $UPLOAD_RES | jq -r '.id')

echo "LOG: Uploading Binary..."
curl -s -X PUT --data-binary @${BINARLY_FIRMWARE_FILE} "${UPLOAD_URL}"

echo "LOG: Finalizing Upload..."
# Generate unique image name with timestamp to avoid conflicts
IMAGE_NAME="CI Build ${BUILD_NUMBER:-0}-$(date +%Y%m%d%H%M%S)"
IMAGE_RES=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
  -F "tempFileId=${TEMP_ID}" \
  -F "imageName=${IMAGE_NAME}" \
  -F "version=1.0.${BUILD_NUMBER:-0}" \
  -F "file=@${BINARLY_FIRMWARE_FILE}" \
  "${BINARLY_API_URL}/api/v4/products/${BINARLY_PRODUCT_ID}/images:upload")
IMAGE_ID=$(echo $IMAGE_RES | jq -r '.id')
if [ "$IMAGE_ID" == "null" ] || [ -z "$IMAGE_ID" ]; then
  echo "ERROR: Upload failed. Response: $IMAGE_RES"
  exit 1
fi
echo "LOG: Scan started for Image ID: ${IMAGE_ID}"

# 3. Poll for Results
echo "LOG: Waiting for scan to complete..."
while true; do
  sleep 30
  SCAN_RES=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
    "${BINARLY_API_URL}/api/v4/products/${BINARLY_PRODUCT_ID}/images/${IMAGE_ID}/scans?status=true")
  STATUS=$(echo $SCAN_RES | jq -r '.scans[0].latestScanState.type')
  echo "LOG: Scan status: ${STATUS}"
  
  # Check if scan is complete
  if [ "$STATUS" == "completed" ]; then
    echo "SUCCESS: Security analysis completed."
    break
  elif [ "$STATUS" == "failed" ]; then
    echo "ERROR: Scan failed"
    exit 1
  elif [ "$STATUS" != "running" ] && [ "$STATUS" != "queued" ] && [ "$STATUS" != "pending" ]; then
    echo "ERROR: Unexpected scan status: ${STATUS}"
    exit 1
  fi
  # Continue waiting for running/queued/pending states
done

Environment Variables

VariableDescription
BINARLY_CLIENT_IDKeycloak API Client ID
BINARLY_CLIENT_SECRETKeycloak API Client Secret
BINARLY_AUTH_URLAuthentication endpoint
BINARLY_API_URLAPI base URL
BINARLY_PRODUCT_IDTarget product ID
BINARLY_FIRMWARE_FILEPath to firmware binary
BUILD_NUMBERCI build number (optional)

Usage

# Set environment variables
export BINARLY_CLIENT_ID="my-api-client"
export BINARLY_CLIENT_SECRET="secret123"
export BINARLY_AUTH_URL="https://auth-{slug}.binarly.cloud/realms/BinarlyRealm/protocol/openid-connect/token"
export BINARLY_API_URL="https://dashboard-{slug}.binarly.cloud"
export BINARLY_PRODUCT_ID="01JQPGDX8QW0YJ0XEKV1EVG534"
export BINARLY_FIRMWARE_FILE="./build/firmware.bin"
export BUILD_NUMBER="42"

# Run script
./binarly-scan.sh

4. Verify Results

After the scan completes, verify the results and optionally fail the build based on findings.
#!/bin/bash
# Binarly CI/CD Verification Script
# Requires: TOKEN, BINARLY_API_URL, BINARLY_PRODUCT_ID, IMAGE_ID from upload
# Optional: FAIL_ON_STATUS (default: "new,inProgress")

set -e

# Configuration: Which statuses should fail the build
FAIL_ON_STATUS="${FAIL_ON_STATUS:-new,inProgress}"

echo "Binarly Security Verification"
echo "Failing on statuses: ${FAIL_ON_STATUS}"
echo ""

# 1. Get all images for the product
IMAGES_RES=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
  "${BINARLY_API_URL}/api/v4/products/${BINARLY_PRODUCT_ID}/images")
IMAGE_COUNT=$(echo "$IMAGES_RES" | jq '.images | length')
echo "Product has ${IMAGE_COUNT} image(s)"

# Initialize statistics
RESOLVED=0
UNCHANGED=0
NEW_ISSUES=0

# 2. If multiple images exist, compare latest with previous
if [ "$IMAGE_COUNT" -gt 1 ]; then
  # Sort images by createTime descending and get IDs of two most recent
  LATEST_ID=$(echo "$IMAGES_RES" | jq -r '[.images | sort_by(.createTime) | reverse][0][0].id')
  PREVIOUS_ID=$(echo "$IMAGES_RES" | jq -r '[.images | sort_by(.createTime) | reverse][0][1].id')
  
  echo "Comparing images:"
  echo "   Previous: ${PREVIOUS_ID}"
  echo "   Latest:   ${LATEST_ID}"
  echo ""
  
  # Count RESOLVED findings (only in previous image = left side)
  RESOLVED=$(curl -s -X POST \
    -H "Authorization: Bearer ${TOKEN}" \
    -H "Content-Type: application/json" \
    -d '{
      "filters": [
        {"field": "compareLeftImageId", "value": "'"${PREVIOUS_ID}"'", "comparator": "equals"},
        {"field": "compareRightImageId", "value": "'"${LATEST_ID}"'", "comparator": "equals"},
        {"field": "compareSide", "value": "left", "comparator": "equals"}
      ]
    }' \
    "${BINARLY_API_URL}/api/v4/grids/findings:gridList" | jq '.total // 0')
  
  # Count UNCHANGED findings (present in both images)
  UNCHANGED=$(curl -s -X POST \
    -H "Authorization: Bearer ${TOKEN}" \
    -H "Content-Type: application/json" \
    -d '{
      "filters": [
        {"field": "compareLeftImageId", "value": "'"${PREVIOUS_ID}"'", "comparator": "equals"},
        {"field": "compareRightImageId", "value": "'"${LATEST_ID}"'", "comparator": "equals"},
        {"field": "compareSide", "value": "both", "comparator": "equals"}
      ]
    }' \
    "${BINARLY_API_URL}/api/v4/grids/findings:gridList" | jq '.total // 0')
  
  # Count NEW findings (only in latest image = right side)
  NEW_ISSUES=$(curl -s -X POST \
    -H "Authorization: Bearer ${TOKEN}" \
    -H "Content-Type: application/json" \
    -d '{
      "filters": [
        {"field": "compareLeftImageId", "value": "'"${PREVIOUS_ID}"'", "comparator": "equals"},
        {"field": "compareRightImageId", "value": "'"${LATEST_ID}"'", "comparator": "equals"},
        {"field": "compareSide", "value": "right", "comparator": "equals"}
      ]
    }' \
    "${BINARLY_API_URL}/api/v4/grids/findings:gridList" | jq '.total // 0')
  
  echo "Comparison Statistics:"
  printf "  Resolved from previous:    %6d\n" "$RESOLVED"
  printf "  Unchanged (still present): %6d\n" "$UNCHANGED"
  printf "  Newly introduced:          %6d\n" "$NEW_ISSUES"
  echo ""
fi

# 3. Check for findings matching configured statuses (using 'in' comparator with array)
# Convert comma-separated statuses to JSON array
STATUSES_JSON=$(echo "$FAIL_ON_STATUS" | tr ',' '\n' | jq -R . | jq -s .)

# Use LATEST_ID if available (from comparison), otherwise use IMAGE_ID from upload step
CHECK_IMAGE_ID="${LATEST_ID:-${IMAGE_ID}}"

TOTAL_ACTIONABLE=$(curl -s -X POST \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": [
      {"field": "imageId", "value": "'"${CHECK_IMAGE_ID}"'", "comparator": "equals"},
      {"field": "issueStatus", "value": '"${STATUSES_JSON}"', "comparator": "in"}
    ]
  }' \
  "${BINARLY_API_URL}/api/v4/grids/findings:gridList" | jq '.total // 0')

echo "   Statuses checked: ${FAIL_ON_STATUS}"

echo ""
echo "Total actionable findings: ${TOTAL_ACTIONABLE}"
echo ""

# 4. Pass/Fail decision
if [ "$TOTAL_ACTIONABLE" -gt 0 ]; then
  echo "BUILD FAILED: ${TOTAL_ACTIONABLE} actionable finding(s)"
  exit 1
else
  echo "BUILD PASSED: No actionable findings"
  exit 0
fi
For detailed explanation of the verification logic, see Verify Results.