Skip to content

feat(config): bind OpenClaw keyless credentials#1999

Open
evandance wants to merge 1 commit into
mainfrom
feat/keyless-integration
Open

feat(config): bind OpenClaw keyless credentials#1999
evandance wants to merge 1 commit into
mainfrom
feat/keyless-integration

Conversation

@evandance

@evandance evandance commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add keyless private_key_jwt authentication and OpenClaw binding so the CLI can reuse an OpenClaw-managed signing identity without requiring or persisting an app secret.

Changes

  • Support private_key_jwt across app registration, tenant-token, and user authorization flows.
  • Add config bind --source openclaw, persisting only the logical provider and key reference; discovered signer executable paths are never written to CLI config.
  • Resolve and validate the optional OpenClaw signer package, with platform-safe handling on macOS, Linux, and Windows.
  • Add built-in non-exportable platform signers, diagnostics, release wiring, and coverage for keyless init/bind behavior.
  • Generate macOS private keys directly in Keychain with a creator-only access policy, without temporary private-key files.

Test Plan

  • make unit-test
  • go vet ./...
  • Real macOS Keychain signer round-trip with CGO_ENABLED=0
  • Manual OpenClaw bot-to-CLI keyless bind, authorization, and document-creation flow

Related Issues

  • None

Summary by CodeRabbit

  • New Features

    • Added private_key_jwt authentication support using secure hardware or approved keyless signing providers.
    • Added config init --private-key-jwt and config init --restore with auth-method aware registration.
    • Enhanced doctor with a TEE/signer diagnostic (tee_signer) and improved human-friendly output while keeping JSON output behavior.
  • Bug Fixes

    • Improved device-flow login and token polling to consistently use resolved client authentication.
    • More reliable config binding with safer writes (locking/rollback) and clearer handling for ambiguous --app-id matches.
  • Chores

    • Updated release automation to validate macOS hardware keychain signing and refined build packaging per platform.

@evandance
evandance requested a review from liangshuo-1 as a code owner July 22, 2026 07:46
@github-actions github-actions Bot added the size/XL Architecture-level or global-impact change label Jul 22, 2026
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds private-key JWT authentication with hardware-backed and external keyless signers, updates registration, device flow, config init, binding, token minting, diagnostics, and platform-specific release validation.

Changes

Authentication and signing

Layer / File(s) Summary
Registration and client-auth contracts
internal/auth/..., internal/auth/jwt/...
Registration negotiates auth methods, restore IDs, attestations, and keyless completion; OAuth flows use ClientAuth and signed assertions.
Hardware and external signers
internal/keysigner/..., internal/keylesshelper/..., internal/keylessprovider/...
Adds signer abstractions, macOS keychain and TPM backends, verified external helper execution, and OpenClaw provider resolution.
Credential integration
internal/core/..., internal/credential/...
Persists auth-method and key-reference metadata and mints tenant tokens using client assertions when configured.

Configuration and operations

Layer / File(s) Summary
Config init and restore
cmd/config/init*.go, cmd/config/*test.go
Adds --private-key-jwt and --restore, auth-method negotiation, key-reference persistence, stale-secret cleanup, and method-specific probing.
Config bind and profile merging
cmd/config/bind*.go, cmd/config/binder*.go
Threads context through binding, selects candidates by app ID and label, validates keyless results, and merges active profiles without clobbering siblings.
Doctor and release validation
cmd/doctor/*, .github/workflows/release.yml, .goreleaser.yml
Adds signer diagnostics and human-readable output, platform-specific release matrices, and a macOS keychain integration gate.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ConfigInit
  participant AppRegistration
  participant KeySigner
  participant OAuth
  ConfigInit->>KeySigner: Probe hardware signer
  ConfigInit->>AppRegistration: Request nonce and supported auth methods
  AppRegistration-->>ConfigInit: Return nonce and auth methods
  ConfigInit->>KeySigner: Sign registration attestation
  ConfigInit->>AppRegistration: Begin registration with attestation
  AppRegistration-->>ConfigInit: Return client ID and auth method
  ConfigInit->>OAuth: Mint token with client assertion
  OAuth-->>ConfigInit: Return access token
Loading

Possibly related PRs

Suggested labels: domain/base, feature

Suggested reviewers: liangshuo-1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: binding OpenClaw keyless credentials.
Description check ✅ Passed The description matches the required template and includes clear Summary, Changes, Test Plan, and Related Issues sections.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/keyless-integration

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.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@a81e5d17c544f8b4ec7b65d47b16a96f82bdf84a

🧩 Skill update

npx skills add larksuite/cli#feat/keyless-integration -y -g

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
internal/keylesshelper/helper.go (1)

195-198: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Apply the restricted environment independently of cwd.

cmd.Env = env is set only inside the cwd != "" branch. The production execute path always passes a non-empty providerCWD, so the minimal environment is applied today. But coupling the two means any caller that supplies a restricted env with an empty cwd (e.g. runCommand) silently drops it and the child inherits the full parent environment — the opposite of the intended isolation. Decouple them so the restricted env is always honored when provided.

🔒 Proposed change
 	cmd := exec.CommandContext(helperCtx, argv[0], argv[1:]...)
-	if cwd != "" {
-		cmd.Dir = cwd
-		cmd.Env = env
-	}
+	if cwd != "" {
+		cmd.Dir = cwd
+	}
+	if env != nil {
+		cmd.Env = env
+	}
🤖 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 `@internal/keylesshelper/helper.go` around lines 195 - 198, Decouple
environment assignment from the working-directory condition in the command
setup: keep cmd.Dir = cwd conditional on a non-empty cwd, but apply the provided
env independently whenever it is supplied. Update the surrounding
command-execution logic near runCommand so an empty cwd cannot cause a
restricted environment to be omitted.
cmd/config/binder.go (2)

204-207: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a shared constant for the "app_secret" literal.

AuthMethodPrivateKeyJWT has a package constant in internal/binding/types.go, but the sibling "app_secret" value used here is a bare string literal. Defining binding.AuthMethodAppSecret = "app_secret" would prevent drift between this check and any future openclaw-schema consumer.

🤖 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 `@cmd/config/binder.go` around lines 204 - 207, Define a shared
binding.AuthMethodAppSecret constant with the value "app_secret" alongside
AuthMethodPrivateKeyJWT in internal/binding/types.go, then update the AuthMethod
validation in the binder to compare against that constant instead of the bare
literal while preserving the existing supported-value behavior.

44-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale interface doc: no "signer command" is ever returned.

SourceBinder.Build's comment says it returns "the app plus any signer command needed by the workspace," but BindResult only carries AppConfig, and its own comment clarifies signer info is just the logical provider on AppConfig.KeyRef — no command/path is ever returned. Worth tightening the wording to avoid a future implementer expecting a command field.

✏️ Suggested wording
-	// Build resolves credentials and returns the app plus any signer command
-	// needed by the workspace. Must be called after ListCandidates succeeds.
+	// Build resolves credentials into the app to persist. External signer
+	// configuration, if any, is embedded as a logical provider reference on
+	// AppConfig.KeyRef. Must be called after ListCandidates succeeds.
 	Build(ctx context.Context, candidate Candidate) (*BindResult, error)
🤖 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 `@cmd/config/binder.go` around lines 44 - 46, Update the SourceBinder.Build
interface comment to remove the claim that it returns a signer command, and
describe it as resolving credentials and returning the app configuration while
signer information is represented through AppConfig.KeyRef. Keep the requirement
that Build follows ListCandidates succeeds.
internal/core/config.go (1)

286-286: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use core.SecretSourceTEE instead of the literal "tee".

  • internal/core/config.go#L286: replace app.KeyRef.Source != "tee" with app.KeyRef.Source != SecretSourceTEE.
  • cmd/config/init.go#L194: replace Source: "tee" with Source: core.SecretSourceTEE.
🤖 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 `@internal/core/config.go` at line 286, Replace the literal TEE source value
with the shared SecretSourceTEE constant in both sites: update the source
comparison in internal/core/config.go at lines 286-286, and set Source to
core.SecretSourceTEE in cmd/config/init.go at lines 194-194.
🤖 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 @.github/workflows/release.yml:
- Around line 44-55: Harden the signer-test-macos job by configuring the
actions/checkout step to disable persisted credentials and configuring
actions/setup-go to disable its default dependency caching. Update only the
checkout and setup-go steps, preserving their existing versions and Go version.

In `@cmd/config/keyless_bind_test.go`:
- Around line 109-126: The error-path test
TestConfigBindRun_OpenClawKeylessProbeFailureDoesNotWrite only checks that
configBindRun returns an error. Strengthen its assertions by extracting the
typed problem with errs.ProblemOf and validating the expected category, subtype,
and parameter, while also verifying that the original errs.NewConfigError cause
is preserved; retain the existing assertion that the config file is not written.

In `@cmd/doctor/doctor_test.go`:
- Around line 182-220: Add an adjacent test covering the external-keyless-signer
path in teeSignerCheck by configuring an app with a non-empty KeyProvider and
exercising keylessprovider.Resolve. Directly assert the resulting check status
and message for both the successful/warn and failure cases as applicable,
ensuring regressions in the cfg.KeyProvider branch and its fail/warn behavior
cause the test to fail; retain TestDoctorRun_TeeSignerWired for the
client_secret path.

In `@cmd/doctor/doctor.go`:
- Around line 164-187: Update teeSignerCheck to enter the external signer flow
whenever cfg.KeyProvider is non-empty, regardless of usesPKJWT, so client_secret
configurations produce the intended warning while private_key_jwt configurations
still fail. After keylessprovider.Resolve, validate that helper is non-nil
before calling helper.Probe, and return the existing unavailable/misconfigured
result with a repair hint when resolution yields no helper.

---

Nitpick comments:
In `@cmd/config/binder.go`:
- Around line 204-207: Define a shared binding.AuthMethodAppSecret constant with
the value "app_secret" alongside AuthMethodPrivateKeyJWT in
internal/binding/types.go, then update the AuthMethod validation in the binder
to compare against that constant instead of the bare literal while preserving
the existing supported-value behavior.
- Around line 44-46: Update the SourceBinder.Build interface comment to remove
the claim that it returns a signer command, and describe it as resolving
credentials and returning the app configuration while signer information is
represented through AppConfig.KeyRef. Keep the requirement that Build follows
ListCandidates succeeds.

In `@internal/core/config.go`:
- Line 286: Replace the literal TEE source value with the shared SecretSourceTEE
constant in both sites: update the source comparison in internal/core/config.go
at lines 286-286, and set Source to core.SecretSourceTEE in cmd/config/init.go
at lines 194-194.

In `@internal/keylesshelper/helper.go`:
- Around line 195-198: Decouple environment assignment from the
working-directory condition in the command setup: keep cmd.Dir = cwd conditional
on a non-empty cwd, but apply the provided env independently whenever it is
supplied. Update the surrounding command-execution logic near runCommand so an
empty cwd cannot cause a restricted environment to be omitted.
🪄 Autofix (Beta)

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

Run ID: 47615d2a-4434-42e7-a879-ae9765fcb856

📥 Commits

Reviewing files that changed from the base of the PR and between 54ddcf4 and d8fb531.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (62)
  • .github/workflows/release.yml
  • .goreleaser.yml
  • cmd/auth/login.go
  • cmd/auth/login_test.go
  • cmd/config/bind.go
  • cmd/config/bind_test.go
  • cmd/config/binder.go
  • cmd/config/binder_test.go
  • cmd/config/config_test.go
  • cmd/config/init.go
  • cmd/config/init_auth_method_test.go
  • cmd/config/init_interactive.go
  • cmd/config/init_probe.go
  • cmd/config/init_probe_test.go
  • cmd/config/init_test.go
  • cmd/config/keyless_bind.go
  • cmd/config/keyless_bind_test.go
  • cmd/doctor/doctor.go
  • cmd/doctor/doctor_test.go
  • go.mod
  • internal/auth/app_registration.go
  • internal/auth/app_registration_test.go
  • internal/auth/client_auth.go
  • internal/auth/client_auth_test.go
  • internal/auth/device_flow.go
  • internal/auth/device_flow_test.go
  • internal/auth/jwt/jwt.go
  • internal/auth/jwt/jwt_test.go
  • internal/auth/uat_client.go
  • internal/auth/uat_client_options_test.go
  • internal/binding/keyless_types_test.go
  • internal/binding/types.go
  • internal/core/config.go
  • internal/core/config_test.go
  • internal/core/secret.go
  • internal/core/types.go
  • internal/core/types_test.go
  • internal/credential/default_provider.go
  • internal/credential/tat_fetch.go
  • internal/credential/tat_fetch_test.go
  • internal/credential/types.go
  • internal/credential/types_test.go
  • internal/identitydiag/diagnostics.go
  • internal/keylesshelper/helper.go
  • internal/keylesshelper/helper_test.go
  • internal/keylessprovider/inspect_command_unix.go
  • internal/keylessprovider/inspect_command_windows.go
  • internal/keylessprovider/inspect_command_windows_test.go
  • internal/keylessprovider/provider.go
  • internal/keylessprovider/provider_test.go
  • internal/keylessprovider/security_unix.go
  • internal/keylessprovider/security_windows.go
  • internal/keysigner/keysigner.go
  • internal/keysigner/keysigner_test.go
  • internal/keysigner/registry.go
  • internal/keysigner/signer_keychain_darwin.go
  • internal/keysigner/signer_keychain_darwin_test.go
  • internal/keysigner/signer_sks.go
  • internal/keysigner/signer_sks_test.go
  • release_config_test.go
  • scripts/build-pkg-pr-new.sh
  • sidecar/server-multi-tenant-demo/auth_bridge.go

Comment on lines +44 to +55
signer-test-macos:
runs-on: macos-latest
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '1.23'
- name: Keychain signer round-trip (CGO-free purego FFI)
run: LARK_KEYCHAIN_IT=1 CGO_ENABLED=0 go test -tags keychain_signer -run Keychain -v ./internal/keysigner/

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Harden the new signer-test-macos job per zizmor findings.

Two supply-chain hardening gaps flagged by static analysis on the new job:

  • checkout@v4 (Line 49) doesn't set persist-credentials: false, leaving the GITHUB_TOKEN credential on disk for the duration of the job (which then runs arbitrary go test code).
  • setup-go@v5 (Line 50) enables its default caching, which is susceptible to cache-poisoning if the cache is later reused by another workflow run.
🔒️ Proposed hardening
       - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+        with:
+          persist-credentials: false
       - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
         with:
           go-version: '1.23'
+          cache: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
signer-test-macos:
runs-on: macos-latest
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '1.23'
- name: Keychain signer round-trip (CGO-free purego FFI)
run: LARK_KEYCHAIN_IT=1 CGO_ENABLED=0 go test -tags keychain_signer -run Keychain -v ./internal/keysigner/
signer-test-macos:
runs-on: macos-latest
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '1.23'
cache: false
- name: Keychain signer round-trip (CGO-free purego FFI)
run: LARK_KEYCHAIN_IT=1 CGO_ENABLED=0 go test -tags keychain_signer -run Keychain -v ./internal/keysigner/
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 49-49: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 50-50: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

🤖 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 @.github/workflows/release.yml around lines 44 - 55, Harden the
signer-test-macos job by configuring the actions/checkout step to disable
persisted credentials and configuring actions/setup-go to disable its default
dependency caching. Update only the checkout and setup-go steps, preserving
their existing versions and Go version.

Source: Linters/SAST tools

Comment on lines +109 to +126
func TestConfigBindRun_OpenClawKeylessProbeFailureDoesNotWrite(t *testing.T) {
saveWorkspace(t)
clearAgentEnv(t)
base := t.TempDir()
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", base)
writeOpenClawKeylessConfig(t, "cli_wrong_key", "openclaw-lark")
replaceBindProbe(t, func(context.Context, *http.Client, core.LarkBrand, string, keysigner.Signer, string, string) (string, error) {
return "", errs.NewConfigError(errs.SubtypeInvalidClient, "public key is not bound")
})

f, _, _, _ := cmdutil.TestFactory(t, nil)
if err := configBindRun(&BindOptions{Factory: f, Source: "openclaw", Identity: "bot-only"}); err == nil {
t.Fatal("expected probe error")
}
if _, err := os.Stat(filepath.Join(base, "openclaw", "config.json")); !os.IsNotExist(err) {
t.Fatalf("config must not be written; stat error = %v", err)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Assert typed error metadata instead of a bare err == nil check.

This error-path test for configBindRun (a cmd/**/*.go command) only checks err == nil; it never verifies the typed category/subtype/param via errs.ProblemOf, nor that the cause (errs.NewConfigError(...)) is preserved.

✅ Suggested strengthening
 	f, _, _, _ := cmdutil.TestFactory(t, nil)
-	if err := configBindRun(&BindOptions{Factory: f, Source: "openclaw", Identity: "bot-only"}); err == nil {
-		t.Fatal("expected probe error")
+	err := configBindRun(&BindOptions{Factory: f, Source: "openclaw", Identity: "bot-only"})
+	if err == nil {
+		t.Fatal("expected probe error")
+	}
+	problem, ok := errs.ProblemOf(err)
+	if !ok || problem.Subtype != errs.SubtypeInvalidClient {
+		t.Fatalf("problem = %#v, ok = %v, want SubtypeInvalidClient", problem, ok)
 	}

As per coding guidelines: "Command error-path tests must assert typed metadata through errs.ProblemOf, including category, subtype, and param, and must verify cause preservation rather than relying only on message substrings."

🤖 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 `@cmd/config/keyless_bind_test.go` around lines 109 - 126, The error-path test
TestConfigBindRun_OpenClawKeylessProbeFailureDoesNotWrite only checks that
configBindRun returns an error. Strengthen its assertions by extracting the
typed problem with errs.ProblemOf and validating the expected category, subtype,
and parameter, while also verifying that the original errs.NewConfigError cause
is preserved; retain the existing assertion that the config file is not written.

Source: Coding guidelines

Comment thread cmd/doctor/doctor_test.go
Comment on lines +182 to +220
// TestDoctorRun_TeeSignerWired proves the tee_signer check is part of doctorRun.
// It asserts the build-independent invariant (a client_secret app must never
// FAIL on TEE) so the test passes whether or not a signer is compiled in.
func TestDoctorRun_TeeSignerWired(t *testing.T) {
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
if err := core.SaveMultiAppConfig(&core.MultiAppConfig{
CurrentApp: "default",
Apps: []core.AppConfig{{
Name: "default", AppId: "test-app",
AppSecret: core.PlainSecret("secret"), Brand: core.BrandFeishu,
}},
}); err != nil {
t.Fatalf("SaveMultiAppConfig() error = %v", err)
}
f, stdout, _, _ := cmdutil.TestFactory(t, &core.CliConfig{
AppID: "test-app", AppSecret: "secret", Brand: core.BrandFeishu,
})
if err := doctorRun(&DoctorOptions{Factory: f, Ctx: context.Background(), Offline: true}); err != nil {
t.Fatalf("doctorRun() error = %v", err)
}
var got struct {
Checks []checkResult `json:"checks"`
}
if err := json.Unmarshal(stdout.Bytes(), &got); err != nil {
t.Fatalf("json.Unmarshal() error = %v", err)
}
var c *checkResult
for i := range got.Checks {
if got.Checks[i].Name == "tee_signer" {
c = &got.Checks[i]
}
}
if c == nil {
t.Fatalf("tee_signer check not present in doctor output: %#v", got.Checks)
}
if c.Status == "fail" {
t.Errorf("tee_signer = fail for a client_secret app; want skip/warn/pass (msg=%q)", c.Message)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

No test exercises the external-keyless-signer branch of teeSignerCheck.

TestDoctorRun_TeeSignerWired only covers a client_secret app with no KeyProvider, which never enters the cfg.KeyProvider != "" branch in teeSignerCheck (cmd/doctor/doctor.go, Lines 166-183). That branch — including the fail/warn split and the keylessprovider.Resolve call — has zero test coverage, which is how the dead-code/nil-pointer issue flagged on doctor.go went unnoticed. Per path instructions, behavior changes should have an adjacent test that would fail if the implementation regresses.

As per path instructions, "Every behavior change must include an adjacent test, and the test must directly assert the changed field or behavior so reverting the implementation causes the test to fail."

🤖 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 `@cmd/doctor/doctor_test.go` around lines 182 - 220, Add an adjacent test
covering the external-keyless-signer path in teeSignerCheck by configuring an
app with a non-empty KeyProvider and exercising keylessprovider.Resolve.
Directly assert the resulting check status and message for both the
successful/warn and failure cases as applicable, ensuring regressions in the
cfg.KeyProvider branch and its fail/warn behavior cause the test to fail; retain
TestDoctorRun_TeeSignerWired for the client_secret path.

Source: Path instructions

Comment thread cmd/doctor/doctor.go
@evandance
evandance force-pushed the feat/keyless-integration branch from d8fb531 to e1bd673 Compare July 22, 2026 11:35
@evandance
evandance force-pushed the feat/keyless-integration branch from e1bd673 to a81e5d1 Compare July 22, 2026 11:46

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
internal/core/config.go (1)

285-289: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the SecretSourceTEE constant instead of the "tee" literal.

SecretSourceTEE = "tee" is defined in secret.go and cmd/config/binder.go already writes Source: core.SecretSourceTEE. Comparing against the literal here risks silent drift if the constant value ever changes.

♻️ Suggested tweak
-		if app.KeyRef == nil || app.KeyRef.Source != "tee" || app.KeyRef.ID == "" {
+		if app.KeyRef == nil || app.KeyRef.Source != SecretSourceTEE || app.KeyRef.ID == "" {
🤖 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 `@internal/core/config.go` around lines 285 - 289, Update the validation
condition in the AuthMethodPrivateKeyJWT configuration path to compare
app.KeyRef.Source with the existing SecretSourceTEE constant instead of the
literal "tee"; preserve the current nil, ID, and error-handling checks.
🤖 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 `@internal/binding/types.go`:
- Around line 315-321: Gate the CandidateApp append in the account-candidate
path with bindableCredential(appSecret, authMethod, keyRef), so candidates
lacking both an effective secret and a private_key_jwt key reference are
skipped. Preserve the existing CandidateApp field construction and match the
credential filtering used by the implicit-default path.

In `@internal/credential/default_provider.go`:
- Around line 182-188: The tests should directly exercise DefaultTokenProvider
with AuthMethodPrivateKeyJWT and assert it selects
FetchTATWithAssertionForProvider, forwarding acct.KeyProvider and acct.KeyLabel
rather than using app-secret minting. Add an adjacent provider-selection test
that verifies the returned token and the forwarded key-selection fields.

In `@release_config_test.go`:
- Around line 37-39: Update the Darwin release matrix test around the existing
builds["darwin"] assertion to first verify that the darwin build entry exists
and is non-nil, then retain the unsupported riscv64 check. Ensure the test
directly fails when the Darwin release target is removed.

---

Nitpick comments:
In `@internal/core/config.go`:
- Around line 285-289: Update the validation condition in the
AuthMethodPrivateKeyJWT configuration path to compare app.KeyRef.Source with the
existing SecretSourceTEE constant instead of the literal "tee"; preserve the
current nil, ID, and error-handling checks.
🪄 Autofix (Beta)

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

Run ID: bafe46ed-a992-48ca-a7d0-f435afc0f764

📥 Commits

Reviewing files that changed from the base of the PR and between e1bd673 and a81e5d1.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (65)
  • .github/workflows/release.yml
  • .goreleaser.yml
  • cmd/auth/login.go
  • cmd/auth/login_test.go
  • cmd/config/bind.go
  • cmd/config/bind_test.go
  • cmd/config/binder.go
  • cmd/config/binder_test.go
  • cmd/config/config_test.go
  • cmd/config/init.go
  • cmd/config/init_auth_method_test.go
  • cmd/config/init_interactive.go
  • cmd/config/init_probe.go
  • cmd/config/init_probe_test.go
  • cmd/config/init_test.go
  • cmd/config/keyless_bind.go
  • cmd/config/keyless_bind_test.go
  • cmd/doctor/doctor.go
  • cmd/doctor/doctor_test.go
  • go.mod
  • internal/auth/app_registration.go
  • internal/auth/app_registration_test.go
  • internal/auth/client_auth.go
  • internal/auth/client_auth_test.go
  • internal/auth/device_flow.go
  • internal/auth/device_flow_test.go
  • internal/auth/jwt/jwt.go
  • internal/auth/jwt/jwt_test.go
  • internal/auth/uat_client.go
  • internal/auth/uat_client_options_test.go
  • internal/auth/uat_client_test.go
  • internal/binding/keyless_types_test.go
  • internal/binding/types.go
  • internal/core/config.go
  • internal/core/config_test.go
  • internal/core/secret.go
  • internal/core/types.go
  • internal/core/types_test.go
  • internal/credential/default_provider.go
  • internal/credential/tat_fetch.go
  • internal/credential/tat_fetch_test.go
  • internal/credential/types.go
  • internal/credential/types_test.go
  • internal/identitydiag/diagnostics.go
  • internal/keylesshelper/helper.go
  • internal/keylesshelper/helper_test.go
  • internal/keylessprovider/inspect_command_unix.go
  • internal/keylessprovider/inspect_command_windows.go
  • internal/keylessprovider/inspect_command_windows_test.go
  • internal/keylessprovider/manifest.go
  • internal/keylessprovider/manifest_test.go
  • internal/keylessprovider/provider.go
  • internal/keylessprovider/provider_test.go
  • internal/keylessprovider/security_unix.go
  • internal/keylessprovider/security_windows.go
  • internal/keysigner/keysigner.go
  • internal/keysigner/keysigner_test.go
  • internal/keysigner/registry.go
  • internal/keysigner/signer_keychain_darwin.go
  • internal/keysigner/signer_keychain_darwin_test.go
  • internal/keysigner/signer_sks.go
  • internal/keysigner/signer_sks_test.go
  • release_config_test.go
  • scripts/build-pkg-pr-new.sh
  • sidecar/server-multi-tenant-demo/auth_bridge.go
🚧 Files skipped from review as they are similar to previous changes (50)
  • internal/keylessprovider/inspect_command_windows.go
  • internal/auth/uat_client_options_test.go
  • internal/identitydiag/diagnostics.go
  • internal/core/types_test.go
  • .goreleaser.yml
  • sidecar/server-multi-tenant-demo/auth_bridge.go
  • internal/core/types.go
  • internal/credential/types_test.go
  • scripts/build-pkg-pr-new.sh
  • internal/auth/jwt/jwt.go
  • internal/keysigner/registry.go
  • internal/keysigner/signer_sks_test.go
  • cmd/config/init_probe.go
  • internal/keylesshelper/helper_test.go
  • internal/keylessprovider/inspect_command_windows_test.go
  • internal/credential/types.go
  • internal/core/config_test.go
  • internal/auth/uat_client_test.go
  • internal/auth/device_flow_test.go
  • internal/auth/uat_client.go
  • cmd/auth/login_test.go
  • internal/keylessprovider/security_unix.go
  • internal/keylessprovider/inspect_command_unix.go
  • cmd/auth/login.go
  • internal/keysigner/keysigner.go
  • cmd/config/init_auth_method_test.go
  • internal/auth/device_flow.go
  • internal/auth/app_registration.go
  • cmd/config/bind_test.go
  • cmd/config/keyless_bind.go
  • internal/auth/client_auth_test.go
  • cmd/doctor/doctor_test.go
  • cmd/config/binder_test.go
  • internal/keysigner/signer_sks.go
  • cmd/config/init_probe_test.go
  • internal/auth/app_registration_test.go
  • cmd/config/config_test.go
  • internal/auth/client_auth.go
  • cmd/config/init_test.go
  • cmd/config/init.go
  • internal/keylesshelper/helper.go
  • internal/credential/tat_fetch.go
  • internal/auth/jwt/jwt_test.go
  • cmd/doctor/doctor.go
  • cmd/config/binder.go
  • internal/keysigner/signer_keychain_darwin_test.go
  • cmd/config/keyless_bind_test.go
  • cmd/config/init_interactive.go
  • internal/keylessprovider/provider.go
  • cmd/config/bind.go

Comment thread internal/binding/types.go
Comment on lines 315 to +321
apps = append(apps, CandidateApp{
Label: label,
AppID: appID,
AppSecret: appSecret,
Brand: brand,
Label: label,
AppID: appID,
AppSecret: appSecret,
Brand: brand,
AuthMethod: authMethod,
KeyRef: keyRef,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Skip account candidates without an effective credential.

Line 315 appends an account even when its inherited secret is empty and its private_key_jwt key reference is blank. That makes an unusable candidate selectable. Gate the append with bindableCredential(appSecret, authMethod, keyRef), matching the implicit-default path.

Proposed fix
+			if !bindableCredential(appSecret, authMethod, keyRef) {
+				continue
+			}
 			apps = append(apps, CandidateApp{
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
apps = append(apps, CandidateApp{
Label: label,
AppID: appID,
AppSecret: appSecret,
Brand: brand,
Label: label,
AppID: appID,
AppSecret: appSecret,
Brand: brand,
AuthMethod: authMethod,
KeyRef: keyRef,
if !bindableCredential(appSecret, authMethod, keyRef) {
continue
}
apps = append(apps, CandidateApp{
Label: label,
AppID: appID,
AppSecret: appSecret,
Brand: brand,
AuthMethod: authMethod,
KeyRef: keyRef,
🤖 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 `@internal/binding/types.go` around lines 315 - 321, Gate the CandidateApp
append in the account-candidate path with bindableCredential(appSecret,
authMethod, keyRef), so candidates lacking both an effective secret and a
private_key_jwt key reference are skipped. Preserve the existing CandidateApp
field construction and match the credential filtering used by the
implicit-default path.

Comment on lines +182 to +188
if acct.AuthMethod == core.AuthMethodPrivateKeyJWT {
signer := keysigner.Active()
token, err := FetchTATWithAssertionForProvider(ctx, httpClient, acct.Brand, acct.AppID, signer, acct.KeyProvider, acct.KeyLabel)
if err != nil {
return nil, err
}
return &TokenResult{Token: token}, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a direct provider-selection test.

The supplied tests cover assertion minting but not that DefaultTokenProvider selects this branch and forwards KeyProvider/KeyLabel instead of using app-secret minting. A regression in this selector could remain undetected.

As per coding guidelines, “Every behavior change must include an adjacent test, and the test must directly assert the changed field or behavior.”

🤖 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 `@internal/credential/default_provider.go` around lines 182 - 188, The tests
should directly exercise DefaultTokenProvider with AuthMethodPrivateKeyJWT and
assert it selects FetchTATWithAssertionForProvider, forwarding acct.KeyProvider
and acct.KeyLabel rather than using app-secret minting. Add an adjacent
provider-selection test that verifies the returned token and the forwarded
key-selection fields.

Source: Coding guidelines

Comment thread release_config_test.go
Comment on lines +37 to +39
if contains(builds["darwin"], "riscv64") {
t.Errorf("darwin release matrix must not include unsupported riscv64; got %v", builds["darwin"])
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that the Darwin build exists.

A missing darwin build makes contains(nil, "riscv64") false, so removing that release target still passes this test.

Proposed fix
+	darwin, ok := builds["darwin"]
+	if !ok {
+		t.Fatal("darwin release build is missing")
+	}
-	if contains(builds["darwin"], "riscv64") {
-		t.Errorf("darwin release matrix must not include unsupported riscv64; got %v", builds["darwin"])
+	if contains(darwin, "riscv64") {
+		t.Errorf("darwin release matrix must not include unsupported riscv64; got %v", darwin)
 	}

As per coding guidelines, “Every behavior change must include an adjacent test, and the test must directly assert the changed field or behavior so reverting the implementation causes the test to fail.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if contains(builds["darwin"], "riscv64") {
t.Errorf("darwin release matrix must not include unsupported riscv64; got %v", builds["darwin"])
}
darwin, ok := builds["darwin"]
if !ok {
t.Fatal("darwin release build is missing")
}
if contains(darwin, "riscv64") {
t.Errorf("darwin release matrix must not include unsupported riscv64; got %v", darwin)
}
🤖 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 `@release_config_test.go` around lines 37 - 39, Update the Darwin release
matrix test around the existing builds["darwin"] assertion to first verify that
the darwin build entry exists and is non-nil, then retain the unsupported
riscv64 check. Ensure the test directly fails when the Darwin release target is
removed.

Source: Coding guidelines

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

Labels

size/XL Architecture-level or global-impact change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant