IMPORTANT: Developer documentation for the current development branch. This content is unreleased, may change without notice, and must not be treated as Buildish release documentation.
Threat Model
Threat Model - Buildish Mammoth Cache
1. Header
Project: Buildish Mammoth Cache for Gradle and Maven.
Version binding: This draft is versioned alongside the unreleased development source and package version 0.0.0-development. A report should be assessed against the model in the affected source revision, not against a later revision. (documented)
Date: 2026-06-05.
Author: The Buildish Authors. (documented)
Reporting cross-reference: Suspected violations of §8 should be reported through SECURITY.md to security@buildish.org. This draft informs triage, but the security team makes the final disposition. (documented)
Status: Draft, awaiting maintainer review as of 2026-06-05. (inferred)
Provenance legend: (documented) means the claim is present in repository docs, action metadata, tests, or source comments; (maintainer) means confirmed by a maintainer; (inferred) means derived from source behavior or project shape and should be confirmed before publication.
Draft confidence: Rough count: 63 documented / 0 maintainer / 38 inferred. The largest inferred areas are operational assumptions, negative side-effect claims, and exact resource-exhaustion thresholds.
Buildish Mammoth Cache is a Node.js GitHub Action that manages Gradle and Maven build-tool cache directories in CI. It restores and saves partitioned base caches, supports distributed worker/aggregator delta exchange across parallel jobs, and for Gradle can validate and provision missing gradle-wrapper.jar files before the build runs. (documented)
2. Scope and Intended Use
Primary intended use cases:
- Running as
buildish-tooling/buildish-mammoth-cache/actions/github/gradlein GitHub Actions jobs for Gradle builds. (documented) - Running as
buildish-tooling/buildish-mammoth-cache/actions/github/mavenin GitHub Actions jobs for Maven builds. (documented) - Restoring, pruning, manifesting, saving, and reporting on selected Gradle user home or Maven local repository partitions. (documented)
- Exchanging worker cache deltas through GitHub workflow artifacts and merging them in an aggregator job. (documented)
- Provisioning Gradle wrapper JARs from canonical Gradle sources, with checksum and detached-signature verification. (documented)
Deployment context: a Node 24 GitHub Action running inside a GitHub-hosted or self-hosted Actions runner. Codeberg/Forgejo and GitLab CI support is planned but not part of the current in-scope deployment. (documented)
Expected callers and roles:
| Role | Trust level | In model? | Notes |
|---|---|---|---|
| Workflow operator | Trusted for the repository/workflow | Yes | Chooses action inputs, permissions, needs, job topology, and cache partition policy. (inferred) |
| Repository contributor on trusted branch | Trusted by repository policy | Yes | Can influence workspace files, build outputs, wrapper files, and build-tool cache contents. (inferred) |
| Pull-request contributor from fork | Untrusted | Yes | May control workspace content and build outputs, but default read-only behavior should prevent cache mutation on PR events. (documented) |
| GitHub Actions platform/cache/artifact services | Trusted service dependency | Yes | Supplies state, cache, artifact, event metadata, and token-scoped APIs. (inferred) |
| Gradle distribution and Gradle GitHub source hosts | Authenticated upstream dependency | Yes for wrapper provisioning | Wrapper bytes and metadata are expected from canonical HTTPS hosts and verified before installation. (documented) |
| Maven/Gradle build process invoked by the workflow | Outside this component’s control | Partially | Its outputs can become cache inputs; its execution behavior is not provided by this action. (inferred) |
Component-family table:
| Family | Representative entry point | External touchpoints | In model? |
|---|---|---|---|
| GitHub Gradle action main/post | actions/github/gradle/action.yml, src/ci/github/gradle/main.ts, src/ci/github/gradle/post.ts |
GitHub inputs/state/outputs/logs/summaries, workspace, cache API, artifact API, network, gpg, java |
Yes |
| GitHub Maven action main/post | actions/github/maven/action.yml, src/ci/github/maven/main.ts, src/ci/github/maven/post.ts |
GitHub inputs/state/outputs/logs/summaries, workspace, cache API, artifact API, java |
Yes |
| Shared cache model and base cache | src/cache/model.ts, src/cache/service.ts, src/cache/manifest.ts |
Filesystem, GitHub cache backend | Yes |
| Distributed delta protocol | src/delta/service.ts, src/delta/apply.ts, src/delta/apply-execution.ts |
Filesystem, temporary directories, GitHub artifact backend | Yes |
| Gradle wrapper validation/provisioning | src/build-tool/gradle/wrapper/* |
Workspace, HTTPS fetches, temporary files, GnuPG subprocess | Yes |
| Maven adapter | src/build-tool/maven/* |
Filesystem under Maven local repository | Yes |
| GitHub platform adapter | src/ci/github/* |
GitHub env vars, event payload, toolkit APIs, auth headers | Yes |
| Job summary and logs | src/util/html.ts, phase summary builders |
GitHub job summary, logs | Yes |
| Tests, smoke fixtures, benchmarks | test/, scripts/smoke-test.mjs, test/bench/* |
Local developer machine, test fixtures | No |
| Release and legal tooling | buildish-release-tooling/, scripts/release-legal*, scripts/rat-check.mjs |
Local/release environment | No |
| Documentation/site content | docs/, site/ |
Static documentation build | No, except when documenting expected security contract |
| Vendored package tree | node_modules/ |
Build/test dependency content | No |
3. Out of Scope
The project does not attempt to provide:
- A sandbox for arbitrary Gradle or Maven build logic. If a workflow executes untrusted build scripts with secrets or write permissions, that is outside this action’s boundary. (inferred)
- A security boundary against a malicious workflow operator or someone who can modify workflow YAML on a trusted branch. (inferred)
- Protection when the caller explicitly disables safety controls, for example Gradle wrapper validation or provenance verification modes. (documented)
- A cryptographic guarantee that cached Maven or Gradle dependencies are legitimate upstream artifacts. The action transports cache files; dependency provenance remains the build tool’s and repository’s responsibility. (inferred)
- A defense against a compromised GitHub Actions runner, compromised Node runtime, compromised
gpgbinary, compromised GitHub cache/artifact service, or compromised repository secrets. (inferred) - Cross-runner or cross-architecture delta reuse. Distributed delta reuse requires identical compatible cache-family inputs. (documented)
- Support for planned non-GitHub CI providers until provider adapters and docs are implemented. (documented)
- Security review of examples, tests, benchmarks, release tooling, generated bundles, or
node_modulesas part of this model. (inferred)
Repository code not covered by this model:
| Path | Policy |
|---|---|
test/ |
Test fixtures and test harnesses are not runtime security surfaces. Findings here are ordinary test-quality issues unless they show an in-scope runtime bug. (inferred) |
scripts/ |
Developer/release scripts are out of this runtime action model. (inferred) |
buildish-release-tooling/ |
Release-process tooling is out of scope for this document. (inferred) |
site/ and most docs/ content |
Static documentation is out of runtime scope except where it defines caller responsibilities. (inferred) |
node_modules/ |
Dependency supply-chain assessment is out of scope for this document. (inferred) |
dist/ when generated |
Generated action bundle should be reviewed through corresponding src/ files. (inferred) |
4. Trust Boundaries and Data Flow
The primary boundary is the GitHub Action API surface: action inputs, GitHub environment variables, GitHub event metadata, workspace files, cache backend content, and artifact backend content are treated as external inputs until validated by the action. (documented)
Data flow:
- The GitHub entrypoint reads action inputs and selected runner environment values, optionally overlays a workspace-relative config file, and normalizes the merged configuration. (documented)
- Bootstrap creates a provider-neutral CI context, derives a cache model, detects Java, and constructs tool-specific cache partitions. (documented)
- Gradle bootstrap validates target wrapper property files; when needed it downloads wrapper checksum/signature/JAR data from canonical HTTPS hosts, verifies checksum and signature, and installs the JAR atomically. (documented)
- Prepare restores the base cache via the CI cache backend, optionally prunes managed files, applies dependent worker deltas for aggregators, captures the pre-build manifest, and stores post-action state. (documented)
- The workflow’s build steps run outside this action and may add, modify, or delete files inside the managed cache partitions. (inferred)
- Finalize reads persisted state, runs timestamp cache garbage collection by default for standalone/aggregator jobs, captures the post-build manifest, computes a delta, saves a base cache for standalone/aggregator jobs or uploads a delta artifact for worker jobs, cleans consumed artifacts, and publishes logs/summaries. (documented)
Reachability preconditions per component:
| Component family | Finding matters only if reachable through |
|---|---|
| Shared config normalization | Action inputs, config-file contents, or GitHub event/env overrides accepted by read*ActionInputs and normalize*ActionConfig. (documented) |
| Cache family/model generation | Validated config plus CI context values and Java detection output. (documented) |
| Manifest capture | Files reachable under active partition include globs and not excluded by partition or hard-exclude rules. (documented) |
| Timestamp cache GC | Files reachable under active partition include globs, not excluded by partition or hard-exclude rules, and older than the configured timestamp cutoff. (documented) |
| Base cache restore/save | Paths emitted by createBaseCachePaths and action-owned family, lineage, and generation keys from the cache model. (documented) |
| Delta package staging/upload | Post-build files in managed partitions that differ from the pre-build manifest. (documented) |
| Delta package download/apply | Artifact packages selected by configured dependent-jobs, current run identity, and artifact name prefix. (documented) |
| Gradle wrapper static validation | Targeted gradle-wrapper.properties files under base-directory. (documented) |
| Gradle wrapper network download | Canonical services.gradle.org, raw.githubusercontent.com, and, when token-scoped, GitHub API host requests derived from a validated distribution URL. (documented) |
| GnuPG signature verification | Wrapper payload/signature bytes and pinned trusted keys passed to the GPG subprocess with stripped environment. (documented) |
| Job summaries/logs | Dynamic strings emitted by bootstrap/prepare/finalize reporting sinks. (documented) |
5. Assumptions About the Environment
- Runtime is Node.js 24, as declared by the action metadata and package engine range. (documented)
- The active CI platform is GitHub Actions with functioning
@actions/core,@actions/cache, and@actions/artifactbehavior. (documented) - Java is installed or discoverable when cache families should include a real Java major version; otherwise the family renders Java as
0. (documented) - GnuPG is available for Gradle wrapper provenance verification unless that verification mode is not used. (documented)
GITHUB_WORKSPACE,RUNNER_TEMP,GITHUB_*,RUNNER_OS, andRUNNER_ARCHare supplied honestly by the runner or overridden by documented action inputs. (inferred)- The runner filesystem enforces normal process permissions, symlink behavior,
O_EXCL,rename,lstat,realpath, and temporary-directory semantics. (inferred) - Runner filesystem access-time behavior is platform-specific and may be stale, deferred, coalesced, or disabled; timestamp cache GC treats timestamps as best-effort freshness signals, not precise usage proof. (documented)
- The action runs as a single Node process per action invocation; separate parallel jobs communicate only through cache/artifact backends, not shared memory. (inferred)
- Cache and same-run artifact service access uses GitHub-provided, job-scoped Actions runtime credentials. Declared
GITHUB_TOKENpermissions separately govern authenticated GitHub API calls and explicit cross-run/repository artifact lookup. (documented)
Concurrency assumptions:
- Within one job, prepare/finalize state is paired through GitHub post-action state and a single-run prepare guard. (documented)
- Distributed workers may run concurrently, but only aggregators save merged base cache state; workers upload deltas instead of saving the base cache. (documented)
- Duplicate dependent delta paths are rejected by default; operators can opt into newest-mtime resolution with
allow-duplicate-dependent-delta-paths=true. (documented)
Memory, CPU, and time assumptions:
- Wrapper downloads have retry caps, per-request timeouts, and response-size caps. (documented)
- Manifest capture is proportional to the number and size of files in managed cache partitions; timestamp GC uses metadata-only scanning proportional to file count. There is no documented global maximum cache size or file count. (inferred)
- Delta package processing is proportional to downloaded artifact contents after schema/path/hash validation; exact global size and count limits are delegated to the artifact backend and filesystem capacity. (inferred)
What the project does not do to its host:
| Side effect | Claim |
|---|---|
| Opens listening sockets | No listening sockets are created. (inferred) |
| Makes network requests | Yes, for Gradle wrapper metadata/JAR downloads and GitHub toolkit cache/artifact/API operations. (documented) |
| Spawns child processes | Yes, java -version fallback and GnuPG verification. (documented) |
| Reads environment variables | Yes, selected GitHub, runner, Java, Maven, Gradle, token, and GPG-related variables. (documented) |
| Writes to workspace | Yes, Gradle wrapper JAR and metadata files may be written next to targeted wrapper properties files. (documented) |
| Writes to build-tool cache roots | Yes, cache restore, timestamp GC, prune-managed cleanup, delta apply, and build hooks may alter managed cache roots. (documented) |
| Writes to temporary directories | Yes, manifests, delta packages, downloads, and GPG homes use temporary locations. (documented) |
| Writes to logs/summaries/outputs/state | Yes, through GitHub Actions runtime APIs. (documented) |
| Installs signal handlers | None identified in source. (inferred) |
| Mutates process-wide locale or FPU state | None identified in source. (inferred) |
| Sends tokens to arbitrary hosts | Auth headers are scoped by exact HTTPS hostname. (documented) |
5a. Build-Time and Configuration Variants
| Knob | Default | Security-relevant effect | Maintainer stance |
|---|---|---|---|
read-only |
true on pull-request events, otherwise false |
Prevents base cache save and worker delta upload; aggregators perform no artifact exchange when true. | Production-supported default. (documented) |
cache-enabled |
true |
When false, cache restore/save/delta behavior is disabled; wrapper provisioning may still run for Gradle. | Supported. (documented) |
job-mode |
standalone |
Controls base-cache writers and delta behavior. distributed-worker skips base cache save; distributed-aggregator consumes deltas and saves merged state. |
Supported. (documented) |
allow-duplicate-dependent-delta-paths |
false |
When true, conflicting dependent deltas can resolve by modification time instead of failing. | Supported but should be used only when expected and acceptable. (documented) |
| Cache family generation | Action-owned | Always separates build tool, schema, Java major, runner OS/architecture, and partition fingerprint; only the namespace prefix is configurable. | Production-supported invariant. (documented) |
cache-partitions |
Tool defaults | Changes which cache files are managed; hard safety excludes remain enforced. | Supported. (documented) |
cache-gc-mode / cache-gc-older-than-days |
timestamp / 14 |
Deletes managed files before standalone/aggregator base-cache save when both effective access time and modification time are older than the cutoff; relies on best-effort filesystem timestamps. | Supported default; operators can disable with off or increase the cutoff. (documented) |
cleanup-enabled |
true |
Gates restore cleanup and timestamp garbage collection. | Supported global cleanup switch. (documented) |
restore-cleanup-mode |
none |
prune-managed deletes active managed paths after a hit and re-restores them for either build tool. |
Supported opt-in cleanup. (documented) |
github-token / GITHUB_TOKEN |
unset input, env fallback | Enables authenticated GitHub-host requests and artifact lookup. Token is not persisted in summaries/state by design. | Supported. (documented) |
BUILDISH_MAMMOTH_CACHE_GRADLE_GPG_COMMAND |
unset | Windows-only absolute GPG override; validated before spawn. Ignored on non-Windows. | Supported escape hatch. (documented) |
maven-user-home |
$MAVEN_USER_HOME or ~/.m2 |
Can target an absolute Maven user-home path outside the workspace, including its repository and wrapper distribution state. | Supported; operators must isolate custom roots across trust zones. (documented) |
gradle-user-home |
Tool default | Non-default values are rejected by the current version. | Supported default only. (documented) |
6. Assumptions About Inputs
Inputs accepted by the project:
- GitHub action inputs declared in
actions/github/gradle/action.ymlandactions/github/maven/action.yml. (documented) - Optional workspace-relative YAML/JSON config files, except secrets such as
github-tokenare rejected in config files. (documented) - GitHub Actions environment variables and event payload metadata. (documented)
- Workspace files including wrapper properties, build-tool cache files, and build reports. (documented)
- GitHub base cache entries and workflow artifact packages. (documented)
- Network responses for Gradle wrapper checksum, signature, and JAR resources. (documented)
- Output of
java -versionand GnuPG invocations. (documented)
Per-parameter trust table:
| Entry point | Parameter/input | Attacker-controllable? | Caller must enforce |
|---|---|---|---|
| Gradle/Maven action | config-file |
Yes, by workflow operator; possibly by repository contributor if workflow points at repo file | Only reference config files from trusted branches when write permissions or secrets are available. (inferred) |
| Gradle/Maven action | base-directory |
Yes, by workflow operator | Keep it repository-relative and pointed at intended project root. (documented) |
| Gradle/Maven action | cache-enabled |
Yes, by workflow operator | Set intentionally; disabling removes cache behavior but not all Gradle provisioning behavior. (inferred) |
| Gradle/Maven action | read-only |
Yes, by workflow operator | Do not override to false for untrusted pull-request code unless the cache poisoning risk is accepted. (documented) |
| Gradle/Maven action | job-mode |
Yes, by workflow operator | Match job topology; use workers for parallel producers and aggregator for the single writer. (documented) |
| Gradle/Maven action | dependent-jobs |
Yes, by workflow operator | Include only expected worker jobs and declare needs for them. (documented) |
| Gradle/Maven action | allow-duplicate-dependent-delta-paths |
Yes, by workflow operator | Leave false unless last-writer/newest-mtime behavior is acceptable. (documented) |
| Gradle/Maven action | cache-key-prefix |
Yes, by workflow operator | Choose prefixes that avoid unintended cross-project or cross-context reuse. (inferred) |
| Gradle/Maven action | cache-partitions |
Yes, by workflow operator/config file | Do not include paths containing secrets or machine-local volatile state; hard excludes are not a full secret scanner. (inferred) |
| Gradle/Maven action | cache-gc-mode, cache-gc-older-than-days |
Yes, by workflow operator/config file | Leave timestamp GC enabled for bounded cache growth; disable or increase cutoff only when old, rarely touched cache entries must be retained. (documented) |
| Gradle/Maven action | cleanup-enabled, restore-cleanup-mode |
Yes, by workflow operator/config file | Enable destructive cleanup only for managed roots whose contents can be recreated. (documented) |
| Gradle action | wrapper-properties-files, wrapper-properties-glob, process-all-wrapper-files |
Yes, by workflow operator/config file | Target only intended wrapper property files. (documented) |
| Gradle action | github-token |
Trusted secret, provided by workflow/operator | Scope token minimally and do not pass secrets to untrusted workflows. (inferred) |
| Maven action | maven-user-home |
Yes, by workflow operator | Avoid targeting directories that contain secrets or unrelated state; pass the .m2-equivalent root rather than its repository/ child. (documented) |
| GitHub context | github-event-name, github-ref-name, github-default-branch, github-job-name |
Yes, by workflow operator | In reusable workflows, pass caller context accurately; incorrect values can change read-only defaults, cache keys, and artifact identity. (documented) |
| Repository config | read-only and other allowed config-file keys |
Yes, by repository contributors and pull-request authors | Config may make read-only policy stricter but cannot lower the pull-request floor; write-enabling overrides must be direct workflow inputs. (documented) |
| GitHub env | GITHUB_TOKEN |
Trusted runner secret | Grant least privilege through workflow permissions. (inferred) |
| Workspace | gradle-wrapper.properties |
Yes for repository contributors | Treat changes to wrapper properties as code changes; run with strict validation. (documented) |
| Build cache root | Managed cache files | Yes, via build steps and restored cache/artifacts | Do not store secrets in managed partitions. (documented) |
| Artifact backend | Delta artifact metadata/payload | Potentially attacker-controlled by jobs able to publish artifacts in the same run | Restrict writable job topology and configure dependent-jobs accurately. (inferred) |
| Network | Wrapper checksum/signature/JAR bodies | Untrusted until validated | Rely on HTTPS, checksum, and signature verification; do not disable them for untrusted code. (documented) |
Size, shape, and rate assumptions:
- Cache namespace prefixes and complete action-owned cache keys are length- and character-restricted. (documented)
- Cache partition IDs, globs, artifact names, SHA-256 values, delta manifests, and metadata are schema-validated. (documented)
- Wrapper checksum and signature responses are capped at 64 KiB each; wrapper JAR responses are capped at 10 MiB; wrapper requests default to 3 attempts and 30 seconds per attempt. (documented)
- Delta discovery is capped at 1,000 current-run artifacts and 100 candidates per configured worker. Selected artifacts are capped at 2 GiB reported size, 4 GiB expanded size, and 200,000 manifest entries. Cache-root scanning remains subject to host/backend capacity. (documented)
7. Adversary Model
In-scope adversaries:
| Actor | Capabilities | Goals |
|---|---|---|
| Untrusted pull-request author | Can change repository files and build outputs for PR workflows; may influence wrapper properties, config files, and cache content observed during the run. | Poison shared caches, exfiltrate secrets through logs/summaries/cache/artifacts, cause action failure, or install malicious wrapper bytes. (inferred) |
| Malicious or compromised worker job in same workflow run | Can upload malformed or conflicting delta artifacts if it has artifact write permission and is referenced by the aggregator. | Poison merged cache, overwrite files outside the cache root, exhaust resources, or hide conflict. (inferred) |
| Network attacker or compromised wrapper content endpoint | Can return malicious, oversized, slow, or inconsistent wrapper metadata/JAR responses. | Install malicious wrapper JAR or deny service. (inferred) |
| Malicious cache entry producer | Can cause future restores to include unexpected cache files if a trusted workflow saved them under a reusable key. | Poison dependencies, cause build nondeterminism, or leak cached secret-bearing files. (inferred) |
| Static analyzer, fuzzer, or human reporter | Can report potential vulnerabilities. | Needs deterministic triage routing against this model. (inferred) |
Out-of-scope adversaries:
- An attacker who controls the trusted branch workflow YAML or action inputs has already crossed the repository’s CI trust boundary. (inferred)
- An attacker who controls the runner host, Node runtime, filesystem semantics, GPG binary, GitHub toolkit implementation, or GitHub cache/artifact service is outside this component’s enforceable boundary. (inferred)
- An attacker who can read repository secrets through another workflow step is outside this action’s boundary. (inferred)
- An attacker who requires maintainers to run non-default or discouraged configurations is handled as non-default-build or property-disclaimed triage, depending on maintainer stance. (inferred)
8. Security Properties the Project Provides
| Property | Conditions | Violation symptom | Severity tier | Provenance |
|---|---|---|---|---|
Workspace-relative config files cannot escape the workspace through direct .. traversal or symlink resolution. |
config-file is used through the normal Gradle/Maven config resolution path. |
Reads a config file outside the workspace. | Security-critical if attacker-controlled config path can read sensitive files. | (documented) |
| Repository config cannot lower the event-derived pull-request read-only floor. | Normal config-file overlay and normalization are used. | A checked-out read-only: false value enables cache or artifact writes on a PR. |
Security-critical in a privileged workflow that exposes write-capable credentials. | (documented) |
| Read-only distributed execution performs no artifact exchange or aggregation. | Effective read-only mode is true. | A worker uploads, or an aggregator contacts the artifact backend or mutates merged state. | Security-relevant write-policy violation. | (documented) |
| User-supplied repository-relative paths for base directory and Gradle wrapper selection reject absolute/rooted/drive/UNC traversal forms. | Normal action input normalization is used. | Action reads or writes outside intended workspace through those inputs. | Security-critical for untrusted PR/workspace contexts. | (documented) |
| Gradle wrapper properties must be regular files inside the workspace and not symlinks. | Gradle wrapper validation is enabled and target files are discovered normally. | Symlink traversal or non-file wrapper target is accepted. | Security-critical. | (documented) |
Gradle wrapper distribution URLs are pinned to canonical HTTPS services.gradle.org distribution URLs without credentials, port, query, or fragment. |
Gradle wrapper validation is not disabled. | Wrapper plan derives from an attacker-controlled host or URL shape. | Security-critical. | (documented) |
| Missing Gradle wrapper JARs are installed only after SHA-256 and pinned detached-signature verification. | Normal wrapper provisioning path, GPG available, and Gradle trusted key allowlist current. | Malicious wrapper JAR is installed or accepted. | Security-critical. | (documented) |
| Wrapper network requests are bounded by timeout, retry, and response-size caps. | Wrapper provisioning uses default or validated request settings. | Unbounded hang or memory exhaustion from wrapper endpoints. | Security-critical for availability if attacker can influence endpoints; otherwise reliability. | (documented) |
| GitHub auth headers are scoped by exact HTTPS host and token values are not written to summaries/state/logs. | GitHub platform adapter creates headers from github-token or GITHUB_TOKEN. |
Token sent to unrelated host or logged. | Security-critical. | (documented) |
| GPG child processes receive a minimal environment excluding CI tokens and application secrets. | Signature verification uses buildMinimalChildEnv. |
Token-bearing parent environment inherited by GPG. | Security-critical for confidentiality. | (documented) |
| Cache families always include action-owned build tool, schema, Java, runner, and partition dimensions. | Normal cache-model rendering is used. | Cache lineage collision across structurally incompatible states. | Security-critical if it enables cache poisoning across trust contexts; otherwise correctness. | (documented) |
| Pull-request events default to read-only cache behavior. | Event name resolves to pull_request or pull_request_target and caller does not override read-only. |
Untrusted PR saves base cache or uploads worker delta by default. | Security-critical for cache integrity. | (documented) |
| Distributed workers do not save shared base caches. | job-mode=distributed-worker. |
Multiple parallel workers write conflicting base cache state. | Correctness/availability; security-critical if it enables poisoned shared cache. | (documented) |
| Dependent delta envelopes must match repository, workflow/run, producer attempt, source revision when available, ref, cache family/lineage, build tool, partition layout, and runner. | Aggregator downloads worker deltas through normal path. | Cross-context or incompatible delta is applied. | Security-critical for cache integrity. | (documented) |
| Delta discovery, selected artifact size, expanded package size, and manifest cardinality are bounded before cache mutation. | Aggregator uses the normal v2 discovery and download path. | Same-run artifact writers cause unbounded metadata/package processing. | Availability boundary; it does not make trusted worker output safe. | (documented) |
| Delta artifact metadata and manifests are schema-validated, sorted, use portable cache-root sentinel, and contain lowercase SHA-256 hashes. | Delta package download path uses downloadAndVerifyDeltaArtifactPackage. |
Malformed package is accepted. | Security-critical. | (documented) |
| Delta payload file hashes are verified before apply. | Downloaded delta packages include payload files and metadata. | Tampered payload is applied. | Security-critical. | (documented) |
| Delta payload paths and cache-relative paths cannot escape package/root directories. | validateNormalizedRelativePosixPath and resolveNormalizedPathWithinRoot are used before staging/apply. |
Path traversal writes, deletes, or reads outside package/cache root. | Security-critical. | (documented) |
| Symlinks are rejected during delta package walks and before cache file read/write in the delta pipeline. | Normal manifest/delta package processing paths are used. | Symlink target outside cache root is read or overwritten. | Security-critical. | (documented) |
| Writes of wrapper JARs, GPG temp files, guard files, and delta payloads are atomic or use exclusive creation where documented. | Underlying filesystem honors wx/rename semantics. |
Partial writes or race-overwrite are observable. | Security-critical for integrity where attacker can race; otherwise reliability. | (documented) |
| Job-summary HTML table cells require escaped/safe HTML values at compile time, and Markdown summary values are escaped by helpers. | Callers use escapeHtml, createHtmlLink, escapeSummaryText, or explicit safeHtml only for trusted literals. |
Branch names, job names, paths, or URLs inject HTML/Markdown into summary. | Security-critical if it can spoof links or expose secrets; otherwise UI integrity. | (documented) |
| Hard cache excludes omit known unsafe Gradle and Maven state such as lock files and selected secret/volatile metadata. | Default hard excludes remain enforced. | Restored cache contains known lock/secret/volatile classes that should never be transported. | Security-critical for secret-bearing files; otherwise correctness/availability. | (documented) |
9. Security Properties the Project Does Not Provide
- No sandboxing of Gradle, Maven, shell, test, plugin, or build-script execution. The action manages cache state around the build; it does not constrain the build. (inferred)
- No guarantee that the contents of a restored dependency cache are semantically safe, up to date, license-compliant, or free of malicious artifacts. (inferred)
- No end-to-end cryptographic authenticity for Maven/Gradle dependency artifacts beyond what Maven/Gradle/repository configuration already provides. (inferred)
- No protection if operators grant write tokens/secrets to untrusted workflows or override
read-only=falsefor untrusted PRs. (inferred) - No guarantee that custom cache partitions are secret-free. Hard excludes cover known built-in unsafe paths, not all possible project-specific secrets. (documented)
- No constant-time, cryptographic, authentication, authorization, or access-control guarantees for its own APIs; it relies on GitHub Actions permissions and repository policy. (inferred)
- No promise that concurrent writers make newest-prefix restore globally deterministic; current-ref and default-branch candidates remain compatibility-bounded. (documented)
- No global resource-exhaustion guarantee for arbitrarily large cache roots or manifest capture. Traversal and hashing concurrency are bounded, but the documented delta-exchange caps cover artifact discovery and package validation rather than local cache-root cardinality. (inferred)
- No protection against malicious or compromised GitHub cache/artifact services, runner hosts,
gpg, Node.js, npm dependencies, or GitHub toolkit packages. (inferred) - No support guarantee for non-GitHub CI execution paths in the current model. (documented)
False-friend properties:
- SHA-256 hashes in manifests and delta packages provide tamper detection for package payloads; they are not an authorization mechanism and do not prove that a worker job was trustworthy. (inferred)
- Envelope identity checks limit accidental or same-run cross-context substitution; any job with artifact-write authority in the same trusted workflow run may still attempt to impersonate a configured worker, so workflow permissions and topology remain the authorization boundary. (inferred)
- Cache-family partition fingerprints prevent accidental cache-layout collisions; they do not prove that cache contents are safe or produced by trusted code. (inferred)
read-onlyprevents this action from saving cache/delta state; it does not make the rest of the workflow read-only. (inferred)- Gradle wrapper verification authenticates the wrapper JAR provisioning path; it does not authenticate all Gradle distributions, plugins, dependencies, or build scripts. (documented)
- Job summaries are escaped for display; they are not an audit log or tamper-proof record. (inferred)
Well-known attack classes left to the caller:
- Cache poisoning through overly broad workflow permissions or namespace sharing must be controlled by workflow topology and branch/PR policy. (inferred)
- Dependency confusion, malicious transitive dependencies, and compromised package repositories must be handled by Maven/Gradle dependency management and repository policy. (inferred)
- Secret exfiltration by malicious build scripts must be prevented by workflow permission/secrets design, not by this cache action. (inferred)
- Build cache poisoning at the build-tool semantic layer must be controlled through trusted build inputs and build-tool configuration. (inferred)
10. Downstream Responsibilities
Operators and workflow authors must:
- Pin the action reference to a reviewed commit or release appropriate for their repository policy. (inferred)
- Use least-privilege GitHub
permissions; ordinary cache and same-run artifact operations do not requireactions: write. Grantactionsaccess only at an explicit GitHub API or cross-execution boundary that documents the need. (documented) - Do not expose write-capable cache operation to untrusted pull-request code; preserve default PR read-only behavior unless the risk is explicitly accepted. (documented)
- Ensure reusable workflows pass the original caller’s event name, ref name, and default branch when needed, so read-only behavior and cache lineages reflect the caller. (documented)
- Keep distributed worker job names unique and configure aggregator
needsto include every worker. (documented) - Keep distributed workers and aggregators on compatible runner OS/architecture and identical cache-family/partition inputs. (documented)
- Treat
cache-partitionsas a data classification decision; do not include directories that may contain secrets or host-local state. (inferred) - Leave Gradle wrapper validation/provenance in strict/verify mode for production use unless maintainers document an approved exception. (inferred)
- Review changes to
gradle-wrapper.properties, cache config files, and workflow YAML as security-relevant. (inferred) - Install Java and GnuPG on runners when using cache families and Gradle wrapper provenance verification. (documented)
- Avoid sharing cache namespaces across repositories or trust zones on backends without repository isolation. Action-owned family dimensions prevent sharing across incompatible build tools, Java versions, runners, and partition layouts. (inferred)
- For self-hosted runners, ensure temporary directories, Maven local repository paths, Gradle user homes, and workspace cleanup policies do not retain sensitive state across trust boundaries. (inferred)
11. Known Misuse Patterns
- Setting
read-only=falseon pull-request workflows from forks, allowing untrusted code to write cache or delta state. (inferred) - Choosing a
cache-key-prefixthat intentionally overlaps unrelated trust domains on a backend without repository-level isolation. (inferred) - Adding custom cache partitions that include secret-bearing build output, credentials, local keystores, or machine-specific state. (inferred)
- Enabling
allow-duplicate-dependent-delta-paths=trueto mask real worker conflicts instead of excluding volatile files or fixing partition boundaries. (documented) - Running a distributed aggregator without
needson all worker jobs, causing incomplete or stale delta consumption. (documented) - Reusing worker job names in a workflow run, causing artifact identity collisions. (documented)
- Disabling Gradle wrapper validation/provenance in workflows that build untrusted repository content. (inferred)
- Assuming wrapper JAR verification covers dependency/plugin provenance. (documented)
- Pointing Maven local repository at a shared self-hosted-runner location that also serves jobs from less trusted repositories. (inferred)
11a. Known Non-Findings
| Reported pattern | Why it is not a bug under this model | Suppression guidance |
|---|---|---|
github-token or GITHUB_TOKEN is read from inputs/env. |
Token use is intentional; headers are scoped to exact HTTPS GitHub API host and diagnostics log presence only. See §8. | Suppress if no token value is logged or sent to a non-matching host. (documented) |
safeHtml() exists and can mark strings safe. |
It is an explicit opt-in for trusted literals/pre-sanitized fragments; normal dynamic content should use escaping helpers. | Flag only call sites passing attacker-controlled raw values. (documented) |
java -version may spawn a process. |
Java detection is intentional for cache keying and prefers $JAVA_HOME/release before PATH fallback. |
Suppress command-spawn findings unless untrusted input controls executable path. (documented) |
gpg is spawned. |
GPG is required for detached-signature verification; environment is stripped and Windows override is validated. | Suppress generic spawn findings unless command path validation or env stripping is bypassed. (documented) |
Cache save is skipped for distributed-worker. |
This is intentional writer coordination, not a broken save path. | Suppress reports claiming workers should save base cache. (documented) |
| Default-branch generations can warm a cold current-ref lineage. | The fallback is explicit and remains inside the same action-owned compatibility family. | Treat as by design unless backend scoping lets the lineage cross an unrepresented trust boundary. (documented) |
| Hard excludes cannot be removed by custom partitions. | Hard excludes are deliberate safety policy for known volatile/secret/lock files. | Suppress reports treating this as lack of configurability. (documented) |
| Delta manifests use portable cache-root sentinel instead of absolute paths. | This prevents worker absolute paths from leaving the worker and is required for portability. | Suppress reports expecting absolute path preservation in artifacts. (documented) |
Non-src/ findings in tests, docs, release tooling, or node_modules. |
Those components are out of this runtime model unless they demonstrate a reachable runtime issue. | Route to OUT-OF-MODEL: unsupported-component. (inferred) |
12. Conditions That Would Change This Model
Revise this model when any of the following occur:
- A non-GitHub CI provider becomes supported. (documented)
- A new public action input, environment override, config-file key, or output is added. (inferred)
- Default values for
read-only, wrapper validation/provenance, job modes, cleanup, or duplicate delta handling change. (inferred) - A new build tool adapter or new cache partition family is added. (inferred)
- Maven gains wrapper provisioning or other network/subprocess behavior. (inferred)
- The distributed delta package schema, artifact selection identity, or merge conflict rules change. (inferred)
- The action starts reading or writing new filesystem locations outside current workspace/cache/temp roots. (inferred)
- The action begins handling secrets, credentials, private package repositories, or dependency provenance directly. (inferred)
- Any vulnerability report cannot be routed to one of the §13 dispositions. That is a
MODEL-GAPand should revise §8, §9, or the relevant assumptions. (inferred)
13. Triage Dispositions
| Disposition | Meaning | Licensed by |
|---|---|---|
VALID |
Violates a property the project claims, via an in-scope adversary and input. | §6, §7, §8 |
VALID-HARDENING |
No §8 property is violated, but the API makes a §11 misuse easy enough that the project elects to harden it. Report privately; fix at maintainer discretion; typically no CVE. | §11 |
OUT-OF-MODEL: trusted-input |
Requires attacker control of a parameter the model marks trusted. | §6 |
OUT-OF-MODEL: adversary-not-in-scope |
Requires an attacker capability the model excludes. | §7 |
OUT-OF-MODEL: unsupported-component |
Lands only in tests, docs, release tooling, node_modules, generated bundles, or other code placed out of runtime scope. |
§3 |
OUT-OF-MODEL: non-default-build |
Only manifests under a non-default or maintainer-discouraged configuration variant. | §5a |
BY-DESIGN: property-disclaimed |
Concerns a property the project explicitly does not provide. | §9 |
KNOWN-NON-FINDING |
Matches a documented recurring false positive. | §11a |
MODEL-GAP |
Cannot be cleanly routed to any listed disposition. Revise this model before final triage. | §12 |
14. Open Questions for Maintainers
Wave 1 - publishability and production posture:
| Question | Proposed answer | Lands in |
|---|---|---|
Should this document live at docs/threat-model.md, be linked from user security docs, or be mirrored at repository root? |
Keep docs/threat-model.md canonical and link from docs/user/security.md and SECURITY-ASSESSMENT.md. |
§1 |
Is maven-user-home intentionally allowed to point outside the workspace? |
Yes, because Maven user homes commonly live outside workspaces, but operators must not share them across trust zones. | §5a, §10 |
| Are the rough resource guarantees in §8 intentionally limited to wrapper downloads and delta validation, with no global cache-root cap? | Yes; large cache roots are operator responsibility and backend limits apply. | §5, §8, §9 |
Wave 2 - trust roles and adversaries:
| Question | Proposed answer | Lands in |
|---|---|---|
| Is a trusted-branch repository contributor considered trusted for cache contents? | Yes; repository branch protection determines that trust. | §2, §7 |
| Should malicious build scripts in untrusted PRs be explicitly in scope only for cache poisoning/exfiltration through this action, not for arbitrary workflow compromise? | Yes. The action should not be modeled as a build sandbox. | §3, §7, §9 |
| Is a compromised GitHub cache/artifact service out of scope? | Yes; the action validates package structure and hashes but relies on GitHub service trust and permissions. | §3, §7, §9 |
| Is dependency provenance intentionally left to Maven/Gradle and repository policy? | Yes. This action manages transport/reuse of cache files, not package authenticity. | §9, §10 |
| Should self-hosted runner persistence across repositories be called out more strongly? | Yes, as an operator responsibility for cache roots and temp/workspace cleanup. | §5, §10 |
Wave 3 - side effects and non-findings:
| Question | Proposed answer | Lands in |
|---|---|---|
| Are the negative side-effect claims complete: no listening sockets, no signal handlers, no process-wide locale/FPU mutation? | Yes for the current source tree. | §5 |
Should findings in generated dist/ be triaged through source files rather than directly against bundles? |
Yes, unless the release bundle differs from source behavior. | §3, §11a |
Should safeHtml() be listed as a known non-finding or should every use require local justification? |
Keep it as known non-finding but require review for attacker-controlled strings. | §11a |
| Should token-presence diagnostics remain explicitly permitted? | Yes, as long as values are never logged. | §8, §11a |
| Should cross-runner delta reuse remain categorically unsupported? | Yes. | §3, §8, §10 |
15. Optional Machine-Readable Companion
No machine-readable companion is included in this draft. If automated triage consumes this model, generate docs/threat-model.yaml from the prose sections containing:
- Component families and in/out-of-scope status from §2 and §3.
- Entry points and per-parameter trust levels from §6.
- Security-relevant knobs from §5a.
- Claimed properties and violation symptoms from §8.
- Disclaimed properties and false friends from §9.
- Known non-findings from §11a.
- Closed triage dispositions from §13.