Skip to content

chore(mint): log requested and granted token scope - #1918

Merged
ralphbean merged 3 commits into
mainfrom
chore/1916-mint-token-scope-logging
Jun 5, 2026
Merged

chore(mint): log requested and granted token scope#1918
ralphbean merged 3 commits into
mainfrom
chore/1916-mint-token-scope-logging

Conversation

@ralphbean

Copy link
Copy Markdown
Member

Summary

  • Parse repositories, permissions, and repository_selection from GitHub's installation token response (fields we were discarding)
  • Log both requested and granted scope on the mint server side
  • Return granted scope in the mint response so the mint-token action can log it in workflow logs too
  • Add OIDC source repo and workflow ref to the mint log line for tracing provenance

Relates to #1916 — gets us the observability needed to investigate how downstream retro runs are posting issues cross-org.

Test plan

  • Existing mint tests pass
  • TestHandler_FullFlow updated to verify granted scope plumbing end-to-end
  • make go-test and make lint pass
  • Deploy mint, trigger a retro run, check Cloud Function logs for the new granted scope: line
  • Check workflow logs for the new Requesting token: / Granted scope: output

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

Site preview

Preview: https://03c0d80c-site.fullsend-ai.workers.dev

Commit: 7aedecc7bf44f5498ec475953ed89dfe66b51b02

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [protected-path] .github/actions/mint-token/action.yml — This PR modifies a file under .github/, which is a protected path requiring human approval. The change is well-justified (observability for investigating cross-org token scoping per mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916) and the scaffold copy at internal/scaffold/fullsend-repo/.github/actions/mint-token/action.yml is kept in sync, but human review is required for all protected-path changes regardless of context.

Low

  • [test-adequacy] internal/mintcore/github_test.go:90TestCreateInstallationToken ignores the new *GrantedScope return value with _. The mock server returns no permissions, repositories, or repository_selection fields, so the unit test does not verify that GrantedScope is correctly populated from the API response. TestHandler_FullFlow and TestHandler_FullFlowGrantedScopeAll exercise the full path with GrantedScope assertions, so this is not a coverage gap in practice, but adding a unit-level assertion would catch parsing regressions in isolation.

  • [naming-convention] internal/mintcore/handler.goGrantedPerms on mintResponse abbreviates "Permissions" while the JSON tag uses the full word granted_permissions and the codebase consistently uses unabbreviated names (canonicalRolePermissions, RolePermissionsFor). Consider GrantedPermissions for consistency.

Info

  • [scope-alignment] The PR is correctly scoped as observability instrumentation for mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916. It adds logging and response fields to surface granted scope without altering authorization logic or security boundaries.

  • [architectural-coherence] The GrantedScope type and OIDC claim logging align with ADR-0029's design of the mint as the central authority for token issuance with observability as a blast-radius mitigation control.

  • [logic-completeness-resolved] The prior finding about permission mismatch detection only iterating over granted.Permissions has been addressed: handler.go now includes a second loop over requested permissions to detect requested permissions that were not granted. The new TestHandler_LogsRequestedPermissionNotGranted test exercises this path.

Previous run

Review

Findings

Medium

  • [protected-path] .github/actions/mint-token/action.yml — This PR modifies a file under .github/, which is a protected path requiring human approval. The change is well-justified (observability for investigating cross-org token scoping per mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916) and the scaffold copy at internal/scaffold/fullsend-repo/.github/actions/mint-token/action.yml is kept in sync, but human review is required for all protected-path changes regardless of context.

Low

  • [test-adequacy] internal/mintcore/github_test.go:90TestCreateInstallationToken ignores the new *GrantedScope return value with _. The mock server returns no permissions, repositories, or repository_selection fields, so the unit test does not verify that GrantedScope is correctly populated from the API response. TestHandler_FullFlow and TestHandler_FullFlowGrantedScopeAll exercise the full path with GrantedScope assertions, so this is not a coverage gap in practice, but adding a unit-level assertion would catch parsing regressions in isolation.

Info

  • [scope-alignment] The PR is correctly scoped as observability instrumentation for mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916. It adds logging and response fields to surface granted scope without altering authorization logic or security boundaries.

  • [architectural-coherence] The GrantedScope type and OIDC claim logging align with ADR-0029's design of the mint as the central authority for token issuance with observability as a blast-radius mitigation control.

  • [logic-completeness-resolved] The prior finding about permission mismatch detection only iterating over granted.Permissions has been addressed: handler.go now includes a second loop over requested permissions to detect requested permissions that were not granted. The new TestHandler_LogsRequestedPermissionNotGranted test exercises this path.

Previous run (2)

Review

Findings

Medium

  • [protected-path] .github/actions/mint-token/action.yml — This PR modifies a file under .github/, which is a protected path requiring human approval. The change is well-justified (observability for investigating cross-org token scoping per mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916) and the scaffold copy is kept in sync, but human review is required for all protected-path changes regardless of context.

Low

  • [logic-completeness] internal/mintcore/handler.go — The permission mismatch detection loop only iterates over granted.Permissions, detecting extra or differently-leveled permissions. It does not detect the inverse case where a requested permission is entirely absent from the granted set (GitHub declined to grant it). For the mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916 investigation, under-scoping detection may also be relevant. Consider adding a second loop over requested checking for keys absent from granted.Permissions.

  • [test-adequacy] internal/mintcore/github_test.go:90TestCreateInstallationToken ignores the new *GrantedScope return value with _. While TestHandler_FullFlow exercises the full path including GrantedScope assertions, the unit test for CreateInstallationToken itself does not verify that GrantedScope is correctly populated from the API response. Consider adding a test case with populated Permissions, Repositories, and RepositorySelection fields.

  • [pattern-inconsistency] internal/mintcore/handler.go — The new WARNING: log prefix (uppercase) diverges from the existing lowercase log format pattern in this package (e.g., failed to mint token:, OIDC verification failed). Consider using lowercase warning: or a structured prefix like [warn] for consistency.

  • [log-format-consistency] internal/mintcore/handler.go — The new logging splits a single mint event across multiple log.Printf calls (requested scope, granted scope, warnings), making it harder to correlate related log lines in Cloud Function logs. Consider consolidating into fewer log statements with consistent key=value format.

Info

  • [scope-alignment] The PR is correctly scoped as observability instrumentation for mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916. It adds logging and response fields to surface granted scope without altering authorization logic or security boundaries. The chore tier is appropriate.

  • [architectural-coherence] The GrantedScope type and OIDC claim logging align with ADR-0029's design of the mint as the central authority for token issuance with observability as a blast-radius mitigation control.

Previous run (3)

Review

Findings

Medium

  • [protected-path] .github/actions/mint-token/action.yml — This PR modifies a file under .github/, which is a protected path requiring human approval. The change is well-justified (observability for investigating cross-org token scoping per mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916) and the scaffold copy is kept in sync, but human review is required for all protected-path changes regardless of context.

Low

  • [logic-completeness] internal/mintcore/handler.go — The permission mismatch detection loop only iterates over granted.Permissions, detecting extra or differently-leveled permissions. It does not detect the inverse case where a requested permission is entirely absent from the granted set (GitHub declined to grant it). For the mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916 investigation, under-scoping detection may also be relevant. Consider adding a second loop over requested checking for keys absent from granted.Permissions.

  • [test-adequacy] internal/mintcore/github_test.go:90TestCreateInstallationToken ignores the new *GrantedScope return value with _. While TestHandler_FullFlow exercises the full path including GrantedScope assertions, the unit test for CreateInstallationToken itself does not verify that GrantedScope is correctly populated from the API response. Consider adding a test case with populated Permissions, Repositories, and RepositorySelection fields.

  • [pattern-inconsistency] internal/mintcore/handler.go — The new WARNING: log prefix (uppercase) diverges from the existing lowercase log format pattern in this package (e.g., failed to mint token:, OIDC verification failed). Consider using lowercase warning: or a structured prefix like [warn] for consistency.

  • [log-format-consistency] internal/mintcore/handler.go — The new logging splits a single mint event across multiple log.Printf calls (requested scope, granted scope, warnings), making it harder to correlate related log lines in Cloud Function logs. Consider consolidating into fewer log statements with consistent key=value format.

Info

  • [scope-alignment] The PR is correctly scoped as observability instrumentation for mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916. It adds logging and response fields to surface granted scope without altering authorization logic or security boundaries. The chore tier is appropriate.

  • [architectural-coherence] The GrantedScope type and OIDC claim logging align with ADR-0029's design of the mint as the central authority for token issuance with observability as a blast-radius mitigation control.

Previous run (4)

Review

Findings

Medium

  • [protected-path] .github/actions/mint-token/action.yml — This file is under the .github/ protected path. The PR provides sufficient context (relates to mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916, explains the change for client-side scope logging). Human approval is always required for protected-path changes regardless of context.

  • [scope-creep] internal/mint/main.go — The PR is titled chore(mint): but adds new API response fields (granted_repos, granted_permissions, repository_selection) to the mint response, which expands the public API surface. The new fields are backward-compatible (omitempty) and the PR body explains the intent, but the commit prefix should be feat(mint): rather than chore(mint): since this is an additive API change, not a maintenance task. GoReleaser uses commit prefixes for release notes.

Low

  • [data-exposure] internal/mint/main.go — The granted scope (repos, permissions, repository_selection) is returned in the HTTP response to the caller. The caller already holds the token and can discover its scope via GitHub's API, so the incremental risk is minimal. However, if the goal is purely server-side observability (per mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916), server-side logging alone would suffice without adding response fields.

  • [error-handling] internal/mint/main.go — The if granted != nil guard in ServeHTTP is always true on the success path because createInstallationToken unconditionally constructs a *grantedScope on success. The guard is defensive and not incorrect, but may mislead future readers into thinking a nil-granted path exists.

  • [log-message-format] internal/mint/main.go — The log message changed from past tense (minted: org=%s...) to present participle (minting finished for request: org=%s...). Existing log messages in this file consistently use past tense. Consider: minted: org=%s role=%s requested_repos=%v source_repo=%s workflow_ref=%s.

  • [commit-prefix] — Per Conventional Commits and CONTRIBUTING.md, chore indicates no user-facing changes. Adding API response fields is a feat even if backward-compatible.

Info

  • [response-field-consistency] internal/mint/main.goGrantedPerms abbreviates "Permissions" while the corresponding installationTokenResponse.Permissions and rolePermissions map use the full word. Consider GrantedPermissions for consistency.

  • [test-adequacy] internal/mint/main_test.go — Tests are properly strengthened: TestHandler_FullFlow adds assertions for new response fields, and TestHandler_FullFlowGrantedScopeAll covers the repository_selection=all scenario from mint: investigate how downstream retro runs post issues to fullsend-ai/fullsend #1916. No assertions loosened or removed.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jun 4, 2026

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review squad (4 agents: claude-coder, claude-researcher, gemini, cursor) — 3 MEDIUM, 6 LOW, 3 INFO findings after dedup and verification (2 false positives removed). Posting the 2 actionable MEDIUM findings inline.

Comment on lines +55 to 65
MINT_RESPONSE=$(curl -sSf --retry 5 --retry-delay 5 --retry-all-errors \
-H "Authorization: Bearer $OIDC_TOKEN" \
-H "Content-Type: application/json" \
-d "$BODY" \
"${MINT_URL}/v1/token" | jq -r '.token')
"${MINT_URL}/v1/token")
TOKEN=$(echo "$MINT_RESPONSE" | jq -r '.token')
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
echo "::error::Token mint returned no token for role=$ROLE"
exit 1
fi
echo "::add-mask::$TOKEN"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

MEDIUM — Token masking window

The full JSON response (including the raw token) now lives in MINT_RESPONSE before ::add-mask:: is called. Previously the token was extracted inline (curl | jq -r '.token'), so the full response never existed in a named variable.

If ACTIONS_STEP_DEBUG=true, the runner traces shell variable assignments — the raw token in MINT_RESPONSE would appear in debug logs before the mask on line 65 takes effect.

Suggestion: Mask immediately after extraction, before any other processing of MINT_RESPONSE:

MINT_RESPONSE=$(curl -sSf --retry 5 --retry-delay 5 --retry-all-errors \
  -H "Authorization: Bearer $OIDC_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$BODY" \
  "${MINT_URL}/v1/token")
TOKEN=$(echo "$MINT_RESPONSE" | jq -r '.token')
echo "::add-mask::$TOKEN"  # mask before any further echo of MINT_RESPONSE

This closes the window between storing and masking.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Pushed b064cf4. Moved the mask right after extraction, before the validity check.

Comment thread internal/mint/main.go Outdated
Comment on lines +543 to +546
if granted != nil {
log.Printf("granted scope: repos=%v permissions=%v repo_selection=%s",
granted.Repos, granted.Permissions, granted.RepoSelection)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

MEDIUM — No validation when granted scope diverges from requested scope

The PR's motivation (#1916) is investigating how downstream retro runs post issues cross-org. This logs granted scope but never compares it to requested scope. When repository_selection=all is returned, the token has access to all repos the app is installed on — not just the requested ones. That's the exact privilege escalation signal #1916 needs to detect, yet it's logged identically to a normal selected response.

Similarly, granted.Permissions is never compared against rolePermissions[role], so a permission-level divergence would go unnoticed.

Suggestion: Add comparison and severity-differentiated logging:

if granted != nil {
	log.Printf("granted scope: repos=%v permissions=%v repo_selection=%s",
		granted.Repos, granted.Permissions, granted.RepoSelection)
	if granted.RepoSelection == "all" {
		log.Printf("WARNING: token granted with repository_selection=all (requested specific repos: %v)", req.Repos)
	}
	requested := rolePermissions[req.Role]
	for perm, level := range granted.Permissions {
		if reqLevel, ok := requested[perm]; !ok {
			log.Printf("WARNING: extra permission granted: %s=%s (not requested)", perm, level)
		} else if level != reqLevel {
			log.Printf("WARNING: permission level mismatch: %s requested=%s granted=%s", perm, reqLevel, level)
		}
	}
}

This turns the observability plumbing into the detection capability #1916 actually needs.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Pushed b064cf4. Added WARNING logs for repository_selection=all and for any permission that diverges from what the role defines in rolePermissions.

Comment thread .github/actions/mint-token/action.yml Outdated
Comment on lines +66 to +69
GRANTED_REPOS=$(echo "$MINT_RESPONSE" | jq -r '.granted_repos // [] | join(",")')
GRANTED_PERMS=$(echo "$MINT_RESPONSE" | jq -r '.granted_permissions // {} | to_entries | map("\(.key)=\(.value)") | join(",")')
REPO_SELECTION=$(echo "$MINT_RESPONSE" | jq -r '.repository_selection // "unknown"')
echo "Granted scope: repos=${GRANTED_REPOS:-none} permissions=${GRANTED_PERMS:-none} repo_selection=${REPO_SELECTION}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are you adding what it seems as default values? If the mint does not return anything we shouldn't mask that, it will be misleading.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, that was misleading. Switched to // empty so jq produces nothing when the field is absent — the log line just shows blank values instead of fabricated ones.

@rh-hemartin rh-hemartin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree with @waynesun09 comments, approving because I expect you to address them.

ralphbean added a commit that referenced this pull request Jun 5, 2026
Move ::add-mask:: immediately after token extraction to close the
debug-log exposure window. Remove fabricated jq defaults — log only
what the mint actually returns. Add WARNING-level logs when GitHub
grants repository_selection=all or permissions that diverge from
what the role requested.

Addresses review feedback from #1918.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Parse repositories, permissions, and repository_selection from
GitHub's installation token response. Log both requested and granted
scope server-side with WARNING when repository_selection=all or
permissions diverge from the role definition. Return granted scope
in the mint response so the mint-token action can log it client-side.

Mask token immediately after extraction to close the debug-log
exposure window. Use jq's // empty instead of fabricated defaults
so absent fields produce blank output.

Relates-to: #1916

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@ralphbean
ralphbean force-pushed the chore/1916-mint-token-scope-logging branch from 978ecc7 to b0840dc Compare June 5, 2026 15:09
@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jun 5, 2026
@ralphbean

Copy link
Copy Markdown
Member Author

Heads up — the mintcore refactor landed on main while this was in review, so I had to rewrite the changes on top of the new structure. The logic is the same but it lives in internal/mintcore/ now instead of internal/mint/main.go. Would appreciate a second look given the diff is substantially different.

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review squad (7 agents) found 1 MEDIUM, 3 LOW, 3 INFO findings. Rebase onto mintcore refactor is clean, prior review issues are resolved, no regressions to identity token flows. The one actionable item is the one-directional permission comparison — adding under-scoping detection would complete the observability for #1916.

Comment on lines +228 to +234
for perm, level := range granted.Permissions {
if reqLevel, ok := requested[perm]; !ok {
log.Printf("WARNING: extra permission granted: %s=%s (not requested)", perm, level)
} else if level != reqLevel {
log.Printf("WARNING: permission level mismatch: %s requested=%s granted=%s", perm, reqLevel, level)
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

MEDIUM — Permission comparison is one-directional; missing permissions not detected

This loop iterates over granted.Permissions and checks each against requested, which catches extra permissions and level mismatches. However, it does not detect the inverse: a permission that was requested via RolePermissionsFor() but entirely absent from the granted set. If GitHub declines to grant a requested permission, the under-scoping goes unlogged.

Given that #1916 is investigating scope divergence, under-granting is arguably as important as over-granting for the observability picture.

Flagged in the prior review round and still unaddressed — 7/7 review agents agreed on this one.

Suggestion: Add a reverse loop after this block:

for perm, reqLevel := range requested {
    if _, ok := granted.Permissions[perm]; !ok {
        log.Printf("WARNING: requested permission not granted: %s=%s", perm, reqLevel)
    }
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think you're right that under-granting is the more interesting case for #1916. Pushed 7b9c539 with the reverse loop and a test that simulates GitHub dropping permissions from the granted set.

Add a reverse loop after the existing over-grant check to detect
permissions that were requested via RolePermissionsFor() but absent
from the granted set. This completes the scope divergence
observability for #1916 — under-granting is now logged alongside
over-granting and level mismatches.

Closes review feedback from waynesun09 on PR #1918.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@ralphbean
ralphbean requested a review from waynesun09 June 5, 2026 17:19
@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Jun 5, 2026

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review Squad Report (5 agents: 2x claude-coder, claude-researcher, gemini, cursor)

2 MEDIUM findings posted inline on action.yml. No critical or high issues found. Overall the PR is well-structured — code is consistent across all paired files, tests are thorough, and add-mask timing is improved over main. Both MEDIUM issues are straightforward shell fixes in the action script.

Comment on lines +54 to 62
echo "Requesting token: role=$ROLE repos=$REPOS"
MINT_RESPONSE=$(curl -sSf --retry 5 --retry-delay 5 --retry-all-errors \
-H "Authorization: Bearer $OIDC_TOKEN" \
-H "Content-Type: application/json" \
-d "$BODY" \
"${MINT_URL}/v1/token" | jq -r '.token')
"${MINT_URL}/v1/token")
TOKEN=$(echo "$MINT_RESPONSE" | jq -r '.token')
echo "::add-mask::$TOKEN"
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

MEDIUM — MINT_RESPONSE variable holds raw token without masking

MINT_RESPONSE holds the full JSON response (including the plaintext token) and is never masked. While TOKEN is masked after extraction, MINT_RESPONSE itself persists for the rest of the step. If ACTIONS_STEP_DEBUG is enabled or a future change echoes this variable, the token could leak. Previously the token was piped directly through jq and never stored in a named variable.

Suggestion — add echo "::add-mask::$MINT_RESPONSE" immediately after the curl, or unset MINT_RESPONSE after extracting all fields:

MINT_RESPONSE=$(curl -sSf --retry 5 --retry-delay 5 --retry-all-errors \
  -H "Authorization: Bearer $OIDC_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$BODY" \
  "${MINT_URL}/v1/token")
echo "::add-mask::$MINT_RESPONSE"
TOKEN=$(echo "$MINT_RESPONSE" | jq -r '.token')
echo "::add-mask::$TOKEN"

Flagged by 4/5 review agents.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed both in 7aedecc. Masked MINT_RESPONSE right after curl, and moved the null/empty check before the token mask so we never register "null" as a secret.

Comment on lines 58 to 62
-d "$BODY" \
"${MINT_URL}/v1/token" | jq -r '.token')
"${MINT_URL}/v1/token")
TOKEN=$(echo "$MINT_RESPONSE" | jq -r '.token')
echo "::add-mask::$TOKEN"
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

MEDIUM — Masking "null" string breaks subsequent logging on mint failure

::add-mask::$TOKEN runs before the null/empty check. If the mint returns "null" for the token, the literal string "null" gets registered as a secret, causing all subsequent occurrences of "null" in workflow logs to be replaced with *** for the remainder of the job.

Suggestion — move validation before the mask:

TOKEN=$(echo "$MINT_RESPONSE" | jq -r '.token')
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
  echo "::error::Token mint returned no token for role=$ROLE"
  exit 1
fi
echo "::add-mask::$TOKEN"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Covered in the same commit — token validation now runs before the mask.

Mask MINT_RESPONSE immediately after curl to prevent token leakage
if ACTIONS_STEP_DEBUG is enabled. Move the null/empty token check
before ::add-mask::$TOKEN to avoid masking the literal string "null"
which would corrupt all subsequent log output for the job.

Both copies (.github/actions/ and scaffold) updated in sync.

Addresses review feedback from waynesun09 on PR #1918.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@ralphbean
ralphbean requested a review from waynesun09 June 5, 2026 18:15
@fullsend-ai-review fullsend-ai-review Bot removed the requires-manual-review Review requires human judgment label Jun 5, 2026
@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jun 5, 2026

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Both MEDIUM findings from the initial 5-agent review have been addressed in commit 7aedecc:

  1. MINT_RESPONSE masking::add-mask::$MINT_RESPONSE added immediately after curl, closing the debug-log exposure window
  2. Null token masking — validation moved before ::add-mask::$TOKEN, preventing the literal "null" from being registered as a secret

Re-reviewed with 2 agents post-fix: no remaining MEDIUM+ issues. All file pairs (source/embed, root/scaffold) are byte-identical. Tests pass. LGTM.

@ralphbean
ralphbean added this pull request to the merge queue Jun 5, 2026
Merged via the queue into main with commit 184ec47 Jun 5, 2026
12 of 14 checks passed
@ralphbean
ralphbean deleted the chore/1916-mint-token-scope-logging branch June 5, 2026 20:09
@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #1918chore(mint): log requested and granted token scope

This human-authored PR added scope logging and diagnostics to the token mint service. The workflow performed well overall.

Review quality: Excellent

  • The review agent surfaced 5 MEDIUM findings across 4 review rounds — all were genuine security/correctness issues (token masking order, one-directional permission comparison, masking the literal string "null").
  • Zero false positives at MEDIUM severity. The human author addressed every finding.
  • The human reviewer (rh-hemartin) caught an additional issue about misleading default values.

Rework: Justified

  • 4 review rounds occurred, but they were driven by legitimate code changes: a major mintcore refactor landed on main mid-review, requiring the author to rewrite the PR. Each round reviewed genuinely new code, not re-flagging old issues.

Minor inefficiency observed (already tracked)

  • A 4th review dispatch ran after the agent had already approved (dispatch at 18:30, approval at 18:28). This ~30-minute review run was redundant. This pattern is already tracked by existing issues:
    • #963 — Skip review dispatch when HEAD SHA was already reviewed and approved
    • #1125 — Filter pull_request_review trigger to only fire on changes_requested
    • #1271 — Filter bot-triggered pull_request_review events to prevent self-triggering

Conclusion

No new proposals — the workflow executed well and the minor inefficiency patterns are already covered by existing open issues.

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

Labels

requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants