# Droidwatch CircleCI Orb — inline source.
#
# To use during development, paste this whole document under `orbs:` in your
# `.circleci/config.yml` via the inline-orb syntax:
#
#   orbs:
#     droidwatch:
#       <<paste this file's contents here>>
#
# Once published to the public CircleCI Orb registry, simply reference:
#
#   orbs:
#     droidwatch: anthropics/droidwatch@1.0.0
#
# See ./PUBLISH.md for the publishing workflow.

version: 2.1

description: |
  Scan an Android APK with Droidwatch (https://droidwatch.app). Independent
  ML model (F1 0.9873) plus 50+ rules including banking trojan, overlay
  attack and exfiltration detection. Fails the build if the verdict matches
  your gate policy.

display:
  home_url:    "https://droidwatch.app"
  source_url:  "https://github.com/anthropics/droidwatch/tree/main/integrations/circleci"

# ── Reusable Executor ────────────────────────────────────────────────────────
executors:
  default:
    description: "Slim image with curl + jq pre-installed."
    docker:
      - image: cimg/base:current

# ── Reusable Commands ────────────────────────────────────────────────────────
commands:
  scan:
    description: "Upload an APK to Droidwatch, wait for the verdict, gate the build."
    parameters:
      apk_path:
        type: string
        description: "Path to the .apk / .aab / .xapk / .dex to scan."
      api_key:
        type: env_var_name
        default: DROIDWATCH_API_KEY
        description: "Name of the env var that contains the Droidwatch API key."
      server:
        type: string
        default: "https://droidwatch.app"
        description: "Droidwatch base URL. Override for self-hosted."
      fail_on:
        type: string
        default: "Malicious"
        description: |
          Pipe-separated verdicts that fail the build. Examples:
            "Malicious"                          (default — block confirmed malware)
            "Malicious|High Risk"                (block anything high-risk)
            "Malicious|High Risk|Suspicious"     (aggressive — fail unless Benign)
      timeout:
        type: integer
        default: 600
        description: "Max seconds to wait for the analysis to finish."
      poll_interval:
        type: integer
        default: 10
        description: "Seconds between job-status polls."
    steps:
      - run:
          name: Install curl + jq
          command: |
            if ! command -v jq >/dev/null 2>&1; then
              sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends jq
            fi

      - run:
          name: Droidwatch APK scan
          environment:
            DW_APK_PATH:        << parameters.apk_path >>
            DW_SERVER:          << parameters.server >>
            DW_FAIL_ON:         << parameters.fail_on >>
            DW_TIMEOUT:         "<< parameters.timeout >>"
            DW_POLL_INTERVAL:   "<< parameters.poll_interval >>"
            DW_OUTPUT_FILE:     /tmp/droidwatch.env
            _API_KEY_VAR:       << parameters.api_key >>
          command: |
            set -eu
            export DW_API_KEY="${!_API_KEY_VAR}"
            if [ -z "$DW_API_KEY" ]; then
              echo "ERROR: env var $_API_KEY_VAR is empty. Add it as a project / context env var." >&2
              exit 4
            fi
            runner="$(mktemp -t droidwatch-scan.XXXXXX.sh)"
            curl --fail --silent --show-error --location \
                 -o "$runner" "${DW_SERVER%/}/ci/lib/droidwatch-scan.sh"
            chmod +x "$runner"
            bash "$runner"
            rc=$?

            # Re-emit outputs into $BASH_ENV so downstream steps see them.
            if [ -f "$DW_OUTPUT_FILE" ]; then
              while IFS='=' read -r k v; do
                [ -z "$k" ] && continue
                upper=$(echo "$k" | tr '[:lower:]' '[:upper:]')
                echo "export DROIDWATCH_${upper}=\"$v\"" >> "$BASH_ENV"
              done < "$DW_OUTPUT_FILE"
            fi
            rm -f "$runner"
            exit $rc

      - store_artifacts:
          path: /tmp/droidwatch.env
          destination: droidwatch.env

# ── Reusable Jobs ────────────────────────────────────────────────────────────
jobs:
  scan_apk:
    description: "Pick up the APK from a workspace and scan it with Droidwatch."
    executor: default
    parameters:
      apk_path:
        type: string
      workspace_root:
        type: string
        default: "."
      fail_on:
        type: string
        default: "Malicious"
    steps:
      - attach_workspace:
          at: << parameters.workspace_root >>
      - scan:
          apk_path: << parameters.apk_path >>
          fail_on:  << parameters.fail_on >>

# ── Example consumer config (NOT part of the orb; for docs only) ─────────────
examples:
  basic_scan:
    description: "Scan the release APK after Gradle build."
    usage:
      version: 2.1
      orbs:
        droidwatch: anthropics/droidwatch@1.0.0
      jobs:
        build:
          docker:
            - image: cimg/android:2024.07-node
          steps:
            - checkout
            - run: ./gradlew assembleRelease
            - persist_to_workspace:
                root: .
                paths:
                  - app/build/outputs/apk/release/*.apk
      workflows:
        ci:
          jobs:
            - build
            - droidwatch/scan_apk:
                requires: [build]
                apk_path: app/build/outputs/apk/release/app-release.apk
                fail_on:  "Malicious|High Risk"
                context: droidwatch   # provides DROIDWATCH_API_KEY
