The toolkit bridges four trust domains: repository content, OCR and its LLM/MCP providers, CI secrets, and the GitLab API. None of the first three should be assumed safe merely because a job runs in a trusted project.
- Immutable base, source, reviewed-head, and release identities.
- Review integrity: selected files, evidence, coverage, findings, suppression decisions, and approval state.
- Private evidence, OCR result, configuration, and release-receipt artifacts.
- CI, GitLab, LLM-provider, registry, and optional MCP credentials.
- Published packages, tags, attestations, release assets, and provider-side merge-request state.
A merge-request contributor may choose repository paths and blob contents, including manifests, templates, accepted-decision-like text, nested guidance, Markdown delimiters, and high-cardinality but bounded tree shapes. They may trigger a review pipeline and supply text that resembles reviewer or tool instructions. Repository content, OCR/LLM/MCP output, provider responses, inherited process state, and persisted local artifacts are therefore untrusted inputs even when the toolkit produced an earlier version of the artifact.
The ordinary contributor is not assumed to control the protected target branch, protected CI variables, publishing identity, or trusted release environment. A compromised runner or a process with the same operating-system ownership as private artifacts is a stronger attacker; controls still fail closed where practical, but complete containment of that host-level compromise is outside the toolkit boundary.
- Repository paths and immutable Git blobs enter bounded collection and parsing.
- Persisted evidence, result, configuration, and receipt files re-enter schema validation and redaction.
- Typed evidence crosses into the compact OCR bootstrap and read-only MCP projections.
- OCR findings and coverage cross into GitLab Markdown, discussions, suppression, and approval decisions. Optional remote finding images add a third-party rendering boundary for normalized display metadata only.
- CI configuration and credentials cross into GitLab, LLM/MCP providers, package registries, and release automation.
- A release candidate crosses into protected-base authorization, publication, provenance, and external readback.
- Repository-controlled content remains data: it is neither imported nor executed and cannot promote itself into policy, permission, suppression, approval, or release authority.
- Every security-relevant claim remains bound to exact immutable identity, provenance, trust class, scope, and semantic applicability.
- Bounds apply while data is acquired and emitted. Exhaustion or malformed input degrades explicitly and cannot silently suppress an unrelated evidence domain.
- Hostile persisted state is accepted only through exact closed schemas whose related snapshots, indexes, coverage, deltas, and receipts agree atomically.
- Markdown and protocol projections preserve trust labels, neutralize control syntax, and expose only bounded context.
- External finding images are disabled by default. When explicitly enabled, their host, path vocabulary, colors, and alt text come from closed toolkit constants and normalized enums rather than repository or model-controlled strings; blocked images retain a text label.
- Provider mutations bind the reviewed identity when the provider supports such a guard; ambiguous non-idempotent writes preserve prior state rather than guessing success.
- Secrets remain outside repository-controlled context, logs, public notes, fixtures, and release artifacts.
Security severity depends on demonstrated reachability across these boundaries. Prompt-like or Markdown content injection is an integrity issue unless it is shown to alter a privileged action or mandatory control. Tampering that requires same-owner access to private local artifacts is not treated as an ordinary contributor privilege escalation without evidence of a lower-privilege writer. Governance scores and the single-maintainer review limitation are posture or residual-risk signals, not application vulnerabilities by themselves.
- Repository reads are bounded, rooted, symlink-aware, and exclude common dependency/build trees.
- Review-invocation metadata is provider-normalized from closed schemas. Numeric project, pipeline, job, and merge-request identifiers remain separate invocation facts. GitLab MR context admits only complete bounded title, description, labels, optional source branch, and source SHA; unknown fields, URLs, author profiles, comments, linked bodies, tokens, and arbitrary environment values are not collected. Author-controlled context carries invocation trust, remains raw only in the private evidence MCP, and cannot select policy, tools, permissions, suppression, posting, or approval.
- Generated Markdown escapes control characters and neutralizes GitLab quick actions.
- Actionable GitLab suggestions require an exact
existing_codematch against one bounded range in the immutable reviewed head blob. Multi-region omission markers, diff-prefixed replacements, unsafe fences, and unverifiable ranges retain the explanatory finding but cannot create an apply button. - Secrets and credential-shaped values are redacted before operational output.
- OCR result and provider response reads have byte limits.
- GitLab notes enforce both UTF-8 byte limits and Python character limits.
- Non-idempotent API writes are not blindly retried.
- Automatic approval is bound to the exact reviewed MR head after GitLab diff synchronization and bounded readback. A v2 review-time receipt makes any run with admitted mutable author-controlled MR context ineligible for approval; comment publication remains available. The transaction is add-only: because GitLab cannot bind unapproval to an immutable reviewed SHA, the toolkit never removes an existing approval. Project-owned approval reset and invalidation rules remain authoritative.
- Markers, fingerprints, snapshots, and rollback logic constrain repeated runs.
- Human replies are ownership boundaries: automation must not rewrite or resolve a discussion after a human takes part.
- Merge-request source SHA and merge-result SHA remain distinct.
The evidence engine reads exact base/head Git objects without checkout, refuses symlinks and submodules, stores redacted typed records and deltas in owner-only files, and exposes them through a closed read-only MCP tool with bounded requests, responses, filters, and pagination. Collection separates pure path registries and projections from immutable object acquisition and one-ref orchestration. Persistence separates limits and recursive normalization from in-memory admission, owner-only atomic replacement, and hostile readback; the decoder re-enters the same admission and snapshot-policy binding controls rather than constructing trusted state directly. Snapshot indexes are checked against admitted records before serialization, and the atomic replacement synchronizes its parent directory where supported. Deltas are recursively re-redacted and re-bounded before list/get projection; their metadata and stable IDs are derived only after that normalization. Colliding semantic facts retain source paths instead of overwriting one another. Recursive redaction normalizes mapping keys before sensitive-name classification and rejects key collisions rather than losing a value.
Repository-owned OCR rules, accepted decisions, and root or nested AGENTS.md/CLAUDE.md guidance come only from immutable target blobs at the captured current protected-target SHA; the forge diff base remains unchanged for code deltas. Exact policy rules are materialized into an owner-only artifact without modifying the checkout; explicit external operator-owned rules are preserved. Guidance touched on either side of a change or rename is excluded, source/head content never becomes policy evidence, and unrelated nested guidance is filtered before blob reads and store admission. Structured policy values are bounded as complete canonical UTF-8 records, not only by repository-text code points, before storage, after recursive redaction, and again on hostile load. A redaction expansion omits only that record during ordinary collection, while hostile readback rejects the incomplete atomic envelope. Schema-v4 policy provenance binds to the distinct policy snapshot while applicability is rebound to the atomic base/head changed paths on every load. Schema-v3 remains explicitly base-bound, and compatible historical text records keep their original legacy provenance. The compact bootstrap carries only refs, coverage, counts, delta kinds, applicable decision summaries, normalized guidance paths/scopes, toolkit-generated applicability hints, diagnostics, and MCP usage instructions. Every repository-derived inline value uses delimiter-aware Markdown rendering and truncation stops only between complete lines. Full redacted rationale and guidance text remain in the evidence store and are untrusted context that cannot override policy, permissions, findings, posting, or authorize actions.
Ansible Galaxy requirement includes use the same immutable-object boundary. Relative includes may only resolve to YAML blobs inside the authenticated tree; absolute, home-relative, root-escaping, symlink, and submodule targets are rejected. Include depth, file count, graph edges, parser items, and emitted diagnostics have independent limits so adversarial manifests degrade visibly without expanding unbounded work.
Use a dedicated bot identity and least-privilege GITLAB_API_TOKEN. Protect and mask credentials. Do not expose secrets to pipelines for untrusted forks. Begin with manual execution for trusted contributors, review generated notes, and enable automatic posting only after the repository's threat model is accepted.
Formal GitLab approval is a default-on write. Set
OCR_AUTO_APPROVE=false before upgrading if the bot must remain comment-only or
is not an eligible project approver. GitLab approval rules, Code Owners,
protected branches, and reauthentication remain server-side controls; the
toolkit does not bypass them.
Finding badges are a presentation-only opt-in. Keep the default text mode when private review viewers or a GitLab image proxy must not contact a third-party image service. Enabling Shields.io does not send finding prose, repository paths, project identifiers, or arbitrary OCR metadata in the image URL, but the render request can still expose ordinary viewer, proxy, and network metadata to that service.
Pin the exact recommended Open Code Review release from the compatibility manifest and verify its listed checksum. Pin Python dependencies through uv.lock and GitHub Actions by immutable commit SHA. MCP stdio commands and remote endpoints are privileged configuration; allow only reviewed servers and tools.
The OCR compatibility policy requires double-source asset digest verification, bounded downloads, an executed Linux contract probe, and protected PR/release gates; qualification automation never writes directly to main or promotes an ambiguous release.
Stable-release authorization executes from the protected base SHA that predates the release candidate. Candidate and merge commits are bounded data rather than the source of their own authorizer. GitHub API reads use a closed endpoint allowlist, HTTPS-only redirect policy, redirect-safe bearer authentication, and atomic replacement only after transfer and status validation. Persisted release receipts accept only their exact versioned top-level and nested schemas.
Remote MCP is HTTPS-only, forbids URL userinfo and fragments, and never logs endpoint URLs or header values. Put credentials in protected/masked CI variables and reference them through headers_from; literal credential-like headers fail closed. OCR expands the resulting $VARIABLE at connection time. Full browser OAuth, PKCE, refresh-token persistence, tenant binding, and revocation remain conditional on a named supported-provider requirement; use a reviewed stdio OAuth proxy when those flows are required today.
All toolkit-owned Git plumbing ignores process-level repository/object-store overrides, global and system Git configuration, and replacement refs before it derives evidence or remaps an inline finding. Existing OCR configuration is treated as hostile persisted input: reads are descriptor-based, single-link, and byte-bounded before JSON parsing.
The repository runs Bandit as a bounded SAST gate over src/ocr_toolkit at medium-or-higher severity and confidence. Narrow # nosec B108 annotations are permitted only beside fixed CI temporary paths whose isolation or containment is explained in the adjacent source comment; tests, examples, and broad plugin suppressions are not part of that exception policy.
Protected main requires pull requests, signed commits, a current branch, resolved review threads, and the complete CI, package-build, dependency, secret, and CodeQL check set. The project currently has one maintainer, so it cannot truthfully require an independent human approval for maintainer-authored changes. This is an explicit residual risk: automated review does not replace a second human. External contributions still receive maintainer review, and independent approval will become mandatory when a second active maintainer can provide it without blocking security fixes.
OpenSSF Scorecard findings are interpreted as supply-chain posture signals rather than vulnerability reports. Repository-age and historical-coverage checks improve only with time and repeated runs; the owner-attested OpenSSF Best Practices record is public and passing; a useful fuzzing integration requires native fuzz targets and infrastructure rather than a workflow added only to satisfy a scanner. Actionable repository-owned findings are fixed through normal signed pull requests.
The detailed environment contract is in configuration.md. Vulnerability reporting is in SECURITY.md.