Security baseline
This is the operator runbook for #282. The coordinator reuses the existing RustSec worker, rather than adding a second advisory schedule. It adds SDK inventory, redacted secret checks, reviewed source/workflow rules, expiring exceptions and bounded canaries. Deployment and finding triage remain explicit gates. Default-branch activation, both-ref scans and required-check enforcement are recorded in the dated evidence ledger.
Events and scope
| Event or changed surface | Selected checks |
|---|---|
Any PR into, or push to, development or release | Complete Git change selection, exception expiry, changed-present text secret scan, aggregate |
| Only Markdown/SVG, including this runbook | Secret check only; no Rust, Python SDK or other SDK build/install |
| Cargo manifests/lock, toolchain, Cargo config, platform WIT, Wasmtime host/surface or component bindings | RustSec |
| Any supported or newly discovered dependency manifest/lock | SDK inventory and OSV; Cargo changes also select RustSec |
| Source files, workflows, local actions or Dependabot config | Applicable source/workflow static rules; workflow changes also select canaries |
Scanner controls, policies, fixtures/tests or central tools/toolchain.toml | All checks |
| Weekly Monday 04:23 UTC, or manual dispatch | All checks against both development and release, regardless of lockfile changes |
Scope selection uses Git object identities and the
complete diff, not a truncated GitHub changed-files page. Empty/unknown change
sets do not select a cheap success. The separate Security baseline result
requires successful scope and secrets, success for every selected job and an
actual skip for every unselected job. Failures, cancellations and unexpected
skips cannot pass it. Existing CI result, documentation/SVG profiles and cache
ownership are unchanged. The only ordinary ci.yml edits disable persisted
credentials on its eight checkout steps; four manual workflows receive the same
fix rather than suppressing existing workflow findings.
GitHub activates schedules from the default branch, currently release.
The separately approved activation PR #461
merged on 2026-09-21. The default-branch coordinator pins scanner controls to
e852442ab2801388e06d420ca6c389031c8bdd14, independently of each scanned source.
An actual scheduled run and a later manual run passed all checks against both
maintained refs. Their exact source and advisory-database identities are retained
in the evidence ledger. Subsequent dependency or control changes still require
their own selected checks; an earlier green scan does not cover a later commit.
Reviewed tools and rules
Official upstream release tags and commits were checked live on 2026-09-19.
The tool lock records the complete release
identity plus separate SHA-256 digests for each Linux/Windows x64 archive and
extracted binary. Installation verifies both
digests before execution, then the exact version. It uses prebuilt binaries, not
cargo install, an unpinned installer or PR-controlled package scripts.
| Tool | Reviewed release | Full upstream commit |
|---|---|---|
| cargo-audit | 0.22.2 | 281452c35cf0870969042374110f099a411bc185 |
| Gitleaks | 8.30.1 | 83d9cd684c87d95d656c1458ef04895a7f1cbd8e |
| zizmor | 1.30.1 | 99a054ed9283c90abdd2d5b9fb5101d27dde9783 |
External actions keep the #281 immutable action policy:
checkout 11d5960a326750d5838078e36cf38b85af677262 and setup-python
a26af69be951a213d495a4c3e4e4022e16d87065. Only static/canary jobs install the
reviewed PyYAML 6.0.3 CPython 3.13 wheel from the
hash-locked requirements, with binary
wheels, --no-deps and --require-hashes. Docs-only scanning needs no Python
package installation. Tool updates require a new upstream identity/digest review
and canary run, not merely a newer version string.
Gitleaks extends the pinned release's built-in rules with one harmless synthetic
canary rule. zizmor uses its pinned regular rules offline, with no discovered
config, ignores or inline suppressions. The existing workflow action-pin
validator also runs. Nine local source rules
cover conspicuous TLS-verification bypasses in Rust, Python, JavaScript/TypeScript,
Go, Java and C#, Python dynamic execution, C unbounded input and shell download
pipes under apps, crates, sdk, tools, examples and tests.
These are narrow lexical checks, not interprocedural/taint analysis. Aliases,
semantic authorization mistakes and runtime resource ownership require review
and integrated tests; absence of these patterns does not certify source safety.
Dependency inventory and fresh data
The inventory is checked against discovered manifests without running package managers, builds, setup scripts or SDK code.
| Surface | Advisory coverage and boundary |
|---|---|
| Root workspace and reviewed isolated native-VM fixture | RustSec scans every tracked Cargo.lock, with separate lock hashes and findings; every workspace member and isolated fixture must appear in its lock |
| TypeScript SDK, renderer example and isolated documentation website npm manifests/locks | OSV for every resolved direct/transitive/dev entry; registry.npmjs.org HTTPS sources only |
tools/requirements.lock, source scanner requirements, and caller scanner requirements | OSV exact PyPI versions; unresolved ranges/options fail |
| Go client and generator modules | OSV for the complete recorded go list -m -json all graph and its exact Go standard-library version; normalized manifest/sum hashes, per-module checksums and generator bindings must agree |
| Java client and generators | OSV Maven coordinates for every pinned runtime/generator archive, including supported platforms; the reviewed Gradle manifest hash binds the data-only graph to its build logic |
| .NET projects | Registered transport projects require resolved NuGet locks, exact direct versions, complete transitive edges and content hashes; only reviewed exact legacy project hashes establish no-external-package status |
| Native C transport | OSV source-commit queries for pinned nghttp2, its bundled sfparse runtime, nanopb and protoc, plus exact PyPI generator/test versions; release archives also require digests and canonical upstream URLs. Upstream native advisory review remains necessary |
| C guest and operating-system libraries | No independent package-manager graph; platform C library, external toolchain and generated ABI review remain separate |
Missing locks, empty resolved graphs, manifest/lock drift, unreviewed Git/private
package sources and new unsupported manifests fail closed. Benchmark setup.py modules
are explicitly hashed as non-distribution helpers and are never executed.
The renderer example, website and source security directory may be entirely absent on an
older maintained ref; that is recorded as not-shipped-at-source-revision.
If any file in one of those directories exists, its expected manifest/lock is mandatory.
The caller scanner requirements are always queried, even if the scanned
branch predates the baseline. New SDK dependencies need a resolved-graph parser,
explicit inventory registration and fixtures before approval. Old Go/Gradle
manifests are accepted only at their exact reviewed no-dependency hashes and only
without a new lock. The legacy C interface allowance binds every C-tree path and
normalized file digest to one of two reviewed historical trees; adding transport
code cannot silently preserve that allowance. NuGet source configuration files
and custom dependencies.lock.json files participate in manifest discovery.
The SDK readers execute no Go, Gradle, MSBuild, C compiler, Python package or project code. Ordinary SDK CI must separately regenerate/check the complete Go graph and perform locked package preparation; the scanner does not assert that a hand-edited graph is a build result. Maven build-logic changes require a new exact reviewed hash. NuGet conditional imports, unresolved versions and additional dependency-loading directives fail rather than being evaluated. Operating-system packages, JDK/.NET runtime distributions and dynamically acquired provider artifacts are not covered by a zero-package record. The Go standard-library query is version-specific advisory matching, not a toolchain archive-authenticity or compiler-safety claim.
Every RustSec job freshly clones RustSec/advisory-db
main, independently resolves remote main, and requires identical full commits.
This baseline adds a 14-day maximum commit age and a five-minute future-clock
tolerance to the original identity-only slice. Even a quiet database exceeding
that bound blocks the scan; an operator must investigate, not silently extend it
or reuse cached data. The runner directly invokes cargo-audit audit --db ... --no-fetch --no-yanked --deny warnings --file ... --json on a bounded staged lock.
It records the independently verified Git commit, timestamp and lock SHA-256,
requires a nonempty advisory database and rechecks unchanged DB HEAD/worktree.
With --no-fetch, cargo-audit can emit null DB commit metadata; the receipt does
not misrepresent that as a tool-reported commit. Yanked-crate network checks are
not covered; advisory and warning results are.
Non-Rust graphs query the official OSV batch endpoint with package ecosystem/name/version or an exact reviewed native-source commit, not repository files or credentials. Commit-query results retain the repository identity in local findings; an empty response is not proof that every upstream native advisory is represented by OSV. Native dependency upgrades still require the SDK maintainer's upstream advisory and archive-to-source identity review. The nghttp2 graph must include exactly its reviewed sfparse source and both bundled file hashes. Removing that runtime dependency, adding an unknown bundle or changing its source/file shape fails the scan. The ordinary C preparation step separately checks those hashes against the authenticated release archive. Responses require a current HTTP Date (within one hour), exactly one result per query and no incomplete pagination, errors or unknown result fields. Transport, schema and freshness failures are not converted into an empty success. An empty result for one explicit package means only that the service reported no match at that observation time. OSV exposes no immutable database commit here: receipts record endpoint, UTC observation, HTTP Date and request/response SHA-256 instead. This verifies response freshness, not the completeness of upstream ingestion.
Secret coverage and private handling
PR/push checks scan changed files still present at the selected revision,
including Markdown and SVG. Scheduled/manual checks scan the complete current
tracked text snapshot. Local runs also include non-ignored untracked files and
mark dirty source/control worktrees in their receipts; those are not exact-head
CI evidence. Text is copied as opaque .txt files so filename/path exclusions
cannot silently skip SVGs and deep Windows benchmark paths stay bounded.
Findings map back to the original relative path and location.
The wrapper always uses full redaction and ignores neither .gitleaksignore,
source baselines nor gitleaks:allow comments. Matches, snippets, credential values
and native scanner stderr are never printed or uploaded. Public output contains
only finding IDs, paths, package versions where applicable, positions and
content-bound fingerprints, capped at 50 findings with the full count. There is
no security artifact-upload job. Temporary staged files are removed at job end;
do not retain native scanner reports in issues, public artifacts or commits.
Binary/archive assets are counted but not scanned; known text extensions with NUL bytes and undecodable text fail rather than silently disappearing. Archives are not unpacked, and decoding depth is limited to two. This job is a snapshot check, not Git history scanning or proof that deleted/intermediate-commit secrets were absent. Enabled GitHub secret scanning and push protection provide the repository service layer for their supported patterns. Non-provider patterns and live validity checking remain disabled; no credential is exercised for triage.
If those GitHub services become unavailable, the replacement must include a maintainer-operated full-history scan and a verified server-side/pre-receive or private promotion gate that rejects synthetic protected pushes before publication, with private alerts, owner response and retained redacted evidence. Local pre-push hooks and PR snapshot checks alone are not equivalent. Service loss is an open acceptance/promotion blocker until that equivalent is deployed and tested.
Trust boundary and execution bounds
The coordinator and reusable worker use only contents: read, no inherited
secrets, no write permissions, no pull_request_target, no automatic updates or
merges, and no shared caches. Checkout credentials are not persisted. Controls
and input occupy separate checkouts; source .cargo settings, scripts and scanner
configs are never executed. Scanner child environments remove authentication and
tool-configuration variables and use temporary HOME/CARGO_HOME with Git prompts
disabled. PR changes to the control code itself remain untrusted code running
on a disposable read-only runner: code review and required checks, not directory
separation alone, defend that boundary. Never pass the operator admin token to CI.
Jobs have outer deadlines of two to eight minutes. Inner bounds include 120-second tool downloads (96 MiB), a 90-second DB clone, 30-second remote lookup, 180-second audit, 120-second native secret/static scans and a four-minute OSV batch loop. Inputs have a 20,000-path cap; ordinary files/locks are capped at 8 MiB, text-secret inputs at 16 MiB/file and 384 MiB total, scanner output at 8 MiB, and OSV queries at 5,000 packages in batches of 100. Linked/reparse inputs, duplicate JSON fields and size/deadline breaches fail with fixed diagnostics. Linux CI kills scanner process groups on timeout; local Windows checks bound the direct scanner process, not arbitrary hostile descendant trees. No source packages are built or launched to perform these scans.
Ownership, updates and exact exceptions
@KirilsTurkins owns security triage and routes dependency findings to the
affected SDK/example/runtime maintainer. CODEOWNERS
routes control, policy, fixture and runbook changes for review; it is not itself
proof that branch protection enforces a review. The parent Phase 3 reviewer
centrally audits the exact PR head and decides merges, promotion and issue closure.
- Treat suspected live credentials privately under the security policy and the enabled private vulnerability-reporting channel. Notify the credential owner for revocation/rotation; do not paste values, probe validity or use a public finding as evidence of compromise.
- For advisories, record exact package/version/lock and advisory ID, confirm the dependency chain and reachable operation, and review the upstream patch and lock diff. Follow #279 for advisory-specific triage. A version match alone is not an LSF exploit.
- Scanner outages, missing data and unknown inventory are failures owned by the security maintainer, not permission to waive an analysis or reduce its scope.
- Submit updates and any temporary exception as reviewed PRs with fresh canaries and exact-head checks. No automation approves or merges dependency/workflow code.
Dependabot configuration proposes weekly updates
to development, capped at three open version PRs per entry. Default-branch Cargo,
npm and scanner-pip entries use zero version PRs while allowing security proposals.
The seven-day cooldown affects version updates, not security fixes. GitHub security
updates target the default branch; target-branch: development does not change
that. These semantics were checked against the official
Dependabot options reference.
The nonstandard tools/requirements.lock and scanner binary pins retain a manual,
owned update process; OSV still checks their declared package graphs. Dependabot
configuration must also reach the default branch before its scheduling is active.
Exceptions require exact scanner, finding ID, package and version for advisories, relative path, fingerprint, owner, reachability/false-positive rationale, review issue/PR, created date and expiry. Source/secret fingerprints bind the complete normalized file content and exact line/column; advisory fingerprints bind the finding, package/version and lock path. No wildcards, broad rule/path ignores, unowned entries or indefinite waivers are accepted. Expiry is exclusive at UTC midnight, with a maximum 30-day lifetime; expired or future entries fail every invocation, including docs-only scope checks. An expired unused entry must still be removed or explicitly re-reviewed.
The initial policy proposed one compatibility waiver: zizmor
self-repository on the exact job-level local RustSec call, expiring 2026-10-03.
GitHub resolves this reusable workflow at the caller commit, unlike a mutable
workspace-relative action step. #281 currently validates ./ references, not the
new $/ syntax recommended by zizmor 1.30.1. The parent reviewed that exact call
in PR #363. Update the pin policy or re-review before expiry; adding an exception
file alone is not approval. The same reviewed policy now includes 16 exact
noncredential secret-rule occurrences: one labelled API-journal digest, one
synthetic environment-variable name and fourteen historical preparation lookup
identities. Their producer/ownership review and clean snapshot results are in the
evidence ledger. They expire on 2026-10-03 and
cannot match a changed file, scanner location or rule. No dependency exception,
general digest exemption or path allowlist exists.
Settings and operator commands
The authorized operator enabled and verified alerts, security-update proposals, secret scanning/push protection and private reporting on 2026-09-19. The evidence snapshot distinguishes those live states from pending default-branch deployment and required-check configuration. Read-only inventory uses an authenticated operator with repository-admin visibility:
python tools/security_settings.py
--enable is the explicit, idempotent operator setting-change mode. API access
failures and disabled services are not success. The script never changes branch
protections, enables auto-merge, creates workflow credentials, probes a secret or
runs from CI. Re-run it at promotion and after service/permission changes.
In an isolated Python 3.13 environment, use a new ignored destination for scanner installation. Each later scan re-verifies the installed binary and receipt:
python -m pip install --only-binary=:all: --no-deps --require-hashes -r .github/security/requirements.txt
python tools/security_install.py --tool cargo-audit --destination target/security-tools
python tools/security_install.py --tool gitleaks --destination target/security-tools
python tools/security_install.py --tool zizmor --destination target/security-tools
python -m unittest discover -s tools/tests -p 'test_security*.py'
python tools/security_selftest.py --tools target/security-tools --scratch target/security-canaries
python tools/security_scan.py rustsec --repo . --tools target/security-tools --scratch target/security-runs
python tools/security_scan.py dependencies --repo . --tools target/security-tools --scratch target/security-runs
python tools/security_scan.py secrets --repo . --tools target/security-tools --scratch target/security-runs
python tools/security_scan.py static --repo . --tools target/security-tools --scratch target/security-runs
Do not overwrite an installed tool destination to hide a digest mismatch. Review
the failure and use a fresh destination for a reviewed update. For changed-file
secret scanning, supply --base with the full base commit; omitting it scans the
snapshot. security_scan.py exits 0 for no unexcepted findings, 1 for findings,
and 2 for unavailable/invalid data. The fixture guide
describes harmless pass/fail coverage, including why canary success means expected
bad fixtures were rejected rather than that vulnerable input was accepted.
Rustls handshake boundary update
RUSTSEC-2026-0285,
published on 2026-09-14, affects Rustls 0.23.13 through 0.23.44. The outbound HTTP
and OCI clients previously pinned 0.23.44 and now pin the reviewed
0.23.45 patch.
Cargo.lock selects the same patched version for the shared TLS dependency graph.
The upstream finding concerns accepting TLS 1.3 handshake messages at the wrong encryption level. The handshake transcript remains authenticated; this update is not evidence of an LSF interception exploit. Rebuild node and client binaries and validate actual TLS success/rejection and HTTP/OCI cancellation behavior. The advisory gate must pass with the updated lockfile; no suppression is added.
Phase 3 acceptance handoff
#240 requires
#282 alongside #238 integrated adversarial evidence.
Before the parent can claim acceptance, it must review exact exceptions and
findings, audit the final PR head, promote controls normally to default release,
retain both-ref manual and scheduled results without changing locks, and require
Security baseline result alongside the existing CI result once deployed.
The 2026-09-22 API verification confirms both contexts are required on
development, bound to the GitHub Actions application. Existing strictness and
the CI result requirement were preserved. The older 2026-09-19 snapshot below
remains historical evidence of the pre-activation state.
The renderer's initial advisory matches are removed by a reviewed, exact
@bytecodealliance/weval 0.5.0 override, without changing the renderer's AOT-disabled
profile or ComponentizeJS version. Older release lock findings, completed
full-snapshot secret triage and remaining deployment/evidence gaps are listed in the
evidence ledger, not silently excepted. A real
fork execution and synthetic server push rejection were not performed by this
delivery; static fork-permission fixtures and enabled-service API state should
not be described as those tests. Parent review determines the remaining evidence
needed for acceptance. No scan certifies guest isolation, provider safety,
capability authorization or production hardening.
Maintained release C surface review, 21 September 2026
The release snapshot af3e8680d4132bd99b3299e300af147e53b7fa80 contains exactly two C SDK files: sdk/c/include/latent/latent.h (9,873 bytes) and sdk/c/tests/invocation_identity.c (18,088 bytes). The header declares the client interface; the C11 fixture supplies a local semantic test implementation. Their includes are the local header and C standard headers. They have no transport implementation, package-manager graph or external library imports to submit as resolved packages.
The normalized file-list identity is 2e038c2e1fe43415f70cbe2771532c8c07f354faf14a87a202a2d9f6975ed61f. The file digests are respectively 882e771526895cd9474ba15da800ec700167506be06bb8230799afb84fe53473 and 7d9872e216cab4e093c0bf8c74229291a589fc0e96a817aee283b25a4229fd6e. This exact historical surface is added to the existing reviewed legacy inventory. Any changed, added or removed file fails that identity check; a present dependency lock must pass the normal resolved-graph validator. This is an inventory classification, not an advisory exception or executable transport qualification.