Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
7.4 KiB
Security runtime and secret-scan / test-report primitives
Scope: runtime/security.Dockerfile, platform_ci/secret_scan.py and platform_ci/test_reports.py with their tests.
Status: unqualified. Nothing here has been built, scanned or published. The root owner builds, scans and publishes the image, and only then pins its digest. Until that happens the image must not be referenced by any workflow or catalog.
Image
security.Dockerfile derives from the published control image
git.kof22.com/koftwentytwo/platform-ci-control@sha256:11f8cb328e7fc7aca8e0c39ed979b33cb1db411a59460baae3e0d48281a15e3c
(digest-checked at build) and adds one file, /usr/local/bin/gitleaks, rebuilt from source.
Why: the upstream prebuilt 8.21.2 binary was scanned at 28 HIGH/CRITICAL findings (26 Go standard library, 2
golang.org/x/text). Gitleaks' source, rules and fingerprints stay exactly the 8.21.2 commit; only the compiler and
golang.org/x/text change. Nothing in the scanner's source or rules is edited.
Rebuild pins (mirroring runtime-qualification/security/rebuild-pins.json, frozen by root; the recipe tests compare the Dockerfile only with the constants in secret_scan.py and do not read that file):
| Item | Value |
|---|---|
| Gitleaks | 8.21.2, commit 43fae355e6fe4d99d2a7b240a224b85e2903aeb4 |
| Source archive | https://codeload.github.com/gitleaks/gitleaks/tar.gz/<commit>, SHA-256 9035d05f3aa6c28dbc06c9785b019b2c2331efdb91c626abe39381417d08ab41 |
| Go toolchain | golang:1.27.3-bookworm@sha256:e482b31b5d30c31f9e495f4b8cbdf41429b2d74955dfbcd9c0a904483d738390 |
golang.org/x/text |
v0.41.0 (fixes both library findings) |
Build stages (only the last ships):
gitleaks-source(control image): downloads the archive withurllib, fails on ahashlibSHA-256 mismatch, extracts withtarfilefilter="data", requires exactly one top-level directory.gitleaks-build(pinned Go image):GOTOOLCHAIN=local,go get golang.org/x/text@v0.41.0, thenCGO_ENABLED=0 go build -trimpath -buildvcs=false -ldflags "-s -w -X <module>/cmd.Version=8.21.2"(cmd.Versionis theVersionvariable of packagecmd, defined in the frozen archive'scmd/version.go; module path read fromgo list -m). It checks that the binary's embedded Go isgo1.27.3, that itsgolang.org/x/textdependency isv0.41.0, and thatgitleaks versionprints8.21.2.- Runtime: the control image plus that one binary. No platform or application source, and no Go, is shipped.
Downloads and go get (Go module proxy and checksum database) happen at image build time only; nothing is installed or
downloaded when a pipeline runs. The constants in secret_scan.py must equal the build args (a test compares them).
The build's dependency and compiler facts are therefore: Go 1.27.3 (official image), x/text v0.41.0, source commit above.
Legacy fact only: the old prebuilt gitleaks_8.21.2_linux_x64.tar.gz checksum
5bc41815076e6ed6ef8fbecc9d9b75bcae31f39029ceb55da08086315316e3ba (from tools.env) describes the vulnerable binary that
is no longer used. It verifies nothing about the rebuilt binary and is not a build input; the rebuilt binary is verified
by the source archive checksum and the pinned Go image, and has no vendor-published binary checksum.
Build and scan proof (root-reported, frozen only; not independently verified here): root built this recipe with the
cmd.Version ldflag (the earlier version.Version flag was wrong), the binary prints 8.21.2, and a Trivy scan of the
rebuilt image found 0 HIGH/CRITICAL vulnerabilities and 0 secrets, against 28 for the old prebuilt binary. The Gitleaks
source commit and rules are unchanged from 8.21.2.
Not yet published: the image is not published and no digest is pinned, so it stays unqualified for workflow use until root publishes it and pins the digest. A real-binary history-scan smoke test is still owed.
Secret scan contract
scan_history(source_directory, source_revision, report_path,
run_command=run_scan_command, parent_environment=os.environ) -> SecretScanResult
# SecretScanResult(passed, source_revision, failure, findings, exit_code, report_path, gitleaks_version)
- Preconditions, all failures (never exceptions): revision is exactly 40 lowercase hex;
HEADequals it; the clone is not shallow (git rev-parse --is-shallow-repositorymust printfalse); the binary is alwaysGITLEAKS_INSTALL_PATH(/usr/local/bin/gitleaks, not configurable) and must report version 8.21.2 (a binary that cannot start fails the scan);report_pathis inside the source checkout and outside.gitand.platform-ci. - Scan:
gitleaks detect --source <dir> --no-banner --redact=100 --report-format json --report-path <report> --exit-code=2 --log-opts="--full-history <sha>". Only that revision's ancestry is scanned. A stale report is deleted first. - Failure: any finding (report is authoritative over exit code 0), exit code 2, any other nonzero exit, or a missing, unreadable or non-list report. Gitleaks and Git output is captured and discarded; only counts and generic reasons are logged or returned, so no matched secret or body reaches a log.
- Environment: only
PATH,LANG,LC_ALL,TMPDIR, plusGIT_CONFIG_NOSYSTEM,GIT_CONFIG_GLOBAL=/dev/null,GIT_NO_REPLACE_OBJECTS,GIT_TERMINAL_PROMPT=0.safe.directoryis the exact resolved source path:-con each Git command, andGIT_CONFIG_COUNT/KEY_0/VALUE_0for the Git processes Gitleaks starts. - Exemptions: Gitleaks reads the repository's standard
.gitleaksignoreitself. There is no central owner/repository map and no baseline option; the approved exact fingerprints are moved into the repositories by the migration worker, and baseline contents are not changed here. - Runner:
run_scan_command(command, cwd, env) -> CommandOutput(exit_code, stdout), no shell; raisesSecretScanErrorwhen a program cannot start (converted to a failed result byscan_history).
Test-report contract
check_junit_reports(workspace_directory, report_paths) -> JUnitSummary
# JUnitSummary(reports, tests, skipped, failures, errors, problems); properties executed, passed
- Reads only the declared files in one pass of the public
xml.parsers.expatparser feedingElementTree.TreeBuilder(the CElementTree.XMLParserexposes no handlers and is not constructed); no process is started and tests are never rerun. - Rejects DOCTYPE, ENTITY and external entity references in UTF-8 and UTF-16 (expat handlers raise, nothing is expanded or
fetched), expat errors as malformed XML, roots other than
testsuite/testsuites, non-integer counts, andskipped > tests. - Fails on failures/errors attributes or on any
testcasewith afailureorerrorchild. - Legacy Maven
--allow-emptyparity: zero reports, zero tests and all-skipped pass. No new zero or skip gate. - Containment: each path must resolve (through symlinks) to a regular file inside the workspace, outside
.gitand.platform-ci, elseTestReportError. A missing declared report is packaging's responsibility, so it is an error here only as "not a regular file", never a pass.
Tests
From candidate/: python -m unittest tests.test_secret_scan tests.test_test_reports (all tests use a fake command
runner or temporary files). Lint: ruff check and ruff format --check; typing: pyright.
Strict pyright must also pass. The author had no shell, so these changes are unverified until root runs them.