# dagsec for GitLab CI: flags leaked secrets and vulnerable dependencies on every merge request.
#
# 1. Add a masked CI/CD variable DAGSEC_API_KEY (create a key at https://app.dagsec.net/keys).
# 2. Optional, for the merge request comment: add a masked variable DAGSEC_GITLAB_TOKEN holding a
#    project access token with the `api` scope and the Reporter role.
# 3. Include this file from .gitlab-ci.yml:
#
#      include:
#        - remote: https://dagsec.net/ci/gitlab.yml
#
# Override any variable below in your own `dagsec:` job, e.g. `variables: { DAGSEC_FAIL_UNDER: "60" }`.
# The scan runs on your runner; your code is never uploaded.

dagsec:
  stage: test
  image: alpine:3.20
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  variables:
    # dagsec reads git history, so it needs the full clone.
    GIT_DEPTH: "0"
    DAGSEC_SERVER: https://app.dagsec.net
    DAGSEC_FAIL_UNDER: "40"
    DAGSEC_PATH: "."
  # gitlab.com signs which project is running; the Free plan needs it to confirm the project is public.
  id_tokens:
    DAGSEC_ID_TOKEN:
      aud: dagsec
  script:
    - apk add --no-cache git curl jq >/dev/null
    - git config --global --add safe.directory "$CI_PROJECT_DIR"
    - |
      if [ -z "$DAGSEC_API_KEY" ]; then
        echo "dagsec: DAGSEC_API_KEY is not set. Add it under Settings > CI/CD > Variables (masked)."
        exit 1
      fi
      if [ "$(uname -m)" != "x86_64" ]; then
        echo "dagsec: the scanner runs on x86-64 Linux runners."
        exit 1
      fi
      # Only gitlab.com tokens can be verified; self-managed GitLab names the project instead (paid plans).
      if [ "$CI_SERVER_HOST" = "gitlab.com" ]; then
        identity="$DAGSEC_ID_TOKEN"
        query=""
      else
        identity=""
        query="gitlab_project=$CI_PROJECT_PATH"
      fi
      code=$(curl -sS -o /tmp/dagsec -w '%{http_code}' \
        -H "Authorization: Bearer $DAGSEC_API_KEY" \
        -H "X-GitLab-OIDC: $identity" \
        "$DAGSEC_SERVER/api/action/binary?$query") || { echo "dagsec: could not reach $DAGSEC_SERVER"; exit 1; }
      if [ "$code" != "200" ]; then
        echo "dagsec refused to run ($code): $(jq -r '.error // empty' /tmp/dagsec 2>/dev/null | head -c 300)"
        exit 1
      fi
      chmod +x /tmp/dagsec
    - |
      set +e
      /tmp/dagsec scan "$DAGSEC_PATH" --fail-under "$DAGSEC_FAIL_UNDER" --format markdown \
        ${CI_MERGE_REQUEST_DIFF_BASE_SHA:+--since "$CI_MERGE_REQUEST_DIFF_BASE_SHA"} > dagsec-report.md
      status=$?
      set -e
      cat dagsec-report.md
      if [ -n "$DAGSEC_GITLAB_TOKEN" ] && [ "$status" != "2" ]; then
        marker='<!-- dagsec-report -->'
        notes="$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes"
        body=$(printf '%s\n%s' "$marker" "$(cat dagsec-report.md)" | jq -Rs '{body: .}')
        existing=$(curl -sS -H "PRIVATE-TOKEN: $DAGSEC_GITLAB_TOKEN" "$notes?per_page=100&sort=asc" \
          | jq -r --arg m "$marker" '[.[]? | select(.body | startswith($m))][0].id // empty')
        if [ -n "$existing" ]; then
          curl -sS -o /dev/null -f -X PUT -H "PRIVATE-TOKEN: $DAGSEC_GITLAB_TOKEN" -H "Content-Type: application/json" \
            --data "$body" "$notes/$existing" || echo "dagsec: could not update the merge request comment"
        else
          curl -sS -o /dev/null -f -X POST -H "PRIVATE-TOKEN: $DAGSEC_GITLAB_TOKEN" -H "Content-Type: application/json" \
            --data "$body" "$notes" || echo "dagsec: could not post the merge request comment"
        fi
      fi
      exit $status
  artifacts:
    when: always
    expose_as: dagsec report
    paths:
      - dagsec-report.md
