Blog

DarkMoon in GitHub Actions: autonomous pentest in the pull request

A Marketplace action that autodetects edition, launches or attaches to a campaign, streams to completion, writes a severity table to the job summary, uploads SARIF to Code scanning, comments the result on the PR, and fails from findings, never from an exit code. Tokens masked, evidence stripped.

· 7 min read

The DarkMoon GitHub Action runs an autonomous assessment as part of a workflow and reports it where the work happens: a severity table in the run summary, a comment on the pull request, and SARIF uploaded to Code scanning. It is on the GitHub Marketplace as ASCIT31/darkmoon-action@v1.

Add it to a workflow

- name: DarkMoon pentest
  uses: ASCIT31/darkmoon-action@v1
  with:
    target: https://staging.example.com
    base-url: https://darkmoon.internal
    api-token: ${{ secrets.DARKMOON_TOKEN }}
    fail-on: critical,high
    report-format: sarif
    post-report: true
    github-token: ${{ secrets.GITHUB_TOKEN }}

What it does

  • Autodetects the edition: Pro through GET /api/v1/system/info, otherwise the open-source CLI.
  • Launches a campaign, or attaches to an existing one with campaign-id.
  • Tracks to completion: server-sent events on Pro, polling on OSS. A run past timeout-ms is a failure, never a pass.
  • Writes a severity table to $GITHUB_STEP_SUMMARY.
  • Optionally synthesises SARIF for the Security, Code scanning tab.
  • Optionally comments the summary on the pull request.

Fails from findings, not from an exit code

The job result comes from the findings and your fail-on policy, surfaced as policy-failed. Outputs include campaign-id, edition, overall-risk, total-findings, per-severity counts, exploited, confirmed, unconfirmed, remediated and report-path, so later steps can branch on the real result.

Safety defaults

Tokens and passwords are masked with core.setSecret() before first use. The report artifact is the redacted body, the findings JSON carries evidence: null, and strings are scrubbed twice. A safety input, refuse-insecure-default, refuses to run against a Pro instance whose admin is still on the default password.

See the integrations catalog and the GitLab component for the same idea on GitLab.

Run it against your own lab

Darkmoon is open source (GPL-3.0) and self hosted. Clone it, point it at a target you own, and read every line.