Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
840d5d2
fix(opencode): permit governed private free-model reviews
seonghobae Aug 8, 2026
8e22caf
test(opencode): preserve central runner contracts
seonghobae Aug 8, 2026
3362860
fix(opencode): retain delegated runner contracts
seonghobae Aug 8, 2026
23eb9f7
fix(opencode): keep stable quick-gate contracts
seonghobae Aug 8, 2026
58de30c
fix(test): restore exact quick-gate assertion
seonghobae Aug 8, 2026
daa1b36
fix(test): restore verified runner contract fixture
seonghobae Aug 8, 2026
dda2e68
test(opencode): reject type-confused free-model policy
seonghobae Aug 8, 2026
df7fcef
fix(opencode): require type-exact free-model policy
seonghobae Aug 8, 2026
e7adeb5
test(opencode): cover equals and duplicate model selectors
seonghobae Aug 8, 2026
8d22eeb
fix(opencode): parse model selectors fail closed
seonghobae Aug 8, 2026
f861d08
fix(opencode): align duplicate selector diagnostic
seonghobae Aug 8, 2026
891058b
test(opencode): pin wrapper cleanup and poll hardening
seonghobae Aug 8, 2026
e427cfb
fix(opencode): harden model-pool wrapper setup
seonghobae Aug 8, 2026
1ef9e3a
docs(opencode): fix governed free model identifiers
seonghobae Aug 8, 2026
9f27a42
test(opencode): reject zero fatal poll intervals
seonghobae Aug 8, 2026
9a9b3e0
fix(opencode): validate fatal poll and clean probes
seonghobae Aug 8, 2026
3405ffa
test(opencode): fail closed on private free-pool bypass
seonghobae Aug 8, 2026
654c6f0
test(opencode): reject malformed policy tree records
seonghobae Aug 8, 2026
0723847
test(opencode): cover provider guard fail-closed aliases
seonghobae Aug 8, 2026
c0fc46c
fix(opencode): reject truncated trusted policy tree records
seonghobae Aug 8, 2026
a952da8
fix(opencode): gate free candidates on verified visibility
seonghobae Aug 8, 2026
919f41e
fix(opencode): harden provider selector parsing
seonghobae Aug 8, 2026
45339d3
fix(opencode): normalize delegated runtime controls
seonghobae Aug 8, 2026
50c4b60
fix(opencode): validate model-pool integer controls
seonghobae Aug 8, 2026
1362055
fix(opencode): align delegated runner integrity markers
seonghobae Aug 8, 2026
6cf29ab
docs(opencode): align private free policy with live catalog
seonghobae Aug 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Semantic Versioning where the repository publishes a release.

- Added a trusted pull-request comment router for `@cwl-noema-review` and review-only `@opencode-agent` dispatches, with an organization sweep, exact-head receipts, repository allowlisting, fixed runners, immutable checkout pins, and a permanent 100% statement/branch/docstring quality gate.
- Added exact-base `uv.lock` materialization that reconstructs standalone nested projects with a checksum-pinned official `uv` exporter, isolated frozen/offline execution, strict exact-pin and SHA-256 output validation, and complete Python 3.10/3.14 quality evidence.
- Added an auditable, fail-closed base-branch policy that lets explicitly `public_equivalent` private repositories prepend the anonymous OpenCode free-model pool without allowing a pull request to authorize its own external processing.

### Fixed

Expand All @@ -18,3 +19,7 @@ Semantic Versioning where the repository publishes a release.
- Bound each review-agent invocation key to the wrapper's complete canonical payload, including the base branch and requesting actor; altered fields with a valid-format key now fail before durable-leader election or forwarding, and wrapper write permission is job-scoped.
- Bound both trusted-uv quality jobs to `github.event.pull_request.head.sha` and added a permanent two-checkout regression contract so exact-head compatibility, coverage, docstring, and compilation claims cannot silently measure GitHub's generated pull-request merge revision.
- Made Strix treat only a single LiteLLM provider-error line containing NVIDIA NIM context and model-catalog 404 evidence as cross-model fallback evidence, rejecting cross-line signal assembly and provider-like target source literals; moved the public default to Nemotron 3 Super 120B and added a second NVIDIA hosted candidate before GitHub Models without neutralizing reported vulnerabilities.

### Security

- Scoped every OpenCode child process to its selected provider credential, stripped GitHub and Actions OIDC/runtime credentials from all model processes, and ensured anonymous free models, exports, and unknown provider prefixes inherit no provider keys.
228 changes: 228 additions & 0 deletions docs/doctoring/opencode-private-free-model-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
# OpenCode private-repository free-model policy

**Status:** Implemented design decision
**Decision date:** 2026-08-08
**Scope:** `ContextualWisdomLab/.github` OpenCode review control plane

## Decision

A private repository may use the anonymous `opencode-free/*` review pool only when
its trusted pull-request base commit contains the exact policy file below and the
reviewed head does not add, remove, rename, chmod, or modify that file.

```text
.github/opencode-private-free-models.json
```

```json
{
"schema_version": 1,
"allow_private_free_models": true,
"repository_data_classification": "public_equivalent",
"external_model_data_use_accepted": true
}
```

The declaration means all tracked repository content that OpenCode can read is
approved for processing under the external free-model terms as though it were
public. It does **not** mean the repository becomes public, and it is not a
claim that a scanner proved the absence of every secret or confidential fact.

The pull request that introduces or changes the policy remains ineligible. The
policy takes effect only after that change has passed normal review and reached
the base branch, on a subsequent pull request. Protect this file with normal
branch protection and, where available, `CODEOWNERS` review.

## Why an explicit policy is required

Repository visibility and data sensitivity are different attributes. A private
repository may contain only non-sensitive open-source work, while another may
contain customer data, personal data, unreleased intellectual property, access
credentials, or confidential architecture. The absence of configured GitHub
Actions secrets does not classify the source tree.

GitHub secret scanning is an important independent control, but it detects
supported patterns rather than proving that no confidential information exists.
For organization-owned private and internal repositories, secret scanning
requires GitHub Secret Protection on an eligible plan. Therefore, model egress
is enabled by an auditable data-owner declaration, not by a heuristic scan or by
repository visibility alone.

## Provider-data and catalog boundary

OpenCode documents free models as limited offerings used to collect feedback or
improve models. Its privacy documentation warns that some free endpoints may
retain or use collected data and that personal or confidential data must not be
submitted. Accordingly, the policy is restricted to `public_equivalent`
repositories and requires explicit acceptance of external-model data use.

The governed anonymous pool is synchronized to the zero-cost OpenCode Zen catalog
published in the primary Zen documentation. At the current decision revision it
contains exactly these seven aliases:

1. `opencode-free/nemotron-3-ultra-free`
2. `opencode-free/deepseek-v4-flash-free`
3. `opencode-free/north-mini-code-free`
4. `opencode-free/laguna-s-2.1-free`
5. `opencode-free/ling-3.0-flash-free`
6. `opencode-free/big-pickle`
7. `opencode-free/mimo-v2.5-free`

Aliases previously carried as `hy3-free`, `minimax-m3-free`, `glm-5-free`,
`kimi-k2.5-free`, and `qwen3.6-plus-free` are not in the current documented
zero-cost catalog and are therefore removed before model selection. The wrapper
never infers that an arbitrary `opencode-free/*` prefix is actually free. A
catalog change requires an independently reviewable source update.

Candidate availability is still runtime-dependent. A provider rejection or
retirement remains ordinary bounded fallback evidence; it does not weaken review
or merge gates.

## Repository-visibility boundary

Preconfigured anonymous candidates are not themselves authorization. The wrapper
first needs positive visibility evidence:

- a trusted caller may provide `OPENCODE_REPOSITORY_IS_PRIVATE=false`; or
- when that signal is absent, the wrapper may prove only the **public** case by
performing a credential-free `git ls-remote` against a strictly validated
`https://github.com/ContextualWisdomLab/<repository>[.git]` origin.

`true`, malformed visibility input, private/auth-required Git access, timeout,
transport failure, missing remote metadata, or any other indeterminate outcome is
fail-closed. The wrapper removes every preconfigured anonymous candidate and the
unchanged trusted-base policy becomes the sole re-enable path. The public probe
runs with GitHub, Actions, model-provider, and OIDC credentials removed and with
Git credential helpers disabled.

This preserves public-repository behavior without treating an untrusted candidate
list as visibility evidence and prevents a private caller from bypassing policy
by pre-populating `OPENCODE_MODEL_CANDIDATES`.

## Credential boundary

Each OpenCode subprocess receives only the credential for its selected provider.
In particular, an anonymous `opencode-free/*` process receives none of these
values:

- GitHub tokens
- GitHub Actions OIDC request credentials
- GitHub Actions runtime, cache, or results credentials
- OpenCode application tokens
- NVIDIA NIM keys
- OpenCode Zen keys
- OpenAI keys
- OpenRouter keys
- GitHub Models tokens

Session export runs without any provider credential. Unknown future provider
prefixes also default to zero provider credentials until they are explicitly
classified. The guard recognizes OpenCode's long and short model selectors
(`--model`, `--model=`, `-m`, and `-m=`), rejects duplicate or missing model
selectors, and stops option parsing at `--` so argument text cannot accidentally
change credential selection.

The model remains read-only under the existing OpenCode review agent contract.
Credential isolation does not make confidential source safe to send to an
external model; the repository-level data classification remains the primary
eligibility control.

## Fail-closed validation

The policy checker:

- accepts only full 40-character base and head commit SHAs;
- reads the policy directly from the immutable base Git tree;
- rejects a policy changed by the current head;
- requires the `git ls-tree -z` response to be exactly one NUL-terminated record,
rejecting truncated or extra records rather than reconstructing delimiters;
- accepts only one regular, non-executable `100644` blob at the fixed path;
- limits the blob to 4,096 bytes;
- requires strict UTF-8 and JSON without duplicate keys;
- rejects missing or unknown fields and requires the exact canonical values;
- ignores system and user Git configuration and disables hooks and filesystem
monitors during evaluation;
- removes preconfigured anonymous candidates on private or unverified calls before
policy evaluation; and
- leaves the existing keyed/private fallback pool unchanged on every denial or
local evaluation error.

The model-pool boundary also validates integer runtime, retry, cycle, and export
controls before shell arithmetic or `timeout` consumption. Malformed values fall
back to reviewed defaults rather than reaching Bash arithmetic or busy-looping a
runner.

## Operating procedure

1. Confirm the repository contains no credentials, personal data, customer data,
confidential documents, restricted source, or other data prohibited by the
free-model terms.
2. Resolve active secret-scanning alerts and enable Secret Protection, push
protection, generic patterns, and organization-specific custom patterns where
available.
3. Add the exact policy file in a separately reviewed pull request.
4. Merge that policy through normal branch protection. Its own pull request will
not use the private free pool.
5. On a later pull request, verify the OpenCode log records that the unchanged
trusted base policy enabled the anonymous candidates and verify the selected
child environment contains no GitHub, Actions, OIDC, or provider credentials.
6. Run a private negative control without the policy and verify anonymous
candidates remain disabled while configured keyed fallbacks remain available.
7. To disable the feature, remove or change the policy through a normal pull
request. The change takes effect after merge; the policy-changing pull request
itself remains fail-closed.

## Rejected alternatives

### Infer eligibility from missing Actions secrets

Rejected because repository source, history, fixtures, issues, and generated
review evidence may be confidential even when no Actions secret is configured.

### Let the current pull-request head add an opt-in marker

Rejected because untrusted code could authorize its own external disclosure.
The marker must already exist on the base and remain unchanged in the head.

### Trust a preconfigured `opencode-free/*` candidate as proof of eligibility

Rejected because candidate text is not a data-classification or visibility
signal. Private or unverified callers must pass the immutable-base policy gate.

### Send all provider keys and rely on agent instructions

Rejected because a model process does not need unrelated credentials. Provider
selection is enforced in the process environment rather than by prompt text.

### Treat secret scanning as a proof of public-equivalent data

Rejected because secret scanning is a defense-in-depth detector, not a complete
information-classification system.

## Verification evidence

The implementation includes tests for valid base policy activation, missing and
self-added policies, head mutations, unknown and weaker declarations, duplicate
JSON keys, symlinks, oversized blobs, malformed UTF-8 and JSON, Git failures,
truncated and extra `ls-tree -z` records, provider-specific credential retention,
anonymous free credential removal, short and long model selectors, option
termination, export isolation, unknown-provider fail-safe behavior, private
preconfigured-free bypass rejection, catalog filtering, visibility fail-closed
behavior, runtime integer controls, and wrapper ordering.

Operational acceptance remains separate from code-level tests. Issue #833 tracks
the required protected-base private canary, negative control, credential-absence
evidence, schema/evidence validation, independent review/protection gates, and
rollback rehearsal.

## References

GitHub. (n.d.-a). *Enabling secret scanning for your repository*. GitHub Docs.
Retrieved August 8, 2026, from
https://docs.github.com/en/code-security/how-tos/secure-your-secrets/detect-secret-leaks/enable-secret-scanning

GitHub. (n.d.-b). *Secrets*. GitHub Docs. Retrieved August 8, 2026, from
https://docs.github.com/en/actions/concepts/security/secrets

OpenCode. (n.d.). *Zen*. Retrieved August 9, 2026, from
https://opencode.ai/docs/zen
6 changes: 6 additions & 0 deletions docs/examples/opencode-private-free-models.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"schema_version": 1,
"allow_private_free_models": true,
"repository_data_classification": "public_equivalent",
"external_model_data_use_accepted": true
}
Loading
Loading