RunnerLens reveals the tools your build inherits from its CI runner and shows which runner image changes can affect it.
Hosted CI runners include compilers, runtimes, SDKs, package managers, and build tools by default. A build can silently depend on those tools without explicitly installing them.
When the runner image changes, the repository may stay the same while the build starts behaving differently.
RunnerLens makes those dependencies visible.
- Getting Started
- Why RunnerLens
- Runner Dependency Receipts
- How It Works
- Compatibility
- Limitations
- Contributing
- License
Note
RunnerLens is experimental. Its command-line interface and receipt schema may change during pre-alpha development.
Install the local development package:
python -m pip install -e ".[dev]"
RunnerLens wraps an existing build or test command and writes a receipt to runnerlens-receipt.json by default:
runnerlens run -- make
Add --json to write the receipt as JSON on standard output. The wrapped
command's standard output goes to standard error in that mode, so the receipt
can be piped to a JSON tool without build output mixed in.
For a command that relies on setuid or setgid helpers, process tracing can
change its privilege behavior. Use --no-process-tree to run it without ptrace
and record only the wrapped executable, with the lower coverage marked in the
receipt:
runnerlens run --no-process-tree -- podman run --rm alpine echo hello
This option is currently CLI-only. The composite action wraps scripts in Bash and continues to use process-tree observation.
If a preceding workflow step provisioned a tool outside the checkout, declare that path as evidence so RunnerLens does not mistake it for a base-image tool:
runnerlens run --workflow-provisioned-path /opt/my-tools -- make
For a command running in a workflow container, declare that execution boundary:
runnerlens run --container -- make
RunnerLens preserves shell and utility executions as receipt events, but keeps routine launcher and support tools out of dependency output by default. Include them when investigating the full process trace:
runnerlens run --include-support-tools -- make
Inspect a saved receipt later with:
runnerlens show runnerlens-receipt.json
Compare a known-good receipt with a newer run:
runnerlens compare baseline-receipt.json runnerlens-receipt.json
Fail CI when a build introduces a new runner-provided or tool-cache dependency:
runnerlens check baseline-receipt.json runnerlens-receipt.json
runnerlens check returns an inconclusive exit status if either receipt did
not capture the full process tree. A root-only or fallback receipt cannot prove
that no child dependencies were added.
Check observed tools against a specific GitHub Ubuntu runner-image release:
runnerlens impact runnerlens-receipt.json
When available, runnerlens impact uses the image version recorded in the
receipt when inspecting the same image. When selecting a different target
image, pass --target-image-version explicitly because image versions are not
assumed to match across image families.
Image impact reports include observation coverage and warn when child-process dependencies may be missing.
Compare two runner-image releases, narrowed to the tools in your receipt:
runnerlens impact runnerlens-receipt.json --baseline-image-version 20260831.293.1 --target-image-version 20260920.314.1
Use the composite action after checkout and any intentional provisioning steps:
- uses: actions/checkout@v4
- uses: jad-fahmi/runnerlens@main
with:
command: python -m pytest
baseline: .runnerlens/accepted-receipt.json
workflow-provisioned-paths: /opt/my-tools:/usr/local/custom-sdkThe action writes runnerlens-receipt.json and publishes the human-readable
receipt to the job summary. To retain the JSON receipt between runs, upload it
with actions/upload-artifact or commit it as a reviewed baseline.
For commands that require setuid or setgid helpers, set
process-tree-observation: false to avoid ptrace changing their privileges.
Because the action wraps scripts in Bash, this mode records only the Bash
wrapper and does not identify tools invoked inside the script. Use the CLI's
--no-process-tree mode for a direct command receipt when possible.
When baseline is set, a successful build fails if it introduces a new
runner-provided or tool-cache dependency, or if the comparison is inconclusive
because either receipt lacks complete process-tree observation. A failing
wrapped build retains its own exit status regardless of the baseline result.
workflow-provisioned-paths accepts colon-separated Linux paths from earlier
workflow steps and records matching executables as workflow-provisioned.
Set container: true only for commands running in a workflow container. It
records external executable paths as container-provided.
Example output:
RunnerLens
Runner
GitHub Actions
ubuntu-24.04
Observed runner dependencies
cmake
path: /usr/local/bin/cmake
origin: runner-provided
ninja
path: /usr/bin/ninja
origin: runner-provided
clang
path: /usr/bin/clang
origin: runner-provided
3 runner-provided dependencies observed
RunnerLens records executable versions and Debian package owners where reliable.
Receipt comparison narrows run-to-run changes to the tools your build actually used. runnerlens impact downloads public GitHub Ubuntu inventories, compares two image releases when both versions are supplied, and filters the result to observed runner-provided and tool-cache dependencies. Tool-cache paths are matched against the image inventory's separate cached-tool versions.
Find hidden build dependencies
Discover tools that your build uses from the CI runner without explicitly provisioning them.
Identify runner image changes that intersect with tools your build actually uses.
See which observed dependencies change before moving between runner images.
Expose dependencies that currently exist only because the runner happens to provide them.
Use observed dependencies as evidence when preparing your own runner image.
RunnerLens records observed environment dependencies in a Runner Dependency Receipt.
A receipt contains information such as:
{
"runner": {
"provider": "github-actions",
"image": "ubuntu-24.04"
},
"dependencies": [
{
"name": "cmake",
"path": "/usr/local/bin/cmake",
"origin": "runner-provided",
"confidence": "confirmed"
}
]
}Receipts are designed to support both human investigation and automated comparison.
They can eventually answer questions such as:
- What runner-provided tools does this build use?
- What changed since the last successful run?
- Did this pull request introduce a new ambient dependency?
- Will a runner image update affect this project?
- What needs to be installed on a self-hosted runner?
RunnerLens observes the command being executed and resolves the external tools it uses.
Build Command
|
v
Runtime Observer
|
v
Dependency Resolver
|
v
Origin Classifier
|
v
Runner Metadata
|
v
Dependency Receipt
|
v
Impact Analysis
Records relevant process execution under the command being analyzed.
Identifies executables, versions, paths, and package ownership where possible.
Determines whether an observed dependency is:
runner-provided
workflow-provisioned
repository-provided
tool-cache
container-provided
unknown
Correlates observed dependencies with information about the CI runner image.
Filters runner image changes down to dependencies observed in the build.
RunnerLens should only report what it can support with evidence.
Seeing:
/usr/bin/cmake
does not automatically mean the workflow forgot to install CMake.
The tool may have been installed earlier in the workflow.
RunnerLens should report:
origin: unknown
when provenance cannot be established reliably.
Reliable evidence is more important than confident output.
Initial target:
| Environment | Support |
|---|---|
| GitHub-hosted Ubuntu | Supported |
| GitHub-hosted Windows | Future |
| GitHub-hosted macOS | Future |
| Self-hosted Linux | Future |
| GitLab CI | Future |
| Azure Pipelines | Future |
| Buildkite | Future |
Support will only be marked complete once it is covered by real integration tests.
RunnerLens is not an SBOM generator.
RunnerLens is not a vulnerability scanner.
RunnerLens is not a CI runtime security product.
RunnerLens is not a replacement for Bazel, Nix, containers, or other reproducible build systems.
RunnerLens focuses on one problem:
Which parts of the CI environment did this build actually depend on?
Runtime observation can expose sensitive information if implemented incorrectly.
RunnerLens should collect only the information required to identify dependencies and their origin.
It should avoid collecting unnecessary:
- environment variable values
- credentials
- tokens
- file contents
- command arguments
- unrelated runtime data
The default principle is simple:
Collect the minimum evidence required to identify the dependency.
The composite action executes only the command supplied in its command input.
It sends no receipt data to RunnerLens services. A dedicated security policy and threat model will be maintained as the project develops.
RunnerLens will not detect every form of environment dependency.
The first version will focus primarily on observed executable dependencies.
It may not detect dependencies on:
- system headers
- dynamically loaded libraries
- SDK files
- environment variables
- kernel behavior
- filesystem assumptions
- background services
A clean RunnerLens report should not be interpreted as proof that a build is fully hermetic.
The next areas to explore are runner migrations, additional CI providers, and operating systems. Changes will be guided by reproducible build cases and real runner-image regressions.
See CONTRIBUTING.md for development setup, evidence rules, receipt compatibility guidance, and privacy requirements.
RunnerLens is intended to support contributions in areas such as:
- package provenance resolvers
- CI provider integrations
- runner metadata parsers
- tracing backends
- operating system support
- regression fixtures
- report formats
Large architectural changes should begin with an issue or discussion.
RunnerLens is currently in early development.
The first objective is to validate that runner dependency information helps diagnose real CI environment problems.
The project will prioritize real workflows and real failures over synthetic demos.
RunnerLens is licensed under the Apache License 2.0.
RunnerLens
Know what your build inherits.