Skip to main content
Integrate Binarly security scanning into your GitLab CI/CD pipeline.

Configuration File

Create .gitlab-ci.yml:
stages:
  - build
  - scan

variables:
  # Replace {slug} with your organization's tenant identifier
  BINARLY_AUTH_URL: https://auth-{slug}.binarly.cloud/realms/BinarlyRealm/protocol/openid-connect/token
  BINARLY_API_URL: https://dashboard-{slug}.binarly.cloud

build:
  stage: build
  script:
    - make firmware
  artifacts:
    paths:
      - build/firmware.bin

binarly-scan:
  stage: scan
  image: curlimages/curl:latest
  needs:
    - build
  before_script:
    - apk add --no-cache jq
  script:
    # Authenticate
    - |
      TOKEN=$(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" | jq -r '.access_token')

    # Generate 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')

    # Upload binary
    - curl -s -X PUT --data-binary @./build/firmware.bin "$UPLOAD_URL"

    # Finalize upload
    - |
      IMAGE_RES=$(curl -s -H "Authorization: Bearer $TOKEN" \
        -F "tempFileId=$TEMP_ID" \
        -F "imageName=GitLab Pipeline $CI_PIPELINE_ID" \
        -F "version=$CI_COMMIT_SHORT_SHA" \
        -F "file=@./build/firmware.bin" \
        "$BINARLY_API_URL/api/v4/products/${BINARLY_PRODUCT_ID}/images:upload")
      IMAGE_ID=$(echo $IMAGE_RES | jq -r '.id')
      echo "Image ID: $IMAGE_ID"

    # Poll for completion
    - |
      STATUS="running"
      while [ "$STATUS" != "completed" ]; 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 "Scan status: $STATUS"
        [ "$STATUS" == "failed" ] && exit 1
      done
      echo "Scan completed successfully"

    # Verify Results
    - |
      echo "Binarly Security Verification"

      # Configuration
      FAIL_ON_STATUS="${FAIL_ON_STATUS:-new,inProgress}"
      echo "Failing on statuses: $FAIL_ON_STATUS"

      # Get all images
      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)"

      # Compare if multiple images exist
      if [ "$IMAGE_COUNT" -gt 1 ]; then
        # Sort images by createTime descending
        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: $PREVIOUS_ID → $LATEST_ID"

        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')

        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')

        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 "Resolved: $RESOLVED | Unchanged: $UNCHANGED | New: $NEW_ISSUES"
      fi

      # Check for findings matching configured statuses (using 'in' comparator with 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 "Total actionable findings: $TOTAL_ACTIONABLE"

      if [ "$TOTAL_ACTIONABLE" -gt 0 ]; then
        echo "BUILD FAILED: $TOTAL_ACTIONABLE actionable finding(s)"
        exit 1
      fi
      echo "BUILD PASSED: No actionable findings"

Required Variables

Configure in Settings → CI/CD → Variables:
VariableFlagsDescription
BINARLY_CLIENT_IDProtected, MaskedKeycloak API Client ID
BINARLY_CLIENT_SECRETProtected, MaskedKeycloak API Client Secret
BINARLY_PRODUCT_IDProtectedTarget product ID

GitLab-Specific Variables

The script uses these built-in GitLab variables:
  • $CI_PIPELINE_ID – Unique pipeline identifier
  • $CI_COMMIT_SHORT_SHA – Short commit hash for version tracking
For detailed explanation of the verification logic, see Verify Results.