Skip to content

feat: add standalone mint with custom role support#2537

Merged
ggallen merged 1 commit into
fullsend-ai:mainfrom
ggallen:worktree-standalone-mint
Jun 29, 2026
Merged

feat: add standalone mint with custom role support#2537
ggallen merged 1 commit into
fullsend-ai:mainfrom
ggallen:worktree-standalone-mint

Conversation

@ggallen

@ggallen ggallen commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds a standalone HTTP token mint (cmd/mint/) that runs without GCP infrastructure, using direct JWKS verification and filesystem PEM storage
  • Fallback proxy routes unknown roles to an upstream (hosted) mint, enabling gradual adoption
  • Custom role permissions via CUSTOM_ROLE_PERMISSIONS env var, with built-in role collision guard
  • How-to guide at docs/guides/infrastructure/standalone-mint.md

Test plan

  • cmd/mint tests pass with -race and 90%+ coverage
  • internal/mintcore tests pass with -race
  • Pre-commit hooks pass (gofmt, go vet, gitleaks, embed sync)
  • Manual: run standalone mint with custom role, request token from GitHub Actions workflow

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

Site preview

Preview: https://373bbbc8-site.fullsend-ai.workers.dev

Commit: b520dcbf7abcaa3f8e9f62a2371e9bd681893070

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add standalone fullsend-mint server with fallback proxy and custom roles
✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

Description

• Add a self-hosted HTTP token mint binary using GitHub JWKS and local PEM keys.
• Support custom role permissions via env var with built-in role collision protection.
• Proxy unknown roles to an upstream hosted mint to enable gradual migration.
Diagram

graph TD
  A["GitHub Actions"] --> B["Standalone mint"] --> C{Role handled locally?}
  C -->|"yes"| D["mintcore handler"] --> E[("PEM dir")]
  D --> F[["GitHub JWKS"]]
  D --> G[["GitHub API"]]
  C -->|"no"| H["Upstream mint"]

  subgraph Legend
    direction LR
    _svc(["Service"]) ~~~ _dec{"Decision"} ~~~ _store[("Storage")] ~~~ _ext[["External"]]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep cmd/mint inside the root Go module
  • ➕ Avoids nested module maintenance (separate go.mod/go.sum, replace directives).
  • ➕ Simplifies tooling (one module for lint/test/release).
  • ➖ Harder to treat the mint as an independently vendorable/distributable artifact.
  • ➖ May pull extra dependencies into the main module build graph over time.
2. Use a config file (YAML/JSON) instead of env-only configuration
  • ➕ More ergonomic for large permission maps and multi-org deployments.
  • ➕ Easier to validate and version configuration outside process environment.
  • ➖ Adds file distribution and secret-handling complexity.
  • ➖ Slightly increases operational surface area vs simple env vars.
3. Move fallback routing to an external reverse proxy layer
  • ➕ Keeps the mint server focused on minting; routing becomes infra concern.
  • ➕ Can leverage mature proxy features (retries, observability, TLS policies).
  • ➖ Harder to route by request JSON role without custom proxy logic.
  • ➖ Adds another component to deploy/manage for self-hosting users.

Recommendation: The PR’s approach is strong for the stated goal: a standalone, self-hostable mint with minimal infrastructure dependencies and a role-aware fallback proxy for gradual adoption. If the project expects users to build and run this as a separate artifact, the nested module is defensible; otherwise, consider folding cmd/mint into the root module to reduce long-term maintenance overhead.

Files changed (15) +1679 / -4

Enhancement (6) +417 / -4
main.goImplement standalone mint server wiring and env configuration +176/-0

Implement standalone mint server wiring and env configuration

• Adds the standalone HTTP server entrypoint: env validation, JWKS verifier setup, filesystem PEM accessor, and mintcore handler initialization. Supports optional fallback proxying and runtime registration of custom role permissions from CUSTOM_ROLE_PERMISSIONS.

cmd/mint/main.go

proxy.goAdd role-based fallback proxy for /v1/token +93/-0

Add role-based fallback proxy for /v1/token

• Implements an HTTP wrapper that routes token requests locally when the role is configured, otherwise proxies the request (including Authorization header) to an upstream mint with bounded body sizes and timeouts.

cmd/mint/proxy.go

file_pem.go.embedAdd filesystem PEM accessor to embedded mintcore source +40/-0

Add filesystem PEM accessor to embedded mintcore source

• Introduces FilesystemPEMAccessor in the embedded mintcore copy, enabling local directory-based private key loading with role name validation and aliasing via PemSecretRole.

internal/dispatch/gcf/mintsrc/mintcore/file_pem.go.embed

github.go.embedAdd custom role permissions support to embedded mintcore source +34/-2

Add custom role permissions support to embedded mintcore source

• Adds registration and lookup precedence for custom role permission maps, including startup-time collision checks against built-in roles and copy-on-read behavior.

internal/dispatch/gcf/mintsrc/mintcore/github.go.embed

file_pem.goImplement filesystem-based PEM accessor in mintcore +40/-0

Implement filesystem-based PEM accessor in mintcore

• Adds FilesystemPEMAccessor for reading {role}.pem keys from a local directory, validating the directory at construction and validating/normalizing role names before reads.

internal/mintcore/file_pem.go

github.goSupport custom role permissions with built-in collision guard +34/-2

Support custom role permissions with built-in collision guard

• Adds RegisterCustomRolePermissions and updates permission lookup to prioritize custom role definitions while keeping built-in roles intact. Includes copy-on-read to prevent external mutation and expands HasRole to recognize custom roles.

internal/mintcore/github.go

Tests (4) +891 / -0
main_test.goAdd comprehensive tests for standalone mint wiring and env parsing +451/-0

Add comprehensive tests for standalone mint wiring and env parsing

• Covers required-env enforcement, CSV parsing helpers, local role parsing, handler construction paths (fallback, per-repo WIF repos, custom permissions), and basic handler behavior for key endpoints.

cmd/mint/main_test.go

proxy_test.goTest fallback proxy routing, forwarding, and error cases +255/-0

Test fallback proxy routing, forwarding, and error cases

• Validates local vs proxied routing by role, header/body forwarding, upstream error propagation, and failure handling (invalid JSON, unreachable upstream, invalid fallback URL).

cmd/mint/proxy_test.go

file_pem_test.goTest filesystem PEM accessor behavior and role aliasing +103/-0

Test filesystem PEM accessor behavior and role aliasing

• Covers constructor validation and AccessPEM behavior for valid roles, fix→coder aliasing, missing files, and invalid/empty role names.

internal/mintcore/file_pem_test.go

github_test.goAdd tests for custom role permission registration and token minting +82/-0

Add tests for custom role permission registration and token minting

• Verifies custom role enablement/clearing, collision rejection with built-in roles, copy semantics, and that CreateInstallationToken uses custom permissions for a custom role.

internal/mintcore/github_test.go

Documentation (2) +349 / -0
README.mdLink standalone mint guide from guides index +1/-0

Link standalone mint guide from guides index

• Adds a new guide entry pointing operators to the standalone mint documentation.

docs/guides/README.md

standalone-mint.mdDocument standalone mint setup, custom roles, and fallback behavior +348/-0

Document standalone mint setup, custom roles, and fallback behavior

• Provides a full how-to for building and running the standalone mint, managing PEMs, configuring env vars, defining custom role permissions, and integrating with GitHub Actions (including fallback proxy semantics).

docs/guides/infrastructure/standalone-mint.md

Other (3) +22 / -0
MakefileAdd build target for standalone mint binary +3/-0

Add build target for standalone mint binary

• Introduces a dedicated make target to build cmd/mint into bin/fullsend-mint, aligning local builds with the new standalone entrypoint.

Makefile

go.modCreate nested Go module for cmd/mint +9/-0

Create nested Go module for cmd/mint

• Adds a standalone go.mod for the mint binary that depends on internal/mintcore via a local replace directive, isolating dependencies for building/distribution.

cmd/mint/go.mod

go.sumAdd dependency checksums for standalone mint module +10/-0

Add dependency checksums for standalone mint module

• Pins module sums required by cmd/mint, including test dependencies and x/sync as an indirect requirement.

cmd/mint/go.sum

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 22, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:21 PM UTC · Completed 10:36 PM UTC
Commit: 85a1792 · View workflow run →

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (1) 📜 Skill insights (2)

Context used
✅ Compliance rules (platform): 51 rules

Grey Divider


Action required

1. Fallback misroutes legacy roles 🐞 Bug ≡ Correctness
Description
The fallback proxy treats legacy ROLE_APP_IDS keys like org/role as “local roles”, but
mintcore’s handler ignores those keys via RoleOnlyAppIDs, so requests can be routed to the local
handler and rejected instead of being proxied upstream.
Code

cmd/mint/proxy.go[R49-60]

+	if err := json.Unmarshal(body, &req); err != nil || req.Role == "" {
+		r.Body = io.NopCloser(bytes.NewReader(body))
+		f.local.ServeHTTP(w, r)
+		return
+	}
+
+	if f.localRoles[req.Role] || f.fallbackURL == "" {
+		log.Printf("routing role %q locally", req.Role)
+		r.Body = io.NopCloser(bytes.NewReader(body))
+		f.local.ServeHTTP(w, r)
+		return
+	}
Relevance

⭐⭐⭐ High

Team previously addressed legacy org/role ROLE_APP_IDS pitfalls; likely accept fixing proxy/handler
mismatch to avoid regressions.

PR-#2331
PR-#2370

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
parseLocalRoles adds a role entry even when the ROLE_APP_IDS key is org/role, while the handler
drops any key containing / via RoleOnlyAppIDs; therefore the proxy can decide “local” for a role
that the handler will never allow/configure.

cmd/mint/main.go[131-145]
cmd/mint/proxy.go[46-60]
internal/mintcore/handler.go[69-76]
internal/mintcore/handler.go[347-360]
PR-#2331

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The fallback proxy’s local-role decision is derived from raw `ROLE_APP_IDS` keys, but the mintcore handler filters `ROLE_APP_IDS` via `RoleOnlyAppIDs` (dropping legacy `org/role` keys). This mismatch can cause roles to be routed locally but then rejected by the local handler, defeating the intended fallback behavior.

## Issue Context
- `parseLocalRoles()` currently includes roles from `org/role` keys.
- `mintcore.NewHandler()` ignores those keys by calling `RoleOnlyAppIDs(ids)`.

## Fix Focus Areas
- In `parseLocalRoles`, after parsing JSON, call `mintcore.RoleOnlyAppIDs(ids)` and only consider those keys as local roles.
- In the proxy, compare using a normalized role (e.g. `strings.ToLower(req.Role)`) to match how you populate `localRoles`.
- Add/adjust tests to cover the legacy `org/role` case when `FALLBACK_MINT_URL` is set (expect proxying, not local rejection).

### Fix Focus Areas (code pointers)
- cmd/mint/main.go[131-145]
- cmd/mint/proxy.go[46-64]
- internal/mintcore/handler.go[69-76]
- internal/mintcore/handler.go[347-360]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. JWKS/OIDC jargon not defined 📜 Skill insight ✧ Quality
Description
The new guide introduces terms like OIDC and JWKS without an inline definition or a link to
docs/glossary.md on first use. This reduces clarity for readers and violates the documentation
requirement to define jargon on first use.
Code

docs/guides/infrastructure/standalone-mint.md[R3-16]

+This guide covers setting up and running the standalone token mint — a lightweight, self-hosted alternative to the GCP-hosted mint Cloud Function. The standalone mint lets you run your own OIDC token exchange service without any GCP infrastructure, with the option to proxy unhandled roles to the hosted mint.
+
+> **This guide is for users who want to run agents with their own GitHub App identity.** If you are using the hosted mint with the shared fullsend apps, see [Mint service administration](mint-administration.md) instead — you do not need to run your own mint.
+
+## Why use the standalone mint?
+
+The hosted fullsend mint uses shared GitHub Apps owned by the fullsend team. Every organization enrolled in the hosted mint shares the same apps — your agents authenticate as `fullsend-ai-triage`, `fullsend-ai-coder`, etc. This works for most use cases, but there are reasons to run your own mint:
+
+- **Own your agent identity.** Your agents authenticate as GitHub Apps you created and control. Commits, PR reviews, and issue comments come from your app, not a shared one. This matters for compliance, auditing, and brand identity.
+
+- **Custom agent roles.** The hosted mint supports a fixed set of roles (triage, coder, review, fix, retro, prioritize, fullsend). The standalone mint lets you define custom roles with permissions tailored to your workflows — a `scanner` role with `security_events:write`, a `deployer` role with `deployments:write`, or anything else the GitHub API supports.
+
+- **No GCP dependency.** The standalone mint is a single binary that reads PEM keys from a local directory and validates OIDC tokens directly against GitHub's JWKS endpoint. No GCP project, Secret Manager, Cloud Functions, or WIF configuration required.
+
Relevance

⭐⭐ Medium

Docs review history shows mixed strictness on wording/clarity; no specific precedent requiring
JWKS/OIDC definitions on first use.

PR-#665

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062083 requires jargon to be defined on first use via glossary link or inline
definition. The guide uses OIDC and JWKS immediately in the opening sections without defining
them or linking to docs/glossary.md.

docs/guides/infrastructure/standalone-mint.md[1-16]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The guide uses domain jargon (e.g., OIDC, JWKS) without defining it on first use via a glossary link or inline definition.

## Issue Context
Readers new to the system may not know these terms; the compliance rule requires definition/link on first occurrence.

## Fix Focus Areas
- docs/guides/infrastructure/standalone-mint.md[1-16]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Mint tests skipped in CI 🐞 Bug ☼ Reliability
Description
Because cmd/mint is a nested Go module, the repo’s current CI/Makefile go test ./... runs won’t
execute cmd/mint tests, allowing standalone-mint regressions to merge undetected.
Code

cmd/mint/go.mod[R1-9]

+module github.com/fullsend-ai/fullsend/cmd/mint
+
+go 1.26
+
+require github.com/fullsend-ai/fullsend/internal/mintcore v0.0.0
+
+require golang.org/x/sync v0.20.0 // indirect
+
+replace github.com/fullsend-ai/fullsend/internal/mintcore => ../../internal/mintcore
Relevance

⭐⭐ Medium

No clear historical evidence team enforces CI running tests inside newly added nested Go modules.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR adds a nested module at cmd/mint/go.mod, but CI and Makefile only run root-module `go test
./...`, which does not execute tests under a nested module boundary.

cmd/mint/go.mod[1-9]
.github/workflows/lint.yml[41-46]
Makefile[96-98]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`cmd/mint` is introduced as a nested Go module (has its own `go.mod`). The current CI and `make go-test` run `go test ./...` from the repo root, which does not traverse into nested modules, so `cmd/mint` tests won’t execute.

## Issue Context
CI job `lint.yml` runs `go test -race -coverprofile=coverage.out ./...` from the root module.

## Fix Focus Areas
- Update CI to also test the nested module (e.g. add a step `cd cmd/mint && go test -race ./...`).
- Update Makefile `go-test` similarly (or add a `go-test-mint` target and invoke it).

### Fix Focus Areas (code pointers)
- cmd/mint/go.mod[1-9]
- .github/workflows/lint.yml[41-46]
- Makefile[96-98]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Custom perms map mutable 🐞 Bug ☼ Reliability
Description
RegisterCustomRolePermissions stores the caller-provided map in a global variable without copying or
validating role names, so later mutation of that map can change effective permissions at runtime and
can race with concurrent reads.
Code

internal/mintcore/github.go[R75-87]

+var customRolePermissions map[string]map[string]string
+
+// RegisterCustomRolePermissions adds user-defined role permissions that are
+// checked before the canonical built-in permissions. Pass nil to clear.
+// Returns an error if any custom role name collides with a built-in role.
+func RegisterCustomRolePermissions(perms map[string]map[string]string) error {
+	for role := range perms {
+		if _, ok := canonicalRolePermissions[role]; ok {
+			return fmt.Errorf("custom role %q collides with built-in role", role)
+		}
+	}
+	customRolePermissions = perms
+	return nil
Relevance

⭐⭐ Medium

Mixed precedent on data-race/defensive-copy concerns; similar race concerns were rejected elsewhere,
unclear enforcement here.

PR-#1682
PR-#279

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The function assigns the caller’s map directly to a global, while other code reads from that global
on the request path; there is no defensive copy or role-name validation despite a dedicated
validator existing in mintcore.

internal/mintcore/github.go[75-117]
internal/mintcore/patterns.go[34-41]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`RegisterCustomRolePermissions` assigns the provided `perms` map directly to a package-global variable. If the caller later mutates the same map, RolePermissionsFor/HasRole will observe changed data; concurrent mutation can also race with reads.

## Issue Context
This is a library-level API that is easy to use unsafely (even if the current standalone mint registers once at startup).

## Fix Focus Areas
- Validate each custom role name with `ValidateRoleName(role)` (and keep the built-in collision guard).
- Deep-copy the incoming `map[string]map[string]string` into a new map before storing it globally.
- Apply the same fix to the embedded mintcore copy used for GCF (`internal/dispatch/gcf/mintsrc/mintcore/github.go.embed`) to keep embed sync.

### Fix Focus Areas (code pointers)
- internal/mintcore/github.go[75-87]
- internal/mintcore/patterns.go[34-41]
- internal/dispatch/gcf/mintsrc/mintcore/github.go.embed[75-88]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

5. Proxy errors not JSON 🐞 Bug ≡ Correctness
Description
fallbackHandler uses http.Error with JSON-looking strings, which produces text/plain responses
(and a trailing newline), diverging from mintcore’s JSON error contract and potentially breaking
strict clients.
Code

cmd/mint/proxy.go[R39-44]

+	body, err := io.ReadAll(io.LimitReader(r.Body, 64<<10))
+	r.Body.Close()
+	if err != nil {
+		http.Error(w, `{"error":"failed to read request body"}`, http.StatusBadRequest)
+		return
+	}
Relevance

⭐⭐ Medium

No prior review evidence found about banning http.Error JSON strings vs enforcing application/json
error contract.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The proxy uses http.Error for errors, while the main mint handler uses a dedicated JSON writer
that sets JSON headers; the two behaviors diverge despite the endpoints being part of the same API
surface.

cmd/mint/proxy.go[39-86]
internal/mintcore/handler.go[396-401]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`http.Error` sets `Content-Type: text/plain; charset=utf-8`, but the proxy writes bodies that look like JSON. This is inconsistent with the core handler which always returns `application/json` objects.

## Issue Context
This affects proxy error paths (read-body errors, invalid fallback URL, unreachable upstream).

## Fix Focus Areas
- Replace `http.Error` calls with a small helper that sets `Content-Type: application/json`, `Cache-Control: no-store`, and `json.NewEncoder(w).Encode(map[string]string{"error": ...})`.

### Fix Focus Areas (code pointers)
- cmd/mint/proxy.go[39-86]
- internal/mintcore/handler.go[396-401]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Guide in infrastructure/ directory 📜 Skill insight ⌂ Architecture
Description
The new guide is placed under docs/guides/infrastructure/, but guides must live under either
docs/guides/admin/ or docs/guides/user/, and target a single audience. This breaks the required
guide organization and makes it unclear whether the intended reader is an administrator or a
developer.
Code

docs/guides/infrastructure/standalone-mint.md[R1-6]

+# Standalone mint
+
+This guide covers setting up and running the standalone token mint — a lightweight, self-hosted alternative to the GCP-hosted mint Cloud Function. The standalone mint lets you run your own OIDC token exchange service without any GCP infrastructure, with the option to proxy unhandled roles to the hosted mint.
+
+> **This guide is for users who want to run agents with their own GitHub App identity.** If you are using the hosted mint with the shared fullsend apps, see [Mint service administration](mint-administration.md) instead — you do not need to run your own mint.
+
Relevance

⭐ Low

Repo already uses docs/guides/infrastructure/ extensively; prior PRs added/edited infra guides
without relocation requirements.

PR-#2072
PR-#1190

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062077 requires each guide under docs/guides/ to be placed in admin/ or
user/ and address a single audience. This PR adds a new guide at
docs/guides/infrastructure/standalone-mint.md and indexes it from docs/guides/README.md.

docs/guides/infrastructure/standalone-mint.md[1-6]
docs/guides/README.md[15-20]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new guide was added under `docs/guides/infrastructure/`, but guides must be placed under either `docs/guides/admin/` or `docs/guides/user/` and written for a single audience.

## Issue Context
The index entry also points to `infrastructure/standalone-mint.md`, reinforcing the incorrect placement.

## Fix Focus Areas
- docs/guides/README.md[15-20]
- docs/guides/infrastructure/standalone-mint.md[1-6]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Hardcoded hosted mint URL 📘 Rule violation ⛨ Security
Description
The guide hardcodes a specific hosted mint URL (https://fullsend-mint-gljhbkcloq-uc.a.run.app),
which is an environment-specific identifier embedded in repo docs. This can leak or ossify
production-like endpoints and violates the requirement to avoid hardcoded
sensitive/environment-specific identifiers in source files.
Code

docs/guides/infrastructure/standalone-mint.md[R32-36]

+- **Go 1.22+** to build the binary (or use a pre-built release)
+- **A GitHub organization** where you will install your custom GitHub Apps
+- **The hosted mint URL** (optional, for fallback proxy): `https://fullsend-mint-gljhbkcloq-uc.a.run.app`
+- **Your organization enrolled in the hosted mint** (optional, for fallback proxy) — see [Mint service administration](mint-administration.md)
+
Relevance

⭐ Low

Hardcoded hosted mint URL was previously added to docs and merged, indicating it’s acceptable in
documentation here.

PR-#2072

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062040 forbids hardcoded sensitive or environment-specific identifiers in changed
files. The new guide includes a concrete Cloud Run *.a.run.app service URL as the hosted mint
endpoint, which is environment-specific rather than a placeholder.

Rule 1062040: Disallow hardcoded secrets and sensitive environment-specific identifiers in source code
docs/guides/infrastructure/standalone-mint.md[32-36]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The documentation hardcodes a specific hosted mint endpoint URL, which is an environment-specific identifier.

## Issue Context
Compliance requires avoiding hardcoded sensitive/environment-specific identifiers in repository files; docs should use clearly fake placeholders or instruct readers how to obtain the correct value.

## Fix Focus Areas
- docs/guides/infrastructure/standalone-mint.md[32-36]
- docs/guides/infrastructure/standalone-mint.md[113-118]
- docs/guides/infrastructure/standalone-mint.md[125-135]
- docs/guides/infrastructure/standalone-mint.md[323-332]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread cmd/mint/proxy.go
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explicitly justify the AGENTS.md modification. The change adds a 2-line "Standalone mint" documentation paragraph to the Go code section. Protected-path changes always require human review regardless of change size or nature.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

  • [missing-authorization] N/A — This PR adds a non-trivial feature (standalone mint with custom role support, ~2000 lines across 22 files including new Go binary, shared library changes, and documentation) with no linked issue. Non-trivial changes require explicit authorization.
    Remediation: File an issue documenting the decision to add standalone mint support and its use cases (self-hosted deployments, custom agent roles, non-GCP environments). Link the issue to this PR.


Labels: PR adds Go code for standalone mint binary and shared library changes.

Previous run

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explicitly justify the AGENTS.md modification. The change adds a 2-line "Standalone mint" documentation paragraph to the Go code section. Protected-path changes always require human review regardless of change size or nature.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

  • [missing-authorization] N/A — This PR adds a non-trivial feature (standalone mint with custom role support, ~2300 lines across 23 files including new Go binary, shared library changes, and documentation) with no linked issue. Non-trivial changes require explicit authorization.
    Remediation: File an issue documenting the decision to add standalone mint support and its use cases (self-hosted deployments, custom agent roles, non-GCP environments). Link the issue to this PR.

Medium

  • [architecture-coherence] internal/mintcore/github.go:76 — Custom role registry (RegisterCustomRolePermissions, customRoles atomic.Value) is added to the shared mintcore library despite being used only by cmd/mint/ (standalone mint). The GCF mint's .embed files now include this unused custom role logic. The code comment justifies the placement ("Lives in mintcore so that RolePermissionsFor, HasRole, and RolePermissions return a unified view"), which is a legitimate trade-off, but the finding stands as an architectural concern.
    Remediation: Either document in architecture.md why shared placement is intentional, or consider moving custom role logic to cmd/mint/ with a variant lookup that checks custom roles.

  • [dead-code-in-deployment-artifact] internal/dispatch/gcf/provisioner.go:45file_pem.go.embed is added to the GCF Cloud Function bundle (go:embed directive and embeddedMintFiles map), but the GCF mint never uses FilesystemPEMAccessor — it uses GCPSecretPEMAccessor exclusively. The author followed the AGENTS.md sync instructions ("sync to corresponding .embed file"), but the file should not be included in the embed directive since it is only needed by the standalone mint.
    Remediation: Remove mintsrc/mintcore/file_pem.go.embed from the go:embed directive and embeddedMintFiles map in provisioner.go. The .embed file can remain on disk for documentation purposes.

Low

  • [code-duplication] cmd/mint/proxy.go:24writeJSONError duplicates the writeError function from internal/mintcore/handler.go. The duplication is documented ("duplicated here because cmd/mint is a separate Go module") and the function is 5 lines. Conscious decision given module boundaries.

  • [operational-correctness] cmd/mint/main.go:80ALLOWED_WORKFLOW_FILES is not in checkRequired(). Behavior is fail-closed (all token requests rejected when unset). A warning is now logged at startup, addressing the prior review's "operational trap" concern. Prior severity (medium) downgraded to low.

  • [data-exposure] cmd/mint/proxy.go:67 — Fallback proxy forwards OIDC Authorization header verbatim to upstream mint. Mitigated by: HTTPS-only validation at startup, short-lived OIDC tokens (~5 min), and upstream re-validation.

  • [authorization] internal/mintcore/github.go:82RegisterCustomRolePermissions does not validate permission key names against known GitHub API permissions. Typos would be silently accepted but fail at GitHub API call time (fail-closed).

  • [documentation-accuracy] docs/guides/infrastructure/standalone-mint.md:197 — Fallback proxy behavior table states "role in ROLE_APP_IDS" is handled locally, but actual routing uses localRoles (built via parseLocalRoles), which only includes roles with permission entries. Roles in ROLE_APP_IDS without matching permissions get proxied. A startup warning is logged for such roles.

  • [edge-case] cmd/mint/proxy.go:49 — 64KB body limit on request read via io.LimitReader. Truncation would produce invalid JSON falling through to local handler. Unlikely in practice (token requests are typically < 1KB).

Previous run (2)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md needs to be changed beyond documenting cmd/mint/. The change adds a "Standalone mint" section which is part of the feature, but protected-path changes always require human review and explicit justification regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

Medium

  • [fail-open] cmd/mint/main.go:74ALLOWED_WORKFLOW_FILES is not in the checkRequired() list. When unset, splitCSV returns nil, and ValidateWorkflowRef's loop never matches any workflow, so requests are rejected (fail-closed). However, the mint starts successfully without any warning, creating an operational trap where all token requests fail silently. The docs list it as "Optional" which is misleading since the mint is non-functional without it.
    Remediation: Log a warning at startup when AllowedWorkflowFiles is empty, and update standalone-mint.md to add a prominent note that the mint rejects all tokens without it.

  • [architecture-coherence] internal/mintcore/github.go:76 — Custom role registry (RegisterCustomRolePermissions, customRoles atomic.Value) is added to the shared mintcore library despite being used only by the standalone mint (cmd/mint/). The GCF mint's .embed files include this unused custom role logic. This couples a standalone-mint-specific feature into the shared library.
    Remediation: Consider refactoring the custom role registry out of mintcore and into cmd/mint/ as a standalone-mint-specific feature, or document why shared placement is intentional.

  • [code-organization] cmd/mint/proxy.gowriteJSONError duplicates the writeError function from internal/mintcore/handler.go. Both implement identical JSON error response logic (set Content-Type: application/json, Cache-Control: no-store, encode error map). The mintcore writeError is unexported, preventing reuse.
    Remediation: Either export mintcore.writeError (rename to WriteError) or document why duplication is intentional for cmd/mint's independence.

  • [stale-deployment-model] docs/guides/user/customizing-agents.md:286 — The Agent Role Architecture diagram states "PEM storage: GCP Secret Manager" as the only option. With the introduction of the standalone mint, PEM storage can also be filesystem-based.
    Remediation: Update line 286 to acknowledge both PEM storage options (e.g., "PEM storage: GCP Secret Manager or filesystem").

Previous run (3)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md needs to be changed beyond documenting cmd/mint/. The change adds a "Standalone mint" section which is part of the feature, but protected-path changes always require human review and explicit justification regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.
Previous run (4)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md needs to be changed beyond documenting cmd/mint/. The change adds a "Standalone mint" section which is part of the feature, but protected-path changes always require human review and explicit justification regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

Low

  • [missing-authorization] PR metadata — This PR introduces a standalone mint deployment model (cmd/mint/) and custom role permissions (~1970 additions, 23 files) without a linked issue. The code is well-tested and documented, but non-trivial features should trace to authorized work.

  • [data-exposure] cmd/mint/proxy.go:57 — Fallback proxy forwards the OIDC Authorization header verbatim to FALLBACK_MINT_URL. Mitigated by HTTPS-only validation at startup (main.go line 106-108), short-lived OIDC tokens (~5 min), and the upstream mint re-validating the token independently.

  • [authorization] internal/mintcore/github.go:82RegisterCustomRolePermissions does not validate that permission keys are valid GitHub App permission names or that values are limited to read/write. GitHub is the enforcement layer (fail-closed): invalid permissions cause a 422 error at token mint time, and no privilege escalation is possible beyond the App's installed permissions.

  • [authorization] cmd/mint/main.go:73ALLOWED_WORKFLOW_FILES is not in the checkRequired list, so omitting it results in an empty allowlist. This is fail-closed: the JWKS verifier rejects all workflows when the list is empty. However, the startup succeeds silently, which could cause confusing "token rejected" errors at request time.

  • [edge-case] cmd/mint/proxy.go:57 — Fallback handler combines invalid JSON and empty-role conditions with ||, delegating both to the local handler. Both conditions follow the same code path, so server-side logs cannot distinguish "invalid JSON body" from "missing role field" for POST /v1/token requests.

  • [error-handling] cmd/mint/main.go:90registerCustomPermissions() mutates process-global state (customRoles atomic.Value) before NewHandler is called at line 99. If NewHandler subsequently fails, custom roles remain registered. Harmless because the process exits on buildHandler error.

  • [race-condition] internal/mintcore/github.go:78customRoles package-level atomic.Value is process-global state. Written once at startup, read concurrently by request handlers. Tests are serialized within packages and cleaned up via t.Cleanup. atomic.Value is the correct Go idiom for this pattern.

  • [behavior-change-enhanced-lookup] internal/mintcore/github.go:77RolePermissions(), RolePermissionsFor(), and HasRole() now check an atomic.Value-backed custom roles registry alongside canonical roles. Backward compatible: existing callers see identical behavior unless RegisterCustomRolePermissions is explicitly called (only done by cmd/mint).

  • [incomplete-mint-url-guidance] docs/reference/github-setup.md:15 — Prerequisites section describes the mint URL as "the deployed mint Cloud Function", implying mint is always a Cloud Function. Post-PR the mint URL could point to a standalone mint server. See also: same wording at line 111 in the flags table.

Previous run (5)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md needs to be changed beyond documenting cmd/mint/. The change adds a "Standalone mint" section which is part of the feature, but protected-path changes always require human review and explicit justification regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

Medium

  • [error-response-consistency] cmd/mint/proxy.go:42proxy.go uses http.Error() to write JSON error responses (e.g., http.Error(w, '{"error":"failed to read request body"}', http.StatusBadRequest)), while handler.go uses a writeError() helper that sets Content-Type: application/json and Cache-Control: no-store headers. The proxy's approach sets Content-Type to text/plain despite the body being JSON.
    Remediation: Use a writeError-style helper in proxy.go that sets Content-Type: application/json and Cache-Control: no-store for consistency with mintcore error responses.

  • [scope-coherence] internal/mintcore/github.go:79 — Custom role permissions (RegisterCustomRolePermissions, HasRole, RolePermissionsFor) are implemented in shared mintcore but only exposed in cmd/mint/ via CUSTOM_ROLE_PERMISSIONS env var. The GCF mint deploys this code (via .embed sync) but does not use it. A brief comment on RegisterCustomRolePermissions clarifying whether this is cmd/mint-only or future-facing would prevent confusion.
    Remediation: Add a comment on RegisterCustomRolePermissions documenting intended usage scope.

Low

  • [missing-authorization] PR metadata — This PR introduces a standalone mint deployment model (cmd/mint/) and custom role permissions (~1960 additions, 23 files) without a linked issue. The code is well-tested and documented, but non-trivial features should trace to authorized work.

  • [architectural-documentation-gap] docs/architecture.md — The architecture.md update adds a standalone mint bullet point but does not explicitly position cmd/mint/ within the ADR-0029 deployment model hierarchy (central vs self-managed).

  • [race-condition] internal/mintcore/github.go:78 — The customRoles package-level atomic.Value is process-global state mutated by RegisterCustomRolePermissions in tests. Safe today: tests are sequential within packages, cross-package tests run in separate binaries, and atomic.Value is the correct Go idiom for write-once-at-startup, read-concurrently patterns.

  • [data-exposure] cmd/mint/proxy.go:57 — Fallback proxy forwards the OIDC Authorization header verbatim to FALLBACK_MINT_URL. Mitigated by HTTPS-only validation at startup (main.go line 107), short-lived OIDC tokens (~5 min), and the upstream mint re-validating the token independently.

  • [authorization] internal/mintcore/github.go:82RegisterCustomRolePermissions does not validate that permission keys are valid GitHub App permission names or that values are limited to read/write. GitHub is the enforcement layer (fail-closed): invalid permissions cause a 422 error at token mint time.

  • [authorization] cmd/mint/main.go:73ALLOWED_WORKFLOW_FILES is not in the checkRequired list, so omitting it results in an empty allowlist. This is fail-closed: the JWKS verifier rejects all workflows when the list is empty. However, the startup succeeds silently, which could cause confusing "token rejected" errors at request time.

  • [error-handling] cmd/mint/main.go:89registerCustomPermissions() mutates global state (customRoles atomic.Value) before NewHandler is called. If NewHandler subsequently fails, custom roles remain registered. In production the process exits on error; in tests, t.Cleanup handles it.

  • [edge-case] cmd/mint/proxy.go:50 — Fallback handler combines invalid JSON and empty-role conditions with ||, delegating both to the local handler. The local handler's ServeHTTP correctly returns "role is required" for the empty-role case.

  • [error-message-formatting] cmd/mint/main.go:58 — Minor error message format variations compared to handler.go patterns. Not blocking but could be harmonized.

  • [error-wrapping-consistency] cmd/mint/main.go:40 — Error wrapping uses initializing PEM accessor: %w while some codebase patterns use creating X for NewX constructors. Both are valid Go style.

  • [logging-message-format] cmd/mint/proxy.go:34 — Log messages use prose format while some handler.go messages use key=value format. The codebase is not fully consistent on this point.


Labels: PR adds standalone mint binary and custom role permissions with documentation; component/ci does not apply as no CI pipelines are changed.

Previous run (6)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md is being changed. The change adds a "Standalone mint" section documenting cmd/mint/, which is part of the feature, but protected-path changes always require human review regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

Medium

  • [logic-error] cmd/mint/main.go:197parseLocalRoles extracts role names from ROLE_APP_IDS keys using strings.Split(key, "/") and keeps the last segment, then includes it if mintcore.HasRole(role) returns true. However, mintcore.NewHandler (called on the same ROLE_APP_IDS env var) uses RoleOnlyAppIDs which skips keys containing /. If a user sets ROLE_APP_IDS to org-prefixed keys only like {"my-org/scanner":"555"}, parseLocalRoles would include scanner in localRoles, but the handler's lookupRoleAppID would fail to find scanner because RoleOnlyAppIDs dropped the entry. The fallback handler would route the request locally, but the local handler would reject it with "no app ID configured."
    Remediation: Either (a) make parseLocalRoles use the same RoleOnlyAppIDs filtering logic (skip keys containing /), or (b) document in the standalone-mint guide that ROLE_APP_IDS keys must use plain role names (not org-prefixed) for the standalone mint, or (c) have parseLocalRoles warn/error when it encounters org-prefixed keys.

Low

  • [authorization] internal/mintcore/github.go:82RegisterCustomRolePermissions does not validate that permission keys are valid GitHub App permission names or that values are limited to read/write. An operator supplying a typo (e.g., contens instead of contents) will get a runtime error from GitHub at token-request time, not at startup. GitHub is the enforcement layer (fail-closed): invalid permissions cause a 422 error, and no privilege escalation is possible beyond the App's installed permissions.

  • [architectural-drift] cmd/mint/main.go — This PR introduces a standalone mint server (cmd/mint/) and custom role permissions without a dedicated ADR. ADR-0029 establishes the mint as a GCP Cloud Function. The PR adds substantial documentation (architecture.md, standalone mint guide, AGENTS.md), but the formal architectural record gap remains. See also: [scope-extension] finding on custom roles extending the role model beyond ADR-0007.

  • [race-condition] internal/mintcore/github.go:78 — The customRoles package-level atomic.Value is process-global state mutated by RegisterCustomRolePermissions in tests across both internal/mintcore and cmd/mint. Tests within each package are sequential and cross-package tests run in separate binaries, so this is safe today. The atomic.Value usage is the correct Go idiom for write-once-at-startup, read-concurrently patterns.

  • [data-exposure] cmd/mint/proxy.go:57 — Fallback proxy forwards the OIDC Authorization header verbatim to FALLBACK_MINT_URL. Mitigated by HTTPS-only validation at startup (main.go lines 106-109 reject non-https URLs), short-lived OIDC tokens (~5 min), and the upstream mint re-validates the token independently.

  • [authorization] cmd/mint/main.go:73ALLOWED_WORKFLOW_FILES is not in the checkRequired list, so omitting it results in an empty allowlist. This is fail-closed: the JWKS verifier rejects all workflows when the list is empty. However, this creates a confusing failure mode where the mint starts successfully but rejects every token request without a clear error message.


Labels: PR adds Go code for standalone mint binary; component/ci label does not apply as no CI pipelines are changed.

Previous run (7)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md is being changed. The change adds a "Standalone mint" section documenting cmd/mint/, which is part of the feature, but protected-path changes always require human review regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

Medium

  • [logic-error] cmd/mint/main.go:197parseLocalRoles extracts role names from ROLE_APP_IDS keys using strings.Split(key, "/") and keeps only those where mintcore.HasRole(role) returns true. However, mintcore.NewHandler (called on the same ROLE_APP_IDS env var) uses RoleOnlyAppIDs which skips keys containing /. If a user sets ROLE_APP_IDS to {"my-org/scanner":"555"} (org-prefixed keys only), parseLocalRoles would include scanner in localRoles, but NewHandler.lookupRoleAppID would fail to find scanner because RoleOnlyAppIDs dropped the my-org/scanner entry. The fallback handler would route the request locally, but the local handler would reject it with "no app ID configured."
    Remediation: Either (a) document in the standalone-mint guide that ROLE_APP_IDS keys must use plain role names (not org-prefixed) for the standalone mint, or (b) have parseLocalRoles warn/error when it encounters org-prefixed keys.

  • [scope-extension] internal/mintcore/github.go — Custom role permissions extend the mint's role model beyond ADR-0007's canonical roles. RegisterCustomRolePermissions allows runtime registration of arbitrary role names with arbitrary GitHub App permissions. While the collision guard prevents overriding built-in roles and ADR-0029 authorizes self-managed deployment, this extension creates a new dimension of variability not explicitly documented in the mint architecture.
    Remediation: Document the custom role extension in ADR-0029 or create a follow-on ADR that records the decision to support user-defined agent roles in self-managed deployments.

Low

  • [race-condition] internal/mintcore/github.go:78 — The customRoles package-level atomic.Value is process-global state mutated by RegisterCustomRolePermissions in tests across both internal/mintcore and cmd/mint. Tests within each package are sequential and cross-package tests run in separate binaries, so this is safe today. The atomic.Value usage is the correct Go idiom for write-once-at-startup, read-concurrently patterns.

  • [misleading-doc] internal/mintcore/github.go:92RolePermissionsFor doc comment states custom roles are "checked first, then canonical roles," implying a precedence relationship that is structurally unreachable since RegisterCustomRolePermissions rejects collisions with built-in roles.

  • [data-exposure] cmd/mint/proxy.go:89 — Fallback proxy forwards the OIDC Authorization header verbatim to FALLBACK_MINT_URL. Mitigated by HTTPS-only validation at startup and short-lived OIDC tokens (~5 min). The upstream mint re-validates the token independently.

  • [authorization] internal/mintcore/github.go:82RegisterCustomRolePermissions does not validate that permission keys are valid GitHub App permission names or that values are limited to read/write. GitHub is the enforcement layer (fail-closed): invalid permissions cause a 422 error at token mint time.

  • [stale-deployment-architecture] docs/architecture.md:42 — The cross-repo dispatch architecture states "App PEM secrets are stored in Secret Manager" as universal fact. The standalone mint introduced in this PR uses filesystem PEM storage. The PR adds a standalone mint bullet at line 139 which partially addresses this, but earlier references remain GCF-only.

  • [incomplete-deployment-options] docs/reference/installation.md:160 — Installation guide refers to "the deployed mint Cloud Function" without mentioning that users can also run a standalone mint. A note pointing to the standalone mint guide would help discoverability.

Previous run (8)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md is being changed. The change adds a "Standalone mint" section documenting cmd/mint/, which is part of the feature, but protected-path changes always require human review regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

Low

  • [race-condition] internal/mintcore/github.go:78 — The customRoles package-level atomic.Value is process-global state mutated by RegisterCustomRolePermissions in tests across both internal/mintcore and cmd/mint. Tests within each package are sequential (no t.Parallel() on relevant tests) and cross-package tests run in separate binaries, so this is safe today. The atomic.Value usage is the correct Go idiom for write-once-at-startup, read-concurrently patterns.

  • [scope-alignment] internal/mintcore/github.go:90 — Custom role permissions extend the mint's role model beyond ADR-0007's canonical roles. The collision guard prevents overriding built-in roles, and ADR-0029 authorizes self-managed deployment. The extension is additive and non-breaking.

  • [misleading-doc] internal/mintcore/github.go:92RolePermissionsFor doc comment states custom roles "take precedence over canonical definitions," but RegisterCustomRolePermissions rejects collisions with built-in roles. The precedence code path is unreachable for valid inputs since custom and canonical role names are guaranteed disjoint by construction. The comment implies a capability that is structurally prevented.

  • [edge-case] cmd/mint/proxy.go:50 — When the fallback proxy encounters a POST /v1/token with invalid JSON or empty role, it falls through to the local handler. The body is restored via io.NopCloser(bytes.NewReader(body)) after r.Body.Close(). The pattern works correctly in practice since ContentLength remains accurate (same bytes), but is worth a clarifying comment.

  • [data-exposure] cmd/mint/proxy.go:89 — Fallback proxy forwards the OIDC Authorization header verbatim to FALLBACK_MINT_URL. Mitigated by HTTPS-only validation at startup and short-lived OIDC tokens (~5 min). The upstream mint re-validates the token independently.

  • [authorization] internal/mintcore/github.go:82RegisterCustomRolePermissions does not validate that permission keys are valid GitHub App permission names or that values are limited to read/write. GitHub is the enforcement layer (fail-closed): invalid permissions cause a 422 error at token mint time.

  • [architectural-documentation] docs/architecture.md:134 — The "Agent Identity Provider" section documents only the GCF mint deployment. It does not mention the standalone mint variant introduced by this PR. Per the doc's preamble, architecture.md should reflect the current state of architectural decisions.

Previous run (9)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md is being changed. The change adds a "Standalone mint" section documenting cmd/mint/, which appears to be a legitimate part of the feature, but protected-path changes always require human review regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

Low

  • [missing-authorization] cmd/mint/main.go — This is a non-trivial feature PR (19 files changed, 1933 additions) with no linked issue. The PR introduces a new deployment profile (standalone mint) and extends the architecture with custom role support. Non-trivial changes should trace to authorized work.

  • [silent-truncation] cmd/mint/proxy.go:69 — The proxy response body is read with io.LimitReader(resp.Body, 1<<20) (1 MiB). If an upstream response exceeds this, the body is silently truncated producing malformed JSON. In practice, mint token responses are well under 1 KiB.

  • [data-exposure] cmd/mint/proxy.go:89 — Fallback proxy forwards the OIDC Authorization header verbatim to FALLBACK_MINT_URL. Mitigated by HTTPS-only validation at startup. Residual low risk: short-lived OIDC token sent to a third-party URL controlled by whoever sets the env var.

  • [authorization] internal/mintcore/github.go:82RegisterCustomRolePermissions does not validate that permission keys are valid GitHub App permission names or that values are limited to read/write. Arbitrary key-value pairs are passed through to the GitHub installation token API, which will reject invalid ones at request time (fail-closed). No security impact since GitHub is the enforcement layer, but malformed entries produce confusing runtime errors rather than clear startup failures.

  • [api-extension-backward-compatible] internal/mintcore/github.goRegisterCustomRolePermissions() changes the behavior of RolePermissions(), RolePermissionsFor(), and HasRole() to include custom roles when registered. The change is additive; without calling RegisterCustomRolePermissions, behavior is identical to before.

  • [comment-style] cmd/mint/main.go:71buildHandler comment uses "constructs" but the codebase convention (see NewHandler, NewSTSVerifier, NewFilesystemPEMAccessor) is "creates".

  • [incomplete-reference] docs/guides/dev/cli-internals.md:547 — The 'Key Source Files Reference' table lists internal/mint/main.go as the GCF mint entry point but does not mention the new cmd/mint/ standalone variant.

  • [missing-cross-reference] docs/guides/README.md:17 — The Infrastructure guides section does not include standalone-mint.md, making it harder for users to discover the standalone mint option.

  • [incomplete-context] AGENTS.md:8 — The relationship between internal/mint/ (GCF variant) and cmd/mint/ (standalone variant) could be clearer. They are documented separately without explicitly stating they serve the same purpose in different deployment models.

Previous run (10)

Review

Findings

High

  • [protected-path] .github/workflows/, AGENTS.md, skills/cutting-releases/ — This PR modifies 7 protected governance and infrastructure files: .github/workflows/functional-tests.yml, .github/workflows/release.yml, .github/workflows/renovate.yml (deleted), .github/workflows/reusable-dispatch.yml, AGENTS.md, skills/cutting-releases/SKILL.md, and skills/cutting-releases/post-flight.md. The PR has no linked issue and the PR description does not explain why the workflow routing logic, release automation, Renovate workflow, or release skill are being changed. The AGENTS.md change documents cmd/mint/ which is part of the feature, but the .github/ and skills/ changes appear unrelated to the standalone mint feature. Human approval is required for all protected-path modifications.
    Remediation: Link this PR to an issue that authorizes the full scope of changes, including protected-path modifications. Alternatively, split the unrelated changes (workflow routing, release process, Renovate removal) into separate PRs with their own justification.

Medium

  • [logic-error] .goreleaser.yml:3 — Removing the git.ignore_tags section means GoReleaser may use the v0 floating tag as the "previous tag" when generating release changelogs. The v0 tag is still moved during releases (now manually per the updated cutting-releases skill), so it will point at an arbitrary recent release commit. If GoReleaser picks v0 as the previous tag instead of the last semver tag (e.g., v0.45.2), the changelog will either be empty or contain an incorrect set of commits.
    Remediation: Keep the ignore_tags section in .goreleaser.yml. The v0 tag still exists and is still moved during releases; only the automation was removed, not the tag itself.

  • [scope-alignment] docs/ADRs/0033-per-repo-installation-mode.md — This PR modifies ADR 0033, ADR 0034, and reusable-dispatch.yml to remove PR-context gating for review triggers (ISSUE_IS_PR renamed to ISSUE_HAS_PR, /fs-review no longer gated on PR context, ready-for-review label routing ungated). It also removes a scaffold test (TestReusableDispatchWorkflowContent) and updates 6 documentation files to reflect this routing change. The PR title and body describe standalone mint support with no mention of routing changes. This bundles a functionally distinct routing refinement with the standalone mint feature.
    Remediation: Split the routing logic changes (ADR edits, reusable-dispatch.yml routing, related doc updates, scaffold test removal) into a separate PR focused solely on dispatch routing.

Low

  • [silent-truncation] cmd/mint/proxy.go:91 — The proxy response body is read with io.LimitReader(resp.Body, 1<<20) (1 MiB). If an upstream response exceeds this, the body is silently truncated producing malformed JSON. In practice, mint token responses are well under 1 KiB.

  • [data-exposure] cmd/mint/proxy.go:89 — Fallback proxy forwards the OIDC Authorization header verbatim to FALLBACK_MINT_URL. Mitigated by HTTPS-only validation at startup. Residual low risk: short-lived OIDC token sent to a third-party URL controlled by whoever sets the env var.

  • [authorization] internal/mintcore/github.go:82RegisterCustomRolePermissions does not validate that permission keys are valid GitHub App permission names or that values are limited to read/write. Arbitrary key-value pairs are passed through to the GitHub installation token API, which will reject invalid ones at request time (fail-closed). No security impact since GitHub is the enforcement layer, but malformed entries produce confusing runtime errors rather than clear startup failures.

  • [architectural-asymmetry] internal/mintcore/github.go:73 — Custom role permissions are implemented in the shared mintcore package but exposed only in the standalone mint via CUSTOM_ROLE_PERMISSIONS env var. The GCP Cloud Function mint does not expose this capability. The asymmetry is intentional (GCF serves canonical shared apps), but documenting the design choice would help future contributors.

  • [api-extension-backward-compatible] internal/mintcore/github.goRegisterCustomRolePermissions() changes the behavior of RolePermissions(), RolePermissionsFor(), and HasRole() to include custom roles when registered. The change is additive (without calling RegisterCustomRolePermissions, behavior is identical to before), so no existing callers are affected.

  • [comment-style] cmd/mint/main.go:22buildHandler comment uses "constructs" but the codebase convention (see NewHandler, NewFilesystemPEMAccessor, NewJWKSVerifier) is "creates".

  • [constant-naming] cmd/mint/proxy.go:50 — Magic numbers 64<<10 and 1<<20 used for body limits should be named constants, matching the pattern in jwks_verifier.go which uses maxJWKSResponseLen.

  • [architectural-alignment] cmd/mint/main.go — Standalone mint aligns with ADR 0029's self-managed deployment profile. Custom roles extend the role model, but ADR 0029 does not explicitly authorize this capability — consider whether custom roles warrant their own ADR.

Previous run (11)

Review

Findings

High

  • [protected-path] .github/workflows/, AGENTS.md, skills/cutting-releases/ — This PR modifies 7 protected governance and infrastructure files: .github/workflows/functional-tests.yml, .github/workflows/release.yml, .github/workflows/renovate.yml (deleted), .github/workflows/reusable-dispatch.yml, AGENTS.md, skills/cutting-releases/SKILL.md, and skills/cutting-releases/post-flight.md. The PR has no linked issue and the PR description does not explain why the workflow routing logic, release automation, Renovate workflow, or release skill are being changed. The AGENTS.md change documents cmd/mint/ which is part of the feature, but the .github/ and skills/ changes appear unrelated to the standalone mint feature. Human approval is required for all protected-path modifications.
    Remediation: Link this PR to an issue that authorizes the full scope of changes, including protected-path modifications. Alternatively, split the unrelated changes (workflow routing, release process, Renovate removal) into separate PRs with their own justification.

Medium

  • [logic-error] .goreleaser.yml:3 — Removing the git.ignore_tags section means GoReleaser may use the v0 floating tag as the "previous tag" when generating release changelogs. The v0 tag is still moved during releases (now manually per the updated cutting-releases skill), so it will point at an arbitrary recent release commit. If GoReleaser picks v0 as the previous tag instead of the last semver tag (e.g., v0.45.2), the changelog will either be empty or contain an incorrect set of commits.
    Remediation: Keep the ignore_tags section in .goreleaser.yml. The v0 tag still exists and is still moved during releases; only the automation was removed, not the tag itself.

  • [scope-alignment] docs/ADRs/0033-per-repo-installation-mode.md — This PR modifies ADR 0033, ADR 0034, and reusable-dispatch.yml to remove PR-context gating for review triggers (ISSUE_IS_PR renamed to ISSUE_HAS_PR, /fs-review no longer gated on PR context, ready-for-review label routing ungated). It also removes a scaffold test (TestReusableDispatchWorkflowContent) and updates 6 documentation files to reflect this routing change. The PR title and body describe standalone mint support with no mention of routing changes. This bundles a functionally distinct routing refinement with the standalone mint feature.
    Remediation: Split the routing logic changes (ADR edits, reusable-dispatch.yml routing, related doc updates, scaffold test removal) into a separate PR focused solely on dispatch routing.

Low

  • [silent-truncation] cmd/mint/proxy.go:91 — The proxy response body is read with io.LimitReader(resp.Body, 1<<20) (1 MiB). If an upstream response exceeds this, the body is silently truncated producing malformed JSON. In practice, mint token responses are well under 1 KiB.

  • [data-exposure] cmd/mint/proxy.go:89 — Fallback proxy forwards the OIDC Authorization header verbatim to FALLBACK_MINT_URL. Mitigated by HTTPS-only validation at startup. Residual low risk: short-lived OIDC token sent to a third-party URL controlled by whoever sets the env var.

  • [authorization] internal/mintcore/github.go:82RegisterCustomRolePermissions does not validate that permission keys are valid GitHub App permission names or that values are limited to read/write. Arbitrary key-value pairs are passed through to the GitHub installation token API, which will reject invalid ones at request time (fail-closed). No security impact since GitHub is the enforcement layer, but malformed entries produce confusing runtime errors rather than clear startup failures.

  • [architectural-asymmetry] internal/mintcore/github.go:73 — Custom role permissions are implemented in the shared mintcore package but exposed only in the standalone mint via CUSTOM_ROLE_PERMISSIONS env var. The GCP Cloud Function mint does not expose this capability. The asymmetry is intentional (GCF serves canonical shared apps), but documenting the design choice would help future contributors.

  • [api-extension-backward-compatible] internal/mintcore/github.goRegisterCustomRolePermissions() changes the behavior of RolePermissions(), RolePermissionsFor(), and HasRole() to include custom roles when registered. The change is additive (without calling RegisterCustomRolePermissions, behavior is identical to before), so no existing callers are affected.

  • [comment-style] cmd/mint/main.go:22buildHandler comment uses "constructs" but the codebase convention (see NewHandler, NewFilesystemPEMAccessor, NewJWKSVerifier) is "creates".

  • [constant-naming] cmd/mint/proxy.go:50 — Magic numbers 64<<10 and 1<<20 used for body limits should be named constants, matching the pattern in jwks_verifier.go which uses maxJWKSResponseLen.

  • [architectural-alignment] cmd/mint/main.go — Standalone mint aligns with ADR 0029's self-managed deployment profile. Custom roles extend the role model, but ADR 0029 does not explicitly authorize this capability — consider whether custom roles warrant their own ADR.


Labels: PR modifies CI workflows and dispatch routing logic in addition to the mint component and docs.

Previous run (12)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md is being changed. The change adds a "Standalone mint" section documenting cmd/mint/, which appears to be a legitimate part of the feature, but protected-path changes always require human review regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

Low

  • [documentation-accuracy] docs/guides/infrastructure/standalone-mint.md:31 — Prerequisites state "Go 1.22+" but cmd/mint/go.mod declares go 1.26. Users with Go 1.22–1.25 will get build errors. The prerequisite should say "Go 1.26+".

  • [missing-custom-role-documentation] docs/guides/infrastructure/infrastructure-reference.md:76 — The Role Permissions Matrix section documents only the built-in roles but does not mention that custom roles can now be registered via RegisterCustomRolePermissions(). A brief cross-reference to the standalone-mint.md guide would help users discover the extensibility.

  • [architectural-alignment] internal/mintcore/github.go — Custom role permissions modifies the shared mintcore package affecting both GCP and standalone mints. Custom roles are only exposed in the standalone mint's env vars (CUSTOM_ROLE_PERMISSIONS), creating an asymmetry — the GCP mint cannot currently define custom roles.

  • [silent-truncation] cmd/mint/proxy.go:83 — The proxy response body is read with io.LimitReader(resp.Body, 1<<20) (1 MiB). If an upstream response exceeds this, the body is silently truncated producing malformed JSON. In practice, mint token responses are well under 1 KiB.

  • [data-exposure] cmd/mint/proxy.go:71 — Fallback proxy forwards the OIDC Authorization header verbatim to FALLBACK_MINT_URL. Mitigated by HTTPS-only validation at startup. Residual low risk: short-lived OIDC token sent to a third-party URL controlled by whoever sets the env var.

Previous run (13)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md is being changed. The change adds a "Standalone mint" section documenting cmd/mint/, which appears to be a legitimate part of the feature, but protected-path changes always require human review regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

Low

  • [documentation-accuracy] docs/guides/infrastructure/standalone-mint.md:31 — Prerequisites state "Go 1.22+" but cmd/mint/go.mod declares go 1.26. Users with Go 1.22–1.25 will get build errors. The prerequisite should say "Go 1.26+".

  • [missing-custom-role-documentation] docs/guides/infrastructure/infrastructure-reference.md:76 — The Role Permissions Matrix section documents only the built-in roles but does not mention that custom roles can now be registered via RegisterCustomRolePermissions(). A brief cross-reference to the standalone-mint.md guide would help users discover the extensibility.

  • [architectural-alignment] internal/mintcore/github.go — Custom role permissions modifies the shared mintcore package affecting both GCP and standalone mints. Custom roles are only exposed in the standalone mint's env vars (CUSTOM_ROLE_PERMISSIONS), creating an asymmetry — the GCP mint cannot currently define custom roles.

  • [silent-truncation] cmd/mint/proxy.go:83 — The proxy response body is read with io.LimitReader(resp.Body, 1<<20) (1 MiB). If an upstream response exceeds this, the body is silently truncated producing malformed JSON. In practice, mint token responses are well under 1 KiB.

  • [data-exposure] cmd/mint/proxy.go:71 — Fallback proxy forwards the OIDC Authorization header verbatim to FALLBACK_MINT_URL. Mitigated by HTTPS-only validation at startup. Residual low risk: short-lived OIDC token sent to a third-party URL controlled by whoever sets the env var.

Previous run (14)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md is being changed. The change adds a "Standalone mint" section documenting cmd/mint/, which appears to be a legitimate part of the feature, but protected-path changes always require human review regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

Medium

  • [scope-coherence] cmd/mint/main.go — This PR introduces multiple distinct capabilities in a single change: (1) standalone mint server, (2) filesystem PEM storage, (3) custom role permissions system, (4) fallback proxy mechanism, and (5) comprehensive documentation. The custom role permissions system modifies shared mintcore code affecting both GCP and standalone mints.

  • [missing-custom-role-documentation] docs/guides/infrastructure/infrastructure-reference.md:76 — The Role Permissions Matrix section documents only the 7 built-in roles but does not mention that custom roles can now be registered via RegisterCustomRolePermissions(). Users reading this reference doc would not know the permission matrix can be extended. See also: standalone-mint.md covers custom roles in detail, but the reference doc should cross-reference or note extensibility.

Low

  • [missing-authorization] — This PR has no linked issue. ~1900 lines introducing a new deployment mode (standalone mint), new capabilities (custom roles), and infrastructure changes (filesystem PEM storage). No reference to an issue number that authorized this work.

  • [architectural-alignment] internal/mintcore/github.go — Custom role permissions modifies the shared mintcore package affecting both GCP and standalone mints. Custom roles are only exposed in the standalone mint's env vars (CUSTOM_ROLE_PERMISSIONS), creating an asymmetry. The GCP mint cannot currently define custom roles.

  • [authorization] cmd/mint/main.go:87 — The ALLOWED_WORKFLOW_FILES env var documentation and tests consistently recommend * (wildcard), which allows any workflow file to request tokens. This is a defense-in-depth layer (org allowlisting and OIDC validation are primary controls), but should be documented as a security-relaxing configuration choice.

  • [silent-truncation] cmd/mint/proxy.go:83 — The proxy response body is read with io.LimitReader(resp.Body, 1<<20) (1 MiB). If an upstream response exceeds this, the body is silently truncated producing malformed JSON. No log or error is emitted. In practice, mint token responses are well under 1 KiB.

  • [dead-code-path] internal/mintcore/github.go:114 — The docstring for RolePermissionsFor states custom roles "take precedence over canonical definitions," but RegisterCustomRolePermissions rejects collisions with built-in roles. The precedence code path is unreachable and the docstring is misleading.

  • [documentation-accuracy] docs/guides/infrastructure/standalone-mint.md:31 — Prerequisites state "Go 1.22+" but cmd/mint/go.mod declares go 1.26. Users with Go 1.22-1.25 will get build errors. The prerequisite should say "Go 1.26+".

  • [data-exposure] cmd/mint/proxy.go:71 — Fallback proxy forwards the OIDC Authorization header verbatim to FALLBACK_MINT_URL. Mitigated by HTTPS-only validation at startup. Residual low risk: short-lived OIDC token sent to a third-party URL controlled by whoever sets the env var.

  • [comment-style] cmd/mint/main.go — Unexported helper functions splitCSV, parseLocalRoles, registerCustomPermissions, checkRequired, and sortedKeys lack doc comments. The codebase pattern shows significant unexported functions receive doc comments.

  • [comment-style] cmd/mint/proxy.go:34fallbackHandler.ServeHTTP implements http.Handler but lacks a doc comment. The codebase pattern shows interface-implementing methods receive doc comments.

Previous run (15)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, a protected governance file that requires human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md is being changed. The change adds a "Standalone mint" section documenting cmd/mint/, which appears to be a legitimate part of the feature, but protected-path changes always require human review regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

Medium

  • [scope-coherence] cmd/mint/main.go — This PR introduces multiple distinct capabilities in a single change: (1) standalone mint server, (2) filesystem PEM storage, (3) custom role permissions system, (4) fallback proxy mechanism, and (5) comprehensive documentation. The custom role permissions system modifies shared mintcore code affecting both GCP and standalone mints.

  • [missing-custom-role-documentation] docs/guides/infrastructure/infrastructure-reference.md:76 — The Role Permissions Matrix section documents only the 7 built-in roles but does not mention that custom roles can now be registered via RegisterCustomRolePermissions(). Users reading this reference doc would not know the permission matrix can be extended. See also: standalone-mint.md covers custom roles in detail, but the reference doc should cross-reference or note extensibility.

Low

  • [missing-authorization] — This PR has no linked issue. ~1900 lines introducing a new deployment mode (standalone mint), new capabilities (custom roles), and infrastructure changes (filesystem PEM storage). No reference to an issue number that authorized this work.

  • [architectural-alignment] internal/mintcore/github.go — Custom role permissions modifies the shared mintcore package affecting both GCP and standalone mints. Custom roles are only exposed in the standalone mint's env vars (CUSTOM_ROLE_PERMISSIONS), creating an asymmetry. The GCP mint cannot currently define custom roles.

  • [authorization] cmd/mint/main.go:87 — The ALLOWED_WORKFLOW_FILES env var documentation and tests consistently recommend * (wildcard), which allows any workflow file to request tokens. This is a defense-in-depth layer (org allowlisting and OIDC validation are primary controls), but should be documented as a security-relaxing configuration choice.

  • [silent-truncation] cmd/mint/proxy.go:83 — The proxy response body is read with io.LimitReader(resp.Body, 1<<20) (1 MiB). If an upstream response exceeds this, the body is silently truncated producing malformed JSON. No log or error is emitted. In practice, mint token responses are well under 1 KiB.

  • [dead-code-path] internal/mintcore/github.go:114 — The docstring for RolePermissionsFor states custom roles "take precedence over canonical definitions," but RegisterCustomRolePermissions rejects collisions with built-in roles. The precedence code path is unreachable and the docstring is misleading.

  • [documentation-accuracy] docs/guides/infrastructure/standalone-mint.md:31 — Prerequisites state "Go 1.22+" but cmd/mint/go.mod declares go 1.26. Users with Go 1.22-1.25 will get build errors. The prerequisite should say "Go 1.26+".

  • [data-exposure] cmd/mint/proxy.go:71 — Fallback proxy forwards the OIDC Authorization header verbatim to FALLBACK_MINT_URL. Mitigated by HTTPS-only validation at startup. Residual low risk: short-lived OIDC token sent to a third-party URL controlled by whoever sets the env var.

  • [comment-style] cmd/mint/main.go — Unexported helper functions splitCSV, parseLocalRoles, registerCustomPermissions, checkRequired, and sortedKeys lack doc comments. The codebase pattern shows significant unexported functions receive doc comments.

  • [comment-style] cmd/mint/proxy.go:34fallbackHandler.ServeHTTP implements http.Handler but lacks a doc comment. The codebase pattern shows interface-implementing methods receive doc comments.

Previous run (16)

Review

Findings

High

  • [protected-path] AGENTS.md — This PR modifies AGENTS.md, which is a protected governance file requiring human approval. The PR has no linked issue and the PR description does not explain why AGENTS.md is being changed. The change adds a "Standalone mint" section documenting cmd/mint/, which appears to be a legitimate part of the feature, but protected-path changes always require human review regardless of context.
    Remediation: Link this PR to an issue that authorizes the scope of changes, including the AGENTS.md update. Human approval is required for all protected-path modifications.

Low

  • [input-validation] internal/mintcore/github.go:92RegisterCustomRolePermissions does not validate custom role names against ValidateRoleName/RolePattern. Invalid role names (uppercase, special chars) are accepted at registration but fail at request time — the handler validates against RolePattern before HasRole/RolePermissionsFor is called, and AccessPEM also validates, so the security impact is nil. However, an admin misconfiguring CUSTOM_ROLE_PERMISSIONS with an invalid name gets confusing runtime errors rather than a clear startup rejection.

  • [rbac] internal/mintcore/github.go:93RegisterCustomRolePermissions stores the caller's map reference directly via customRoles.Store(perms) without deep-copying the inner permission maps (map[string]string). The caller retains mutable references to the stored permission data. In the current call site (cmd/mint/main.go registerCustomPermissions) the map comes from json.Unmarshal and is not retained, so this is not exploitable today, but it is a defensive gap.

  • [missing-error-propagation] cmd/mint/proxy.go:90io.Copy(w, io.LimitReader(resp.Body, 64<<10)) silently truncates responses exceeding 64 KiB. The error is logged, but the client receives a truncated response with no indication of truncation. In practice, mint token responses are small, so truncation is unlikely.

  • [data-exposure] cmd/mint/proxy.go:85 — Fallback proxy forwards the caller's OIDC Authorization header verbatim to FALLBACK_MINT_URL. If misconfigured to a non-HTTPS endpoint, the short-lived OIDC token could be leaked. Risk is low since this is server-side configuration.

  • [error-message-style] cmd/mint/main.go:139 — Error messages use "parsing ROLE_APP_IDS: %w" and "parsing CUSTOM_ROLE_PERMISSIONS: %w" while handler.go uses "failed to parse ROLE_APP_IDS: %w" for the same env var, creating inconsistency.


Labels: PR adds standalone mint binary and custom role permissions. Existing labels are appropriate; adding feature label for new capability.

Previous run (17)

Review

Findings

Medium

  • [documentation-correctness] docs/guides/infrastructure/standalone-mint.md:222 — The list of reserved built-in role names omits e2e. canonicalRolePermissions in internal/mintcore/github.go defines 8 built-in roles (triage, coder, review, fix, retro, prioritize, fullsend, e2e), but the doc only lists 7. A user defining a custom role named e2e would get an unexpected collision error at startup.
    Remediation: Add e2e to the reserved role list.

  • [naming-alignment] cmd/mint/cmd/mint/ creates potential confusion with internal/mint/ (GCP Cloud Function). AGENTS.md documents internal/mint/main.go as the source of truth but does not mention cmd/mint/, leaving developers unaware of the standalone variant.
    Remediation: Update the "Mint function" section of AGENTS.md to document cmd/mint/ as the standalone variant.

Low

  • [input-validation] internal/mintcore/github.goRegisterCustomRolePermissions does not validate custom role names against ValidateRoleName/RolePattern. Invalid role names (uppercase, special chars) are accepted at registration but fail at request time in AccessPEM. The handler validates req.Role against RolePattern before reaching RolePermissionsFor, so the runtime impact is nil, but adding validation at registration would surface misconfigurations early.

  • [missing-error-propagation] cmd/mint/proxy.go:90io.Copy(w, io.LimitReader(resp.Body, 64<<10)) silently truncates responses exceeding 64 KiB. The error is logged, but the client receives a truncated response with no indication of truncation. In practice, mint token responses are small, so truncation is unlikely.

  • [data-exposure] cmd/mint/proxy.go:85 — Fallback proxy forwards the caller's OIDC Authorization header verbatim to FALLBACK_MINT_URL. If misconfigured to a non-HTTPS endpoint, the short-lived OIDC token could be leaked. Risk is low since this is server-side configuration.
    Remediation: Consider validating at startup that FALLBACK_MINT_URL uses the https:// scheme.

  • [error-message-style] cmd/mint/main.go:192 — Error messages use "parsing ROLE_APP_IDS: %w" and "parsing CUSTOM_ROLE_PERMISSIONS: %w" while handler.go uses "failed to parse" for the same env var, creating inconsistency.

  • [comment-style] cmd/mint/main.go:72buildHandler lacks a doc comment. The codebase consistently documents significant unexported functions.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

Comment thread internal/mintcore/github.go
Comment thread internal/mintcore/github.go Outdated
Comment thread cmd/mint/main.go Outdated
Comment thread cmd/mint/proxy.go
Comment thread cmd/mint/proxy.go Outdated
Comment thread cmd/mint/proxy.go Outdated
@fullsend-ai-review fullsend-ai-review Bot added component/mint Token mint and cross-boundary credentials component/docs User-facing documentation labels Jun 22, 2026
@ggallen ggallen force-pushed the worktree-standalone-mint branch from 85a1792 to 336456d Compare June 22, 2026 23:27
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 22, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 11:30 PM UTC · Completed 11:44 PM UTC
Commit: 336456d · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 23, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:17 AM UTC · Completed 12:31 AM UTC
Commit: 6a8f7c2 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

Comment thread internal/mintcore/github.go
Comment thread internal/mintcore/github.go
Comment thread cmd/mint/proxy.go
Comment thread cmd/mint/proxy.go Outdated
Comment thread cmd/mint/main.go
@fullsend-ai-review fullsend-ai-review Bot added the feature Feature-category issue awaiting human prioritization label Jun 23, 2026
@ggallen ggallen force-pushed the worktree-standalone-mint branch from 6a8f7c2 to cb9c2df Compare June 23, 2026 02:19
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 23, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 2:22 AM UTC · Completed 2:35 AM UTC
Commit: cb9c2df · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 23, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 3:01 AM UTC · Ended 3:15 AM UTC
Commit: 4e21a60 · View workflow run →

@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 3:01 AM UTC · Completed 3:15 AM UTC
Commit: 928b858 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 23, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:56 AM UTC · Completed 11:12 AM UTC
Commit: bc64a9a · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

Comment thread .goreleaser.yml
Comment thread cmd/mint/proxy.go
Comment thread cmd/mint/proxy.go
Comment thread internal/mintcore/github.go
Comment thread internal/mintcore/github.go
Comment thread cmd/mint/main.go
Comment thread cmd/mint/proxy.go
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 26, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 11:05 PM UTC · Completed 11:06 PM UTC
Commit: b29303b · View workflow run →

@ggallen

ggallen commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 27, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:14 AM UTC · Completed 12:27 AM UTC
Commit: 7f5ce3c · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

Comment thread internal/mintcore/github.go
Comment thread internal/dispatch/gcf/provisioner.go Outdated
Signed-off-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Greg Allen <gallen@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 27, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 2:54 AM UTC · Completed 2:56 AM UTC
Commit: b520dcb · View workflow run →

@ggallen

ggallen commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 27, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 3:07 AM UTC · Completed 3:08 AM UTC
Commit: 7f5ce3c · View workflow run →

@ggallen

ggallen commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 27, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 3:30 AM UTC · Completed 3:31 AM UTC
Commit: 7f5ce3c · View workflow run →

@ggallen

ggallen commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 27, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 11:25 AM UTC · Completed 11:26 AM UTC
Commit: 7f5ce3c · View workflow run →

@ggallen

ggallen commented Jun 27, 2026

Copy link
Copy Markdown
Member Author

/fs-review

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 27, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:16 PM UTC · Completed 12:33 PM UTC
Commit: 7f5ce3c · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

@ggallen ggallen added this pull request to the merge queue Jun 29, 2026
Merged via the queue into fullsend-ai:main with commit 21a3760 Jun 29, 2026
23 checks passed
@ggallen ggallen deleted the worktree-standalone-mint branch June 29, 2026 11:45
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jun 29, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 11:50 AM UTC · Completed 12:00 PM UTC
Commit: b520dcb · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #2537 — standalone mint with custom role support

PR type: Human-authored feature PR (ggallen), 2048 additions across 22 files, merged after 7 days.

Timeline: Created Jun 22 → fullsend-ai-review[bot] ran 24 review rounds (10 SUCCESS, 13 FAILURE, 1 TERMINATED) → Review Squad (waynesun09, 8 agents) posted Jun 23 → human review (ifireball) requested changes Jun 24 → final approval Jun 29.

What worked well:

  • The review bot caught 9 genuine bugs in its first round, including a high-severity API contract inconsistency (RolePermissions() missing custom roles), a race condition on the global customRolePermissions, and a fail-open on malformed JSON.
  • The Review Squad independently caught 3 high-severity security issues the bot missed (missing server timeouts, insecure proxy headers, weak URL validation), all of which were fixed.
  • The human reviewer caught a performance regression (atomic.Value on hot path) and a strategic architecture question neither bot surfaced.
  • Between the three review sources, the PR received thorough coverage and shipped with significantly improved quality.

What didn't work well:

Existing issues that cover observed problems (skipped as proposals):

Proposals filed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/dispatch Workflow dispatch and triggers component/docs User-facing documentation component/mint Token mint and cross-boundary credentials feature Feature-category issue awaiting human prioritization go Pull requests that update go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants