diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index bdf2a3ae8b4..f18b4e600e1 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -24,8 +24,8 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"4f7e5c41cd2f17c529fb86458709bbce3bf49628d87bd079f9b43be66051fdc3","strict":true,"agent_id":"copilot"} diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 9d49b69408d..ef115486749 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -24,8 +24,8 @@ # Resolved workflow manifest: # Imports: # - shared/jqschema.md -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"e42b64ea2fb20f9c883549780d089111742a80762ff916976fd105379c709842","strict":true,"agent_id":"claude"} diff --git a/.github/workflows/daily-compiler-quality.lock.yml b/.github/workflows/daily-compiler-quality.lock.yml index b2045e9b833..1c35ff05653 100644 --- a/.github/workflows/daily-compiler-quality.lock.yml +++ b/.github/workflows/daily-compiler-quality.lock.yml @@ -24,8 +24,8 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5a9f20ae90588d58e44d24939df67e2cac4a5b7f8914793ecca1886e4d62c025","strict":true,"agent_id":"copilot"} diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml index d02afd7ed0d..2ec4500534f 100644 --- a/.github/workflows/daily-file-diet.lock.yml +++ b/.github/workflows/daily-file-diet.lock.yml @@ -25,8 +25,8 @@ # Resolved workflow manifest: # Imports: # - shared/activation-app.md -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # - shared/safe-output-app.md # diff --git a/.github/workflows/daily-function-namer.lock.yml b/.github/workflows/daily-function-namer.lock.yml index b7b7d9e3393..f93b93cd546 100644 --- a/.github/workflows/daily-function-namer.lock.yml +++ b/.github/workflows/daily-function-namer.lock.yml @@ -24,8 +24,8 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"3000e055e2609285d5978a0e5a7929aaa0a2e0349fb8633338acdac9368ec7b0","strict":true,"agent_id":"claude"} diff --git a/.github/workflows/daily-testify-uber-super-expert.lock.yml b/.github/workflows/daily-testify-uber-super-expert.lock.yml index 40ee431b643..d4bf5fe0c9f 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -25,8 +25,8 @@ # Resolved workflow manifest: # Imports: # - shared/activation-app.md -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # - shared/safe-output-app.md # diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index 1bbaf6a5fb2..aa79b450be3 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -24,8 +24,8 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/qmd.md # - shared/reporting.md # diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index 0d7b16d6471..ae6f9b6032e 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -24,8 +24,8 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"45a3b3d6fa12808929677c56136017f7369ac7cb1886c88501279a51a7b6500a","strict":true,"agent_id":"codex"} diff --git a/.github/workflows/glossary-maintainer.lock.yml b/.github/workflows/glossary-maintainer.lock.yml index 21c9bfca06e..1cc66250783 100644 --- a/.github/workflows/glossary-maintainer.lock.yml +++ b/.github/workflows/glossary-maintainer.lock.yml @@ -26,8 +26,8 @@ # Imports: # - ../agents/technical-doc-writer.agent.md # - ../skills/documentation/SKILL.md -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/qmd.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b74cb3c536157c4794068da770c3cfd6ca30b5d0ffbfe95990b6147b6f97f982","strict":true,"agent_id":"copilot"} diff --git a/.github/workflows/go-fan.lock.yml b/.github/workflows/go-fan.lock.yml index 921eea7f7bd..61c624a207e 100644 --- a/.github/workflows/go-fan.lock.yml +++ b/.github/workflows/go-fan.lock.yml @@ -24,8 +24,8 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a0cfeca82bc472d0f9c93528021e5c3fec79201341ef88f80027cae3fcd0b580","strict":true,"agent_id":"claude"} diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index a1ac30e2199..1d0d7ac26ba 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -35,8 +35,8 @@ # - shared/mcp/microsoft-docs.md # - shared/mcp/notion.md # - shared/mcp/sentry.md -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/mcp/server-memory.md # - shared/mcp/slack.md # - shared/mcp/tavily.md diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 827525d26f1..73d231de766 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -24,8 +24,8 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"414f4c7ae0fd2681b02648ca35945a676d382a50fc17081df6cbca04288663bc","strict":true,"agent_id":"copilot"} diff --git a/.github/workflows/repository-quality-improver.lock.yml b/.github/workflows/repository-quality-improver.lock.yml index 1d3d37ca89f..cc08de3e208 100644 --- a/.github/workflows/repository-quality-improver.lock.yml +++ b/.github/workflows/repository-quality-improver.lock.yml @@ -24,8 +24,8 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"22c747872e9dd8339a43a9fe92e851adb398bed72690aeba5d22fb96e1b62b78","strict":true,"agent_id":"copilot"} diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index 7ac332b8e5b..93563c6a2e4 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -24,8 +24,8 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"8a68127b1888587b0445b5b055dc37c7d3f24a62e752d4978ae7ce624f15e646","strict":true,"agent_id":"claude"} diff --git a/.github/workflows/sergo.lock.yml b/.github/workflows/sergo.lock.yml index 6879f6fec6c..2ef7eb3e87a 100644 --- a/.github/workflows/sergo.lock.yml +++ b/.github/workflows/sergo.lock.yml @@ -24,8 +24,8 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5aee3d458efb1579e66dd4f560c29dc760203ab1c9195ef48ad0251472b6a524","strict":true,"agent_id":"claude"} diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 1141a068a15..5f837da6e1e 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -29,8 +29,8 @@ # - shared/github-queries-mcp-script.md # - shared/go-make.md # - shared/mcp-pagination.md -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/mcp/tavily.md # - shared/reporting.md # diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 1dcf06c80aa..b20082ebe32 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -25,8 +25,8 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/qmd.md # - shared/reporting.md # diff --git a/.github/workflows/smoke-copilot-arm.lock.yml b/.github/workflows/smoke-copilot-arm.lock.yml index 912bd601362..690d51407d9 100644 --- a/.github/workflows/smoke-copilot-arm.lock.yml +++ b/.github/workflows/smoke-copilot-arm.lock.yml @@ -26,8 +26,8 @@ # Imports: # - shared/gh.md # - shared/github-queries-mcp-script.md -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"2e0f56dd848e004d1382bd59038ab27e5bee664d9e065d8ff1ac2c0a2822cb5c","agent_id":"copilot"} diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 6023b1f7c17..4b6eb5a2d12 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -26,8 +26,8 @@ # Imports: # - shared/gh.md # - shared/github-queries-mcp-script.md -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"38b177fbe41846384d9b7a2f66fe7f3f344216b074fae0e00c12e299dbc2ad47","agent_id":"copilot"} diff --git a/.github/workflows/terminal-stylist.lock.yml b/.github/workflows/terminal-stylist.lock.yml index 66d213c4546..971667cc02c 100644 --- a/.github/workflows/terminal-stylist.lock.yml +++ b/.github/workflows/terminal-stylist.lock.yml @@ -24,8 +24,8 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f0344a3841cec3dc22183e17b976c85e95a74fafbc989116453459a6418da6a3","strict":true,"agent_id":"copilot"} diff --git a/.github/workflows/typist.lock.yml b/.github/workflows/typist.lock.yml index aac7e5dbde0..6e8f8b3c35d 100644 --- a/.github/workflows/typist.lock.yml +++ b/.github/workflows/typist.lock.yml @@ -24,8 +24,8 @@ # # Resolved workflow manifest: # Imports: -# - shared/mcp/serena-go.md # - shared/mcp/serena.md +# - shared/mcp/serena-go.md # - shared/reporting.md # # gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"49fffa022552509cf47cc4d542b676a8cfe275c2c68a72bc8280c657fd059878","strict":true,"agent_id":"claude"} diff --git a/docs/src/content/docs/reference/safe-outputs-specification.md b/docs/src/content/docs/reference/safe-outputs-specification.md index 60255cf538e..2cd8cf2d3fa 100644 --- a/docs/src/content/docs/reference/safe-outputs-specification.md +++ b/docs/src/content/docs/reference/safe-outputs-specification.md @@ -7,9 +7,9 @@ sidebar: # Safe Outputs MCP Gateway Specification -**Version**: 1.13.0 +**Version**: 1.15.0 **Status**: Working Draft -**Publication Date**: 2026-02-18 +**Publication Date**: 2026-03-29 **Editor**: GitHub Agentic Workflows Team **This Version**: [safe-outputs-specification](/gh-aw/reference/safe-outputs-specification/) **Latest Published Version**: This document @@ -38,7 +38,8 @@ This specification follows World Wide Web Consortium (W3C) formatting convention 8. [Protocol Exchange Patterns](#8-protocol-exchange-patterns) 9. [Content Integrity Mechanisms](#9-content-integrity-mechanisms) 10. [Execution Guarantees](#10-execution-guarantees) -11. [Appendices](#appendix-a-conformance-checklist) +11. [Cache Memory Integrity](#11-cache-memory-integrity) +12. [Appendices](#appendix-a-conformance-checklist) --- @@ -68,6 +69,14 @@ This specification uses the following terms with precise definitions: **Provenance**: Metadata identifying the workflow and run that created a GitHub resource. Included in footers or API metadata fields. +**Integrity Level**: A classification of the trust level assigned to a workflow run based on its guard policy. The four levels in descending order of trust are: `merged`, `approved`, `unapproved`, and `none`. + +**Policy Hash**: An 8-character hexadecimal digest of the canonical form of a workflow's guard policy fields (`blocked-users`, `min-integrity`, `repos`, `trusted-bots`, `trusted-users`). Used as part of the cache key to detect policy changes. + +**Integrity Branch**: A Git branch within the cache memory repository corresponding to a specific integrity level. Each branch holds data written exclusively by runs at that integrity level. + +**Cache Poisoning**: A Bell-LaPadula write-up violation where a lower-integrity agent writes data to a shared cache store that is subsequently consumed by a higher-integrity run without provenance verification. + --- ## 1. Introduction @@ -500,7 +509,28 @@ This specification addresses five primary threat scenarios: *Residual Risk*: Misconfigured allowlists may permit unintended targets. Mitigation: principle of least privilege in configuration, periodic review. -### 3.2.6 Cross-Repository Security Model +**Threat T6: Cache Integrity Poisoning** + +*Attack Vector*: A `none`-integrity agent writes malicious or attacker-controlled data to a shared cache-memory directory. A subsequent `merged` or `approved`-integrity run blindly restores and consumes that data, violating the Bell-LaPadula write-up property (lower-integrity subjects MUST NOT write where higher-integrity subjects read). + +*Examples*: + +- A `none`-integrity run injects fabricated results into a shared JSON cache file, causing a `merged`-integrity run to act on false analysis data +- A compromised workflow at `unapproved` integrity poisons a shared state file consumed by `approved` runs in later workflow steps +- Legacy flat-file caches shared across integrity levels with no provenance attribution + +*Architectural Mitigations*: + +| Layer | Mechanism | Effectiveness | +|-------|-----------|---------------| +| **Integrity-scoped cache keys** | Key encodes integrity level and policy hash; different levels never share the same cache entry | High | +| **Git-backed integrity branching** | Each integrity level writes to its own Git branch; branch structure enforces write isolation | High | +| **Merge-down semantics** | Lower-integrity runs receive higher-integrity data via read-only merge; reverse never occurs | High | +| **Policy hash invalidation** | Any change to `allow-only` policy fields forces a cache miss, preventing stale policy inheritance | Medium | + +*Residual Risk*: A compromised runner may directly manipulate the `.git` directory within the restored cache tarball. Mitigation: restrict runner access to trusted environments and enable repository-level security policies. + + **Repository Reference Format** @@ -4033,7 +4063,262 @@ This section defines required behavior for unusual or boundary conditions. --- -## Appendix A: Conformance Checklist +## 11. Cache Memory Integrity + +### 11.1 Overview and Motivation + +The cache-memory subsystem provides agents with a persistent filesystem share backed by GitHub Actions cache. Prior to this specification version, caches used a flat directory structure with no integrity provenance. This allowed a `none`-integrity agent to write data into a shared cache store that was subsequently restored and consumed by a higher-integrity run—a Bell-LaPadula write-up violation (Threat T6). + +This section specifies the integrity-aware cache architecture that prevents cross-integrity cache contamination while preserving the ability for lower-integrity runs to read data produced by higher-integrity runs (read-down semantics). + +**Design Goals**: + +1. **Write isolation**: Data written at integrity level *L* MUST NOT be visible to a run at integrity level *H* where trust(*H*) > trust(*L*) (no write-up). +2. **Read-down access**: A run at integrity level *L* MAY read data produced by runs at higher integrity levels (read-down is permitted and expected). +3. **Policy binding**: A cache entry MUST be invalidated when the guard policy changes, preventing data inherited under one policy from being consumed under a different, potentially more permissive policy. +4. **Transparency**: The agent MUST remain unaware of the git repository structure within the cache directory. The agent reads and writes plain files as normal. +5. **Migration**: Legacy flat-file caches (with no `.git` directory) MUST be automatically imported onto the `merged` integrity branch on first use. + +### 11.2 Integrity Levels + +Four integrity levels are defined, ordered from highest to lowest trust: + +| Level | Description | +|-------|-------------| +| `merged` | Content that has passed code review and been merged into the default branch | +| `approved` | Content from pull requests that have been reviewed and approved | +| `unapproved` | Content from open, un-approved pull requests | +| `none` | Content from workflows without a configured guard policy | + +The ordering MUST be: `merged` > `approved` > `unapproved` > `none`. + +### 11.3 Integrity-Aware Cache Key Format + +**Requirement CI1: Integrity-Scoped Keys** + +All cache-memory keys MUST include the integrity level and policy hash as prefixes, in the following format: + +``` +memory-{integrityLevel}-{policyHash}-[{cacheID}-]{workflowID}-{runID} +``` + +Where: + +- `{integrityLevel}` is the `min-integrity` value from the guard policy, or `none` when no guard policy is configured. +- `{policyHash}` is the 8-character hex prefix of the SHA-256 policy hash (see Section 11.4), or the sentinel string `nopolicy` when no guard policy is configured. +- `{cacheID}` is the user-defined cache identifier. The `default` cache ID MUST be omitted from the key to maintain a clean format. +- `{workflowID}` is the sanitized workflow identifier (`GH_AW_WORKFLOW_ID_SANITIZED`). +- `{runID}` is the GitHub Actions run identifier (`github.run_id`). + +**Examples**: + +``` +# Default cache, with guard policy (min-integrity: unapproved, 8-char policy hash) +memory-unapproved-7e4d9f12-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }} + +# Default cache, no guard policy +memory-none-nopolicy-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }} + +# Named "session" cache, no guard policy +memory-none-nopolicy-session-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }} +``` + +**Requirement CI2: Restore Key Cascade** + +Restore keys MUST use the same integrity-scoped prefix so that a partial key match never crosses integrity level boundaries: + +``` +restore-keys: | + memory-{integrityLevel}-{policyHash}-{workflowID}- + memory-{integrityLevel}-{policyHash}- + memory- +``` + +The final fallback `memory-` entry exists solely to allow migration from legacy (non-scoped) caches and MUST be removed in a future major version. + +### 11.4 Policy Hash Computation + +**Requirement CI3: Deterministic Policy Hash** + +The policy hash MUST be computed as the first 8 characters of the lowercase hex SHA-256 digest of a canonical policy string, constructed as follows: + +1. For each of the following fields, produce a canonical value: + - `blocked-users`: Lowercase, sort, deduplicate. If specified as a GitHub Actions expression (e.g., `${{ github.event.sender.login }}`), prefix the raw expression with `expr:` (e.g., `expr:${{ github.event.sender.login }}`). + - `min-integrity`: Use the literal string value. + - `repos`: If a string (`"all"` or `"public"`), lowercase. If an array, lowercase all entries, sort, and deduplicate. + - `trusted-bots`: Reserved for future use; always empty. + - `trusted-users`: Reserved for future use; always empty. + +2. Concatenate the fields in the fixed order shown below, each followed by a newline: + + ``` + blocked-users:{canonicalBlockedUsers}\n + min-integrity:{minIntegrity}\n + repos:{canonicalRepos}\n + trusted-bots:\n + trusted-users:{canonicalTrustedUsers} + ``` + +3. Compute SHA-256 over the UTF-8 encoding of the canonical string. +4. Take the first 8 characters of the lowercase hexadecimal representation. + +**Requirement CI4: Sentinel for No-Policy Workflows** + +Workflows without a configured `min-integrity` field MUST use the sentinel string `nopolicy` in place of the policy hash. + +**Rationale**: The sentinel avoids hash computation for the common case of no guard policy and is visually distinguishable from a genuine policy hash in cache key inspection. + +### 11.5 Git-Backed Integrity Branching + +The cache-memory directory MUST be a Git repository when integrity branching is active. The `.git` directory rides along within the GitHub Actions cache tarball, persisting integrity branch history across workflow runs. + +**Repository Structure**: + +``` +/tmp/gh-aw/cache-memory/ +├── .git/ ← Git metadata (integrity branches, history) +│ └── refs/heads/ +│ ├── merged +│ ├── approved +│ ├── unapproved +│ └── none +├── file-written-by-merged-run.json +└── file-written-by-unapproved-run.txt +``` + +**Agent Transparency**: + +The agent MUST see and interact with only the plain files in the working directory. The agent MUST NOT need knowledge of Git or the branching structure. File system operations (read, write, delete) behave normally from the agent's perspective. + +**Requirement CI5: .git Directory Exclusion from Validation** + +File validation steps that enforce allowed extensions, size limits, or other constraints MUST skip the `.git` directory. The Git metadata directory contains binary and extension-less files that are not agent-managed content. + +### 11.6 Pre-Agent Setup (Integrity Checkout) + +A setup step MUST execute after the cache is restored and before the agent runs. The reference implementation of this step is `actions/setup/sh/setup_cache_memory_git.sh` (informative). All conforming implementations MUST satisfy requirements CI6–CI9 regardless of the implementation mechanism. + +**Requirement CI6: Git Repository Initialization** + +If the restored cache directory does not contain a `.git` subdirectory (fresh or legacy cache), the implementation MUST: + +1. Initialize a new Git repository on the `merged` branch. +2. Stage and commit all existing files (if any) as an `initial` commit. This migrates legacy flat-file caches automatically. +3. Create all four integrity branches (`merged`, `approved`, `unapproved`, `none`) from the same baseline commit. + +**Requirement CI7: Integrity Branch Checkout** + +After initialization (or if the repository already exists), the implementation MUST check out the branch corresponding to the run's `min-integrity` value. If `min-integrity` is absent, the `none` branch MUST be used. + +**Requirement CI8: Merge-Down from Higher-Integrity Branches** + +Before the agent executes, the implementation MUST merge all higher-integrity branches into the current branch, in descending trust order (highest first), using the `theirs` merge strategy (`-X theirs`) so that higher-integrity content takes precedence in conflicts. + +The merge semantics table is: + +| Run integrity | Branches merged in (read access) | Branches NOT merged in | +|---------------|----------------------------------|------------------------| +| `merged` | (none — highest, no merge-down) | `approved`, `unapproved`, `none` | +| `approved` | `merged` | `unapproved`, `none` | +| `unapproved` | `merged`, `approved` | `none` | +| `none` | `merged`, `approved`, `unapproved` | (none — reads all) | + +**Requirement CI9: Merge Failure Handling** + +If a merge from a higher-integrity branch fails for reasons other than "nothing to merge" or "already up-to-date", the implementation MUST abort the merge, restore the working tree to its pre-merge state, and exit with a non-zero status code to fail the workflow step. + +### 11.7 Post-Agent Commit (Integrity Persistence) + +A commit step MUST execute after the agent completes and before the cache is saved. The reference implementation is `actions/setup/sh/commit_cache_memory_git.sh` (informative). The step MUST execute regardless of whether the agent step succeeded or failed (i.e., unconditional execution, not gated on agent success). + +**Requirement CI10: Agent Changes Committed** + +The implementation MUST: + +1. Stage all changes within the cache directory (`git add -A`). +2. Commit on the current integrity branch with a message of the form `run-{GITHUB_RUN_ID}`. +3. Allow empty commits (`--allow-empty`) so that runs that made no file changes still produce a commit marker in the branch history. + +**Requirement CI11: Repository Compaction** + +After committing, the implementation MUST invoke `git gc --auto` to prevent unbounded growth of the Git object database within the cache tarball. + +**Requirement CI12: No-Repository Fallback** + +If no `.git` directory is present at commit time (e.g., the setup step was skipped), the commit step MUST exit cleanly with a diagnostic message and MUST NOT fail the workflow. + +### 11.8 Lifecycle Diagram + +The following diagram illustrates the full per-run lifecycle: + +``` +GitHub Actions Cache Restore + │ + ▼ +setup_cache_memory_git.sh + 1. If no .git: git init -b merged, import files, create all branches + 2. git checkout {integrity} + 3. For each higher-integrity branch (descending): + git merge {branch} -X theirs + │ + ▼ +Agent Execution + (reads/writes plain files — unaware of git) + │ + ▼ +commit_cache_memory_git.sh [if: always()] + 1. git add -A + 2. git commit --allow-empty -m "run-{run_id}" + 3. git gc --auto + │ + ▼ +GitHub Actions Cache Save + (tarball includes .git directory with all integrity branches) +``` + +### 11.9 Compliance Requirements + +| Requirement | Test ID | Level | +|-------------|---------|-------| +| CI1: Integrity-scoped cache keys | T-CI-001 | Required | +| CI2: Restore key cascade | T-CI-002 | Required | +| CI3: Deterministic policy hash | T-CI-003 | Required | +| CI4: Sentinel for no-policy workflows | T-CI-004 | Required | +| CI5: .git directory excluded from validation | T-CI-005 | Required | +| CI6: Git repository initialization | T-CI-006 | Required | +| CI7: Integrity branch checkout | T-CI-007 | Required | +| CI8: Merge-down from higher-integrity branches | T-CI-008 | Required | +| CI9: Merge failure handling | T-CI-009 | Required | +| CI10: Agent changes committed | T-CI-010 | Required | +| CI11: Repository compaction | T-CI-011 | Recommended | +| CI12: No-repository fallback | T-CI-012 | Required | + +### 11.10 Migration from Legacy Flat-File Caches + +Existing deployments using the pre-integrity cache format MUST expect a **cache miss** on the first run after upgrading to an implementation supporting this section. + +**Legacy key format** (before this section): +``` +memory-{workflowID}-{runID} +# Example: memory-my-workflow-12345678 +``` + +**New key format** (this section): +``` +memory-{integrityLevel}-{policyHash}-{workflowID}-{runID} +# Example (with policy): memory-unapproved-7e4d9f12-my-workflow-12345678 +# Example (without policy): memory-none-nopolicy-my-workflow-12345678 +``` + +The integrity level and policy hash prefixes are new components not present in legacy keys. Because the key formats differ, legacy cache entries will never match the new restore keys, resulting in a one-time cache miss. + +*Rationale*: Legacy cache data has no integrity provenance. Blindly consuming legacy data under the new integrity model would provide no security guarantee. The automatic migration path in Requirement CI6 handles any residual files from the old format by importing them to the `merged` branch on first initialization. + +Operators SHOULD communicate this expected one-time cache miss to their teams to avoid confusion during upgrade. + +--- + + **Required for Full Conformance**: @@ -4352,6 +4637,13 @@ safe-outputs: ## Appendix F: Document History +**Version 1.15.0** (2026-03-29): + +- **Added**: Section 11 "Cache Memory Integrity" specifying integrity-aware cache key format, git-backed branching, merge-down semantics, pre-agent setup, and post-agent commit requirements (CI1–CI12) +- **Added**: Threat T6 "Cache Integrity Poisoning" to Section 3.2, describing Bell-LaPadula write-up violations in cache-memory and their architectural mitigations +- **Added**: Terminology entries for *Integrity Level*, *Policy Hash*, *Integrity Branch*, and *Cache Poisoning* +- **Updated**: Table of Contents to include Section 11 + **Version 1.14.0** (2026-02-22): - **Added**: Section 5.5 "Templatable Fields" documenting support for GitHub Actions expressions in integer and boolean configuration fields