Skip to content

🐛 Fail-closed token revocation on intermediate stage failure - #133

Merged
savitharaghunathan merged 2 commits into
konveyor:mainfrom
savitharaghunathan:issue109
Aug 12, 2026
Merged

savitharaghunathan merged 2 commits into
konveyor:mainfrom
savitharaghunathan:issue109

Conversation

@savitharaghunathan

@savitharaghunathan savitharaghunathan commented Aug 10, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • Bug Fixes

    • Improved Hub API token handling to revoke valid tokens when runs fail, complete, or execute independently.
    • Preserved tokens only when a successful intermediate stage is followed by another stage requiring the token.
    • Added clearer warnings for invalid token IDs.
  • Tests

    • Expanded coverage for token parsing, workflow stages, and revocation decisions, including malformed and edge-case inputs.

…konveyor#109)

Assisted-By: Claude Code <noreply@anthropic.com>
Signed-off-by: Savitha Raghunathan <saveetha13@gmail.com>
Assisted-By: Claude Code <noreply@anthropic.com>
Signed-off-by: Savitha Raghunathan <saveetha13@gmail.com>
@savitharaghunathan savitharaghunathan self-assigned this Aug 10, 2026
@savitharaghunathan savitharaghunathan linked an issue Aug 10, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The migration harness now validates Hub token IDs, revokes valid tokens on workflow exit, and defers revocation only after successful intermediate stages. Tests cover parsing, stage detection, and revocation decisions. A changelog entry documents the behavior.

Changes

Token revocation lifecycle

Layer / File(s) Summary
Token cleanup lifecycle
harness/cmd/migration-harness/main.go
Valid token IDs now use deferred cleanup. Failures, final stages, standalone runs, and invalid stage metadata revoke tokens. Successful intermediate stages defer revocation.
Decision validation and release notes
harness/cmd/migration-harness/main_test.go, changes/unreleased/109-fail-closed-token-revocation.yaml
Tests cover token parsing, intermediate-stage detection, and revocation decisions. The changelog documents fail-closed token handling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MigrationHarness
  participant WorkflowStage
  participant HubAPI
  MigrationHarness->>HubAPI: Parse and validate token ID
  MigrationHarness->>WorkflowStage: Run migration stage
  WorkflowStage-->>MigrationHarness: Return success or failure
  MigrationHarness->>HubAPI: Revoke token unless successful intermediate stage
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description contains only the template comments and provides no summary, testing details, or confirmation of the required changelog fragment. Add a concise change summary, testing information, and relevant implementation details, and confirm the changelog fragment for this bug fix.
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title uses the required bug-fix prefix and clearly describes fail-closed token revocation for intermediate stage failures.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@harness/cmd/migration-harness/main.go`:
- Around line 452-456: Update the Hub token ID parsing in the relevant helper in
main.go to use strconv.IntSize instead of a fixed 64-bit size, rejecting values
that exceed the target uint range before conversion. Add boundary tests in
main_test.go covering the maximum uint value and the value immediately above it,
including 32-bit behavior where applicable.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f8404ad0-3103-4dd8-9f8c-8860fedc5440

📥 Commits

Reviewing files that changed from the base of the PR and between 72f126d and a9a175d.

📒 Files selected for processing (3)
  • changes/unreleased/109-fail-closed-token-revocation.yaml
  • harness/cmd/migration-harness/main.go
  • harness/cmd/migration-harness/main_test.go

Comment on lines 452 to +456
tokenID, err := strconv.ParseUint(cfg.HubTokenID, 10, 64)
if err != nil {
return 0, false
}
if cfg.WorkflowStage == "" && cfg.WorkflowStageCount == "" {
return uint(tokenID), true
return uint(tokenID), true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect declared build targets. A 32-bit target makes the uint64-to-uint
# conversion lossy for token IDs above math.MaxUint32.
rg -n -i \
  --glob 'Dockerfile*' \
  --glob '*.yaml' \
  --glob '*.yml' \
  --glob '*.json' \
  --glob '*.sh' \
  'GOARCH|GOOS|--platform|architecture' .

# Show the parser, its tests, and the downstream uint API contract.
rg -n -C 4 'func parseHubTokenID|ParseUint|func \(c \*Client\) RevokeToken|func TestParseHubTokenID' \
  harness/cmd/migration-harness/main.go \
  harness/cmd/migration-harness/main_test.go \
  harness/internal/hub/client.go

Repository: konveyor/agentic-controller

Length of output: 6771


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== parser and tests =="
sed -n '440,505p' harness/cmd/migration-harness/main.go
echo
sed -n '75,95p' harness/cmd/migration-harness/main_test.go

echo
echo "== Dockerfile multiarch build context =="
sed -n '1,35p' Dockerfile

echo
echo "== runtime semantics probe =="
cat > /tmp/probe.go <<'EOF'
package main

import (
	"fmt"
	"strconv"
)

func main() {
	for _, s := range []string{"281474976710656", "42"} {
		n, err := strconv.ParseUint(s, 10, 64)
		if err != nil {
			panic(err)
		}
		u := uint(n)
		fmt.Printf("%s -> uint64=%d -> uint=%d (overflow=%t)\n", s, n, u, n != uint64(u))
	}
}
EOF
go run /tmp/probe.go
rm /tmp/probe.go

Repository: konveyor/agentic-controller

Length of output: 4171


🌐 Web query:

Go strconv.IntSize documentation

💡 Result:

In the Go programming language, strconv.IntSize is a constant that represents the size in bits of an int or uint value on the machine where the code is currently running [1][2]. Because the Go int and uint types have a size that is platform-dependent (typically 32 bits on 32-bit systems and 64 bits on 64-bit systems), this constant allows developers to programmatically determine the bit size of these types at runtime [2][3]. It is defined as a constant within the standard library's strconv package [1]. You can access it as follows: import "strconv" fmt.Println(strconv.IntSize) // Outputs 32 or 64 depending on the architecture [2][3]

Citations:


Reject Hub token IDs that overflow the target uint.

strconv.ParseUint(..., 10, 64) accepts values larger than uint on 32-bit targets. In a 32-bit build, a parsed ID truncates when converted to uint, so the harness can revoke the wrong token. Parse with strconv.IntSize and add boundary tests around the maximum uint value and one value above it.

📍 Affects 2 files
  • harness/cmd/migration-harness/main.go#L452-L456 (this comment)
  • harness/cmd/migration-harness/main_test.go#L82-L85
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@harness/cmd/migration-harness/main.go` around lines 452 - 456, Update the Hub
token ID parsing in the relevant helper in main.go to use strconv.IntSize
instead of a fixed 64-bit size, rejecting values that exceed the target uint
range before conversion. Add boundary tests in main_test.go covering the maximum
uint value and the value immediately above it, including 32-bit behavior where
applicable.

@ibolton336 ibolton336 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.

Verified the semantics this leans on; all hold on main:

  • runStage has exactly one success exit and stageSucceeded = true sits immediately before it — every other path returns an error and revokes. Viewer-cancel lands on the failure path too, which is right since the workflow run dies with it.
  • The workflow controller fails the entire run on a failed stage (never creates stage N+1), and sandbox pods run RestartPolicy: Never — so nothing today can retry a stage into a revoked token. Harness tests pass locally at this head.

Two non-blocking notes:

  1. The sandbox pod spec comment reserves "bounded retry can be added later" (agentrun_controller.go ~388). When that lands, revoke-on-failure fights it — a retried stage boots with a dead token. Worth one breadcrumb line in the fail-closed comment here tying it to the no-retry semantics (the coupling predates this PR for standalone runs; this widens it to workflow stages).
  2. This closes the in-process exit paths, but two #109 paths still leak until TTL: a pod killed without defers running (OOM/node loss), and an intermediate success where the next stage never actually runs (create failure, run deleted between stages). Only a controller/Hub-side janitor covers those — is that tracked anywhere, or should #109 stay partially open rather than auto-close?

Nit: TestTokenRevocationDecision recomputes !(intermediate && tt.stageSucceeded) — the same expression as the defer — so it can't catch a regression in the real decision. Extracting the condition into a named func and testing that would make it bite.

@savitharaghunathan
savitharaghunathan merged commit 38e552d into konveyor:main Aug 12, 2026
15 checks passed

@fabianvf fabianvf left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM.

agentworkflowrun_controller.go:376 seems stale now, it still says the harness "revokes the Hub API token only on the last stage (#68)".

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hub API token leaked when intermediate workflow stage fails

3 participants