Skip to content

Jenkins, Azure Pipelines, CodeBuild, and any other CI

Veridor is one command and an exit code, so it runs in any CI that can run a process. The Veridor repository ships starting points for Jenkins (ci/Jenkinsfile), Azure Pipelines (ci/azure-pipelines.yml) and AWS CodeBuild (ci/buildspec.yml), next to the GitLab component and the GitHub Action.

Untested on their platforms, and not published

None of the Jenkins, Azure or CodeBuild files has run on its own platform, and each says UNTESTED in its header. No Jenkins controller, Azure agent or CodeBuild project is attached to the repository. What was executed instead is in the table below. The files also name the first release (0.1.0): its PyPI package, container image and zipapp do not exist yet, and the source repository is private.

What every template does

Each one runs:

veridor run --config <config> --output-dir <out> --fail-on <policy> \
  --require-complete \
  --json <out>/veridor-run.json \
  --report junit=<out>/veridor-junit.xml \
  --report sarif=<out>/veridor.sarif \
  --report markdown=<out>/veridor-summary.md \
  [--base-url ...] [--baseline ...]

(--baseline in the Jenkins and CodeBuild templates; the Azure template has no baseline setting.) Then it prints one line naming the exit code and what it means, and ends the step with Veridor's own code. Nothing uses || true, set +e, or an exit 0 after the gate, except the one opt-in exit 3 branch on Azure. Reports never decide the result: that is the exit code's job.

Who may set what

Whoever may set the base URL decides where every VERIDOR_* credential is sent. Veridor sends each identity's credential to the base-URL override, or to target.base_url in the configuration, and veridor run asks for no acknowledgement. A value someone can set when a build starts is therefore a way to collect every token without permission to read them. The Veridor repository's ci/README.md records the Jenkins and Azure gate shells, run with only that value changed, sending the owner, other-tenant and admin tokens to a capture server. The same goes for the settings that can weaken the gate: the fail-on policy, the baseline, the exit-3 switch and the configuration.

So none of them is a build-time or queue-time knob any more. They are fixed in the file, and change by a commit:

Platform Where the templates set them Settings What else to restrict
Jenkins environment { } entries in the Jenkinsfile. No build parameters. VERIDOR_CONFIG, VERIDOR_BASE_URL, VERIDOR_FAIL_ON, VERIDOR_BASELINE, VERIDOR_ALLOW_UNMEASURED An environment entry wins over a build parameter of the same name, including one added in the job's configuration (measured on Jenkins 2.568.3, below). Jenkins drops a parameter the job does not declare unless hudson.model.ParametersAction.keepUndefinedParameters is true (SECURITY-170, read in Jenkins' advisory): keep it unset. A variable the Jenkinsfile does not fix still reaches the gate from the agent, the controller's global properties or a parameter added in the job's configuration; keep those settings to people who may read the credentials.
Azure Pipelines The variables: block in azure-pipelines.yml. No runtime parameters. VERIDOR_CONFIG, VERIDOR_BASE_URL, VERIDOR_FAIL_ON, VERIDOR_ALLOW_UNMEASURED Azure documents that a YAML variable cannot be overridden at queue time and outranks a queue-time variable of the same name; do not also define these names in the pipeline settings. A name the YAML does not define can be added when queueing a run, through the UI or the Runs API, unless Limit variables that can be set at queue time is on for the organization or the project. Turn it on wherever someone who may queue a run must not read the credentials.
AWS CodeBuild env: variables in the buildspec VERIDOR_CONFIG, VERIDOR_FAIL_ON, VERIDOR_BASE_URL, VERIDOR_BASELINE A buildspec cannot fix them: codebuild:StartBuild can override every environment variable and the buildspec itself. Deny StartBuild requests that carry a buildspec (Null on codebuild:source.buildspec set to false) or any environment override (codebuild:environment.environmentVariables.name) to anyone who may start a build but not read the credentials. Deny any override, not a list of names. Read in AWS's documentation, not tried.

A fixed base URL is not the whole answer. Veridor follows http_proxy or https_proxy, in either case, for a target that is not loopback (proxies), so whoever may add such a variable when a build starts chooses where the credentials go too. The Veridor repository's ci/README.md records the Azure gate step, with the literal base URL http://staging.invalid:8080, sending 9 requests carrying all three tokens to a capture server named only by http_proxy or HTTP_PROXY, and 0 without it (measured outside Azure). PIP_INDEX_URL, PIP_EXTRA_INDEX_URL and PIP_FIND_LINKS pick the code a pip install then runs with the credentials, and BASH_ENV runs a file in any non-interactive bash. No list of names is complete, so the control is each platform's setting in the table above. The GitLab component removes the proxy variables itself unless asked; these three templates do not.

Exit-code handling

Code Meaning Jenkins build Azure step CodeBuild build
0 Nothing the fail-on policy gates on was found. A finding it does not gate on, such as one in the baseline, still gives 0 SUCCESS Succeeded succeeds
1 The gate failed on a finding FAILURE Failed fails
2 Refused: configuration, credential, path or coverage FAILURE Failed fails
3 No gating finding, but something went unmeasured FAILURE; UNSTABLE only with VERIDOR_ALLOW_UNMEASURED = 'true' in the Jenkinsfile Failed; SucceededWithIssues only with VERIDOR_ALLOW_UNMEASURED: "true" in the YAML fails: CodeBuild has no warning state
70 Veridor crashed; never a finding FAILURE Failed fails
130 / 143 Interrupted by SIGINT / SIGTERM; teardown ran first FAILURE Failed fails
other Not a Veridor code FAILURE Failed fails

Each template's exit-0 line names the policy and says that a finding it does not gate on also gives 0. It used to say "Every declared expectation held"; the CHANGELOG records the Azure and CodeBuild shells with the policy at none exiting 0 and printing that sentence under a run that recorded 1 vulnerable. See what exit 0 means.

This is what the templates instruct. How Jenkins, Azure and CodeBuild then render each result was not observed.

Jenkins

ci/Jenkinsfile, a declarative pipeline.

  • Install. Active: the Docker Pipeline agent runs the stage in ghcr.io/veridor-authz/veridor:0.1.0 with args '--entrypoint=', because the image's entrypoint is veridor and the plugin starts the container with a command of its own. The plugin passes -u <jenkins uid>:<gid>, so the workspace stays writable; do not add -u root. Alternatives in the header: pip in a python:3.12-slim agent into a virtual environment, or the zipapp. The pip line names veridor==0.1.0, which is not on PyPI and whose name is unclaimed there; until the first release, give it the path of a wheel built from the repository.
  • Settings. The five environment entries above. VERIDOR_BASE_URL empty uses the configuration's own target.base_url; VERIDOR_BASELINE empty means no baseline.
  • Gate. sh(returnStatus: true, script: '... exec veridor run ...'), so the exit code comes back to the pipeline instead of aborting the stage, then a switch gives each code its result: error() for 1, 2, 70, 130, 143 and anything unknown; unstable() for 3 only when VERIDOR_ALLOW_UNMEASURED is 'true', error() otherwise.
  • Credentials. withCredentials([string(credentialsId: ..., variable: 'VERIDOR_...')]), one per ${VERIDOR_*} reference. A binding to a credential that does not exist fails the build.
  • Reports. junit with skipMarkingBuildUnstable: true, so a failed test case cannot turn an exit-0 build UNSTABLE; archiveArtifacts; optionally Warnings NG recordIssues with the SARIF parser and empty qualityGates, so it cannot change the result the gate set.

Azure Pipelines

ci/azure-pipelines.yml.

  • Install. Active: UsePythonVersion@0 3.12, then pip install on ./REPLACE_WITH_THE_PATH_OF_A_VERIDOR_WHEEL.whl, which stops the job until you put in the path of a wheel built from the repository. The template does not name the PyPI package: the name is unclaimed there, so anyone could publish under it. A container job is not offered: Azure's container-job documentation requires an image without an ENTRYPOINT whose user can run groupadd, and the Veridor image meets neither. The header shows docker run --entrypoint "" from a bash step instead, and the zipapp.
  • Settings. The four variables: entries above, exported to the gate step's shell under the same names (documented by Azure, not observed). The step accepts true, True, false or False for VERIDOR_ALLOW_UNMEASURED and refuses anything else, including TRUE and FALSE, with exit 2. The step's env: maps only the output directory and the secrets: mapping a setting there from a parameter would reopen it to whoever queues the run.
  • Credentials. Secret pipeline variables, mapped one by one in the gate step's env:; Azure does not export secrets to scripts unless they are mapped. A mapped variable that was never defined reaches the script as its literal macro text $(NAME), not as an empty string, so the step refuses any VERIDOR_* value that still reads $(...) with exit 2 before running Veridor.
  • Exit 3. Only with VERIDOR_ALLOW_UNMEASURED: "true" does the step print ##vso[task.complete result=SucceededWithIssues;] and exit 0; otherwise it exits 3.
  • Reports. PublishTestResults@2 with failTaskOnFailedTests: false, so the test report cannot give the gate a second verdict; the Markdown summary via ##vso[task.uploadsummary]; SARIF and the run document in a build artifact named CodeAnalysisLogs, the name the SARIF SAST Scans Tab extension reads. Output goes to $(Build.ArtifactStagingDirectory)/veridor/.

The Azure ##vso[task.logissue] converter that used to live in ci/tools/ is gone and has no replacement; the template publishes JUnit, SARIF and the Markdown summary instead.

AWS CodeBuild

ci/buildspec.yml, buildspec version 0.2 with shell: bash.

  • Install. Active: runtime-versions: python: 3.12, then pip install on ./REPLACE_WITH_THE_PATH_OF_A_VERIDOR_WHEEL.whl, which stops the build until you put in the path of a wheel built from the repository, for the same reason as on Azure. The zipapp is the alternative. The container image as the project's build image was not checked at all.
  • Settings. The four env: variables above. StartBuild can override them, so the IAM denies in who may set what are part of the gate.
  • Credentials. env: secrets-manager or env: parameter-store, never env: variables, which is plain text in the buildspec.
  • Exit 3 always fails the build: CodeBuild has no warning state.
  • Reports. JUnit to a report group (file-format: JUNITXML); the run JSON, SARIF and Markdown summary as artifacts under veridor-out/.
  • CodeDeploy is a deployment service with no build phase; the gate belongs in CodeBuild, and in CodePipeline as a Test action whose provider is CodeBuild.
  • For a target inside a VPC, give the CodeBuild project VPC settings.

A downloaded zipapp

Each template's header offers the zipapp. A downloaded zipapp runs with every VERIDOR_* credential, so check it, after the first release, with the pinned provenance check on install, which accepts only an attestation signed by release.yml at the release's tag. SHA256SUMS comes from the same release page, so it detects a damaged download, not a substituted one.

Any other CI

Three ways to get Veridor onto a runner.

Route Needs Status
The container image a runner that can run a container; clear or bypass the veridor entrypoint if your CI supplies its own command Built and run locally from the repository's Dockerfile; no image is published
The zipapp (veridor-<version>.pyz) any Python 3.11+ Built and run locally from scripts/build_zipapp.py; not published
pip Python 3.11+, and a package index or a wheel Not on PyPI; install a wheel you built

Details for each are on install.

Then, in whatever shell your CI runs:

set -e
out=veridor-out
mkdir -p "$out"
trap 'echo "veridor-gate: SIGTERM received; waiting for veridor to finish its teardown." >&2' TERM
status=0
veridor run --config veridor.json --output-dir "$out" --require-complete \
  --json "$out/veridor-run.json" \
  --report junit="$out/veridor-junit.xml" \
  --report markdown="$out/veridor-summary.md" || status=$?
echo "veridor exited $status"
exit "$status"

If the target starts earlier in the same job and may not be listening yet, add --wait-for-target 60: Veridor holds its first request until the target's port accepts a TCP connection, for at most 60 seconds, and then runs either way. It never changes a verdict, and an accepted connection says only that a port is open. None of the Jenkins, Azure or CodeBuild templates passes it; see the configuration reference.

The rules the shipped templates follow, which a hand-written one should too:

  1. End the step with Veridor's code. Never || true, never exit 0 after the gate. If your platform has a warning state, map only 3 to it, and only by an explicit switch in the file.
  2. Keep the base URL and the gate settings out of anything a build trigger can set, and restrict who may add variables when a build starts: a proxy variable redirects every credential whatever the base URL says.
  3. Keep the shell alive on SIGTERM until Veridor exits, so teardown finishes and the exit line reaches the log. Measured in docker for the GitLab job: without the trap the job still ends 143, but the line never appears.
  4. Credentials as VERIDOR_* environment variables, from the platform's secret store. An unset or empty one is exit 2 before anything is sent.
  5. Publish the reports with the step's result, not instead of it. A test report that marks the build failed or unstable on its own gives the gate a second verdict.

Tested and untested

"Executed" means the thing ran and its result was observed. Recorded in the Veridor repository's ci/README.md, except where this page says it was re-run.

What How Result
Every template's gate shell Executed. tests/test_ci_configs.py GateShellRunTest (slow tier) runs the GitLab, Jenkins, Azure and CodeBuild gate shells with the host's sh -e, against an in-process demo server, with the real CLI. The fail-on value and allow-unmeasured switch each shell gets are the ones its own file ships Each exits 1 / 0 / 3 for vulnerable / fixed / unreachable and writes its reports. Azure exits 0 on 3 only when VERIDOR_ALLOW_UNMEASURED is true, and 2 for a value that is neither, and for an undefined $(VERIDOR_...) mapping. Controls: none or an opt-in value in any of the three files fails it. Re-run for this page: python3.12 -m unittest tests.test_ci_configs, 111 tests, OK (macOS, 2026-11-09, tree at commit 38025ed)
Who can set the gate settings Checked (QueueTimeSettingsTest, fast tier): the Jenkinsfile declares no build parameter and reads no params., and every setting its shell reads is an environment entry; the Azure pipeline has no runtime parameter, and every setting its gate step reads is a literal in variables:, not remapped in the step's env: Clean. Controls named: the old parameters block, an entry removed from environment, a params. read, the old baseUrl runtime parameter, an env: remap from a parameter
Every veridor command line Parsed by Veridor's own argument parser (fast tier): the rendered GitLab component, the Jenkinsfile, the Azure step, the buildspec All parse. Controls refused: a misspelt flag, a bad --fail-on, an unknown report format, a map-only flag
Azure YAML Schema-validated against service-schema.json from microsoft/azure-pipelines-vscode (fetched 2026-09-24) 0 errors; controls rejected. The schema types most scalars as strings, so it cannot catch every wrong value
Jenkinsfile Linted by Jenkins' declarative linter in jenkins/jenkins:lts-jdk21 (2.568.3), again on 2026-09-24 for the Jenkinsfile without build parameters; step parameters checked against its gdsl Validated; controls rejected. The linter does not check step names (junitz was accepted), and archiveArtifacts and the sarif tool were not in the gdsl, so they were not checked
Jenkins parameter vs environment Executed on that local Jenkins, 2026-09-24: pipelines built with VERIDOR_BASE_URL=http://collector.example, the parameter declared in the pipeline or added in the job's configuration, each with and without the environment entry With the entry, every parameterised build's sh saw an empty value; without it (the controls), it saw the collector. So the Jenkinsfile fixes every gate setting in environment and declares no build parameter
buildspec Structure-checked against the keys in AWS's buildspec reference; no public schema exists No problems; controls caught a misspelt phase, shell: zsh, an unknown report format
Shell hygiene shellcheck 0.11.0, -s sh, on every extracted gate shell Clean; control flagged

UNTESTED, because it needs the platform:

  • Jenkins. Any build. The Docker Pipeline plugin with --entrypoint=, withCredentials, what skipMarkingBuildUnstable does to the result, Warnings NG's SARIF parsing, keepUndefinedParameters.
  • Azure. Any run. task.complete result=SucceededWithIssues followed by exit 0; the export of the variables: block to the step's shell; that a queue-time variable cannot override it; Limit variables that can be set at queue time; task.uploadsummary; both publish tasks; secret mapping; fork builds.
  • CodeBuild. Any build. exit inside a buildspec 0.2 command, which runs in one shared shell; report and artifact upload after a failed build; runtime-versions python 3.12 on your build image; secrets-manager; the IAM condition keys.
  • pip from PyPI. The package is not published.
  • The gate-shell rows above ran under the host's sh (macOS). Only the GitLab end-to-end rows ran under other shells: busybox sh, Debian's dash and bash, in docker.

Related: exit codes, reports, GitLab CI, install.