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.0withargs '--entrypoint=', because the image's entrypoint isveridorand 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 apython:3.12-slimagent into a virtual environment, or the zipapp. The pip line namesveridor==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
environmententries above.VERIDOR_BASE_URLempty uses the configuration's owntarget.base_url;VERIDOR_BASELINEempty 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 aswitchgives each code its result:error()for 1, 2, 70, 130, 143 and anything unknown;unstable()for 3 only whenVERIDOR_ALLOW_UNMEASUREDis'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.
junitwithskipMarkingBuildUnstable: true, so a failed test case cannot turn an exit-0 build UNSTABLE;archiveArtifacts; optionally Warnings NGrecordIssueswith the SARIF parser and emptyqualityGates, so it cannot change the result the gate set.
Azure Pipelines¶
ci/azure-pipelines.yml.
- Install. Active:
UsePythonVersion@03.12, thenpip installon./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 anENTRYPOINTwhose user can rungroupadd, and the Veridor image meets neither. The header showsdocker 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 acceptstrue,True,falseorFalseforVERIDOR_ALLOW_UNMEASUREDand refuses anything else, includingTRUEandFALSE, with exit2. The step'senv: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 anyVERIDOR_*value that still reads$(...)with exit2before running Veridor. - Exit
3. Only withVERIDOR_ALLOW_UNMEASURED: "true"does the step print##vso[task.complete result=SucceededWithIssues;]and exit0; otherwise it exits3. - Reports.
PublishTestResults@2withfailTaskOnFailedTests: 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 namedCodeAnalysisLogs, 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, thenpip installon./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: variablesabove.StartBuildcan override them, so the IAM denies in who may set what are part of the gate. - Credentials.
env: secrets-managerorenv: parameter-store, neverenv: variables, which is plain text in the buildspec. - Exit
3always 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 underveridor-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:
- End the step with Veridor's code. Never
|| true, neverexit 0after the gate. If your platform has a warning state, map only3to it, and only by an explicit switch in the file. - 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.
- 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. - Credentials as
VERIDOR_*environment variables, from the platform's secret store. An unset or empty one is exit2before anything is sent. - 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, whatskipMarkingBuildUnstabledoes to the result, Warnings NG's SARIF parsing,keepUndefinedParameters. - Azure. Any run.
task.complete result=SucceededWithIssuesfollowed byexit 0; the export of thevariables: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.
exitinside a buildspec 0.2 command, which runs in one shared shell; report and artifact upload after a failed build;runtime-versionspython 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: busyboxsh, Debian'sdashandbash, in docker.
Related: exit codes, reports, GitLab CI, install.