Skip to content

Set up GitLab

MopokeHQ works with gitlab.com and self-managed GitLab, including instances behind a firewall: the scan runs on your runners and only sends results out. You need a runner that can run Docker images (any shared or group runner with the Docker or Kubernetes executor) and outbound HTTPS from it to app.mopokehq.com and gitlab.boosoft.com.au:5050, where the public scanner image lives.

  1. In MopokeHQ, make a CI token: organisation menu, CI tokens, kind CI pipeline (Tokens and keys).
  2. In GitLab, open the group that holds your projects, then Settings, CI/CD, Variables, Add variable:
    • Key MOPOKE_TOKEN, value the token.
    • Masked on.
    • Protected off. Protected variables only reach pipelines on protected branches and tags, so merge request pipelines would not be scanned.

A group variable covers every project in the group and its subgroups. You can set it on a single project instead.

Add one line to each project’s .gitlab-ci.yml:

.gitlab-ci.yml
include:
- remote: https://app.mopokehq.com/ci/scan.yml

That adds one job, mopoke-scan. It:

  • runs on pipelines for the default branch, tags and merge requests, and only when MOPOKE_TOKEN is set;
  • runs in the .post stage with needs: [], so it starts with the pipeline and nothing waits for it;
  • checks dependencies, code, secrets, infrastructure files and licences (see Scanners), all inside the scanner image, and uploads the results;
  • never fails your pipeline, except a merge request that fails the merge gate when you have chosen to enforce it. A scanner or upload problem shows as a warning.

Run a pipeline on the default branch. The job log ends with a line per scanner and the upload. In MopokeHQ the repository appears on the Overview, and the Now screen fills as advisories match.

The first default-branch scan is the baseline: what it finds is existing work, listed and prioritised, but never counted against a merge request. From then on, merge requests are judged only on what they add.

Set these as CI/CD variables on the group or project. The full list is on Scanners.

Variable Use
MOPOKE_SCANNERS Which scanners run: a list like sbom,secrets, or removals like -licence,-iac.
MOPOKE_GATE enforce lets a failing merge gate fail the merge request pipeline. See Merge gate.
MOPOKE_LICENCE_POLICY A licence policy file. A repository can also commit .mopoke/licence-policy.json.
MOPOKE_SCANNER_IMAGE Your mirror of the scanner image, if your runners cannot reach the public one.

The main scan works on the checked-out code. To scan a container image your pipeline builds, add a job that runs after the build has pushed it:

.gitlab-ci.yml
mopoke-image:
extends: .mopoke-image
needs: [build]
variables:
MOPOKE_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

Images in the project’s own GitLab registry are read with the job’s registry login. For another registry, set MOPOKE_REGISTRY_USER and MOPOKE_REGISTRY_PASSWORD (masked). Set MOPOKE_IMAGE on this job only: on the whole pipeline, the main scan would look for an image that is not built yet.

MopokeHQ needs to know what is live. A problem in a deployed build is confirmed live; one only on a branch is possible. A fix closes a problem on its own only once MopokeHQ sees the fixed version deployed (see Priorities and deadlines). Use either of these.

Add the .mopoke-deploy snippet to each deploy job. It runs after the job succeeds, reports the commit as live in the job’s environment (CI_ENVIRONMENT_NAME, or production), and never fails the job:

.gitlab-ci.yml
deploy:
# your deploy job as it is
environment: production
after_script:
- !reference [.mopoke-deploy, script]

If the deploy ships an image the mopoke-image job scanned, name it here too, so the PHP version measured in that image counts as what the environment runs:

deploy:
variables:
MOPOKE_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
after_script:
- !reference [.mopoke-deploy, script]

GitLab can tell MopokeHQ about every successful deployment without any pipeline change.

  1. In MopokeHQ (owners and admins): Settings, Integrations, GitLab deployment webhook, Create webhook. Copy the URL and the secret token. The secret is shown once.
  2. In GitLab, on the group (group webhooks need GitLab Premium) or on each project: Settings, Webhooks, Add new webhook. Paste the URL and the secret token, and tick Deployment events only.
  3. Use Test, Deployment events. Last delivery on the MopokeHQ page updates.

Only successful deployments are recorded; repeats of the same delivery are ignored. Rotate secret keeps the URL and refuses the old secret until GitLab has the new one. A self-managed GitLab needs outbound HTTPS to app.mopokehq.com for this; where that is not allowed, use the deploy job snippet instead.

Every merge request pipeline that runs the scan is checked against the branch it targets. The job log shows the merge gate result (pass, warn or fail) and why.

MopokeHQ also keeps one comment on each merge request, edited in place as you push: what the change adds and fixes, new packages and any lookalike names, and the gate’s reasons. MopokeHQ holds no GitLab write access, so the comment is posted by the fix runner below, within 15 minutes of the pipeline.

On the Now screen, Open upgrade MR on an action asks for one merge request per repository that needs the upgrade. A scheduled job in one central project of your GitLab, the fix runner, collects the requests every 15 minutes, makes the change and opens the merge requests with its own GitLab token. That token stays in your GitLab’s CI/CD variables; MopokeHQ never sees it. The same job posts the merge request comments above.

The quickest way. An owner or admin opens Settings, Integrations, Fix runner and:

  1. Enters the GitLab address and a GitLab administrator’s personal access token (scope api, plus admin_mode if Admin Mode is on). MopokeHQ uses it for this one request and never stores or logs it.
  2. Ticks the groups the runner may open merge requests in (and any single projects outside them).
  3. Picks where the runner project goes.

MopokeHQ then creates a bot user (mopokehq-bot by default) with Developer access to those groups, a private runner project (<group>/mopokehq-fix-runner by default) with the include below, the bot’s token (api and write_repository, valid for a year) and a fresh CI token as masked, protected variables there, and a 15-minute schedule owned by the bot. It starts a first run.

The page shows the bot token’s expiry date: run the setup again before then to renew both tokens. It also shows the runner’s last run, and turns ember when the runner has not collected work for an hour.

  1. Create one private project, for example acme/mopokehq-fix-runner, with only this .gitlab-ci.yml:

    .gitlab-ci.yml
    include:
    - remote: https://app.mopokehq.com/ci/act.yml
  2. Make a GitLab token for the runner: scopes api and write_repository, from a user with Developer access to every group whose projects MopokeHQ should fix. A dedicated bot user’s personal access token covers several groups; a group access token covers only its own group. Give it an expiry date and a reminder.

  3. In the runner project, Settings, CI/CD, Variables, add both as Masked:

    Variable Value
    MOPOKE_TOKEN A MopokeHQ CI token
    MOPOKE_GITLAB_TOKEN The GitLab token from step 2
    MOPOKE_ACT_LIMIT Optional: fixes per run, 1 to 50 (default 5)
    MOPOKE_GITLAB_URL Optional: the GitLab address, if not the one running the job

    Protected is fine here: schedules run on the default branch, which is protected.

  4. Build, Pipeline schedules, New schedule: interval */15 * * * *, the default branch, active. Press Run pipeline once: with nothing queued the job prints No fixes queued.

The job runs only from a schedule, Run pipeline, a trigger or the API, and only once both variables are set; pushes to the project never run it. The fix runner image, gitlab.boosoft.com.au:5050/mopokehq/act:1, is public, so your runners pull it without a login.

Every change touches only the manifest, lockfile or Dockerfile, and no install, lifecycle or Composer scripts or plugins ever run. After the change the runner reads the lockfile again and requires every copy of the package to be at or above the fix; otherwise it reports why.

Project What the runner does
npm (package-lock.json) Updates the package in the lockfile; for a dependency of a dependency that still holds an old copy, adds an overrides entry
pnpm (pnpm-lock.yaml) The same, with pnpm’s overrides (in pnpm-workspace.yaml for pnpm 11)
Composer (composer.lock) composer require or composer update with dependencies, without installing; if another package holds it back, the failure names that package
PHP runtime Bumps FROM php:X.Y.Z in your Dockerfile when the minor version matches, keeping any suffix such as -fpm-alpine
Yarn and other ecosystems Not yet: upgrade by hand

Composer resolves for PHP 8.3 unless your composer.json sets config.platform.php; set it to the PHP you run in production.

Each fix goes on a branch named mopoke/upgrade-<package>-<version>, committed as MopokeHQ <[email protected]>, with a merge request labelled mopokehq and security that lists the advisories, the versions and an Open in MopokeHQ link. If a merge request from that branch is already open, the runner reports it and pushes nothing. CI must pass before you merge, as with any change.

For private registries, add what the tools need to the runner project as masked variables: NPM_TOKEN for an .npmrc that reads ${NPM_TOKEN}, or COMPOSER_AUTH for Composer.

The action on Now shows the runner’s reason with Try again. The usual ones:

Reason What to do
GitLab rejected MOPOKE_GITLAB_TOKEN (401) The token expired or was revoked. Make a new one (or run the setup in MopokeHQ again).
GitLab refused access to <project> (403) or can't find <project> (404) The token’s user is not a Developer in that project’s group, or the token lacks the api scope.
Could not push mopoke/upgrade-... Branch protection or push rules. The push rule “Check whether the commit author is a GitLab user” rejects [email protected]: turn it off for these projects, or allow mopoke/* branches.
<lockfile> still has <pkg> <old>, below <target> Something pins the old version. npm explain <pkg> or pnpm why <pkg> shows what; upgrade that, or add an override by hand.
<dep> requires <pkg> <constraint> Composer can’t move the package without a newer <dep>: upgrade <dep> first, then try again.
PHP is not pinned to an exact version in a Dockerfile here Pin the image (for example FROM php:8.3.12-fpm-alpine), or upgrade PHP wherever it is set.
pnpm refuses the version because of minimumReleaseAge The fix is newer than your release-age policy allows. Wait, or add an exclusion.
Already up to date The default branch already has the fix. Nothing to do.