Skip to content

feat(hermes): use invoke JWT for inference - #6885

Draft
cv wants to merge 7 commits into
mainfrom
codex/salvage-pr-4627
Draft

feat(hermes): use invoke JWT for inference#6885
cv wants to merge 7 commits into
mainfrom
codex/salvage-pr-4627

Conversation

@cv

@cv cv commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Salvages the contributor work from #4627 onto an NVIDIA-owned branch after maintainer pushes to the contributor fork were denied. Hermes onboarding and the managed tool-gateway broker prefer Nous inference:invoke access JWTs for inference.local, with legacy agent-key minting retained as a fallback.

This remains a draft. Product-scope approval and the credential-boundary findings recorded on #4627 must be resolved before approval.

Changes

  • Preserve Shannon Sands as the commit author while replacing the inaccessible fork history with one signed, GitHub-verified commit on current main.
  • Request the inference:invoke and legacy agent-key scopes during Hermes OAuth device-code onboarding.
  • Register usable invoke access JWTs directly with the OpenShell hermes-provider, while retaining legacy agent-key minting for opaque, expired, or non-invoke tokens.
  • Rotate managed tool-gateway credentials through the same invoke-JWT or legacy-agent-key paths and record credential expiry.
  • Carry forward focused tests for scope requests, token classification, provider registration, fallback, and broker rotation.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification: this draft preserves the existing contribution; documentation must wait for the product-scope and credential-boundary decisions before describing canonical behavior.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: unresolved PR Review Advisor findings from feat(hermes): use invoke JWT for inference #4627 cover untrusted inference base URLs, non-verifying JWT classification, and missing negative-path fallback coverage.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Verification

  • PR description includes a Signed-off-by: line and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run check:diff passed when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — npx vitest run src/lib/oauth-device-code.test.ts src/lib/hermes-provider-auth.test.ts test/hermes-tool-gateway-broker.test.ts --maxWorkers=1 --testTimeout=60000 (14 tests passed)
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result:
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Shannon Sands shannon.sands.1979@gmail.com
Signed-off-by: Carlos Villela cvillela@nvidia.com

Summary by CodeRabbit

  • New Features
    • Hermes authentication now supports secure invoke tokens directly, with legacy agent-key authentication retained as a fallback.
    • OAuth requests support both invoke and legacy inference access scopes.
    • Authentication state now records the credential type and expiration details.
    • Invoke tokens are used directly when connecting to supported inference services.
  • Bug Fixes
    • Improved credential refresh handling, including detection of expired or soon-to-expire tokens.
    • Updated authentication flows to consistently use refreshed credentials across inference requests.

Salvaged from #4627 after the contributor branch became inaccessible to maintainer pushes.

Signed-off-by: Shannon Sands <shannon.sands.1979@gmail.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
@cv cv self-assigned this Jul 14, 2026
@copy-pr-bot

copy-pr-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 6ba731ef-28cd-4c31-bf48-7c6f055e9137

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Hermes OAuth onboarding and tool-gateway refresh flows now support Nous invoke JWT credentials alongside legacy agent keys, with JWT inspection, path-specific expiration metadata, updated provider registration, refresh handling, and corresponding test coverage.

Changes

Hermes inference credential flow

Layer / File(s) Summary
Token scopes and JWT introspection
src/lib/oauth-device-code.ts, src/lib/oauth-device-code.test.ts
OAuth requests now include invoke and legacy scopes. Exported helpers decode JWT claims, inspect scopes, derive expiry, and identify usable invoke tokens, with tests for these behaviors.
Provider credential selection
src/lib/hermes-provider-auth.ts, src/lib/hermes-provider-auth.test.ts
Provider onboarding uses an invoke JWT directly when eligible, otherwise mints a legacy agent key, and records the selected auth path and expiration fields.
Broker refresh and credential rotation
agents/hermes/host/tool-gateway-broker.ts, test/hermes-tool-gateway-broker.test.ts
Broker refresh paths use ensureInferenceCredential, persist path-specific state, propagate invoke JWT authorization, and verify that legacy agent-key requests are skipped for usable JWTs.

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

Sequence Diagram(s)

sequenceDiagram
  participant HermesToolGatewayBroker
  participant OAuthDeviceCode
  participant HermesProviderAuth
  participant OpenShellInferenceProvider
  HermesToolGatewayBroker->>OAuthDeviceCode: refresh OAuth access token
  OAuthDeviceCode-->>HermesToolGatewayBroker: return invoke JWT or opaque token
  HermesToolGatewayBroker->>HermesProviderAuth: select inference credential path
  alt invoke JWT is usable
    HermesProviderAuth->>OpenShellInferenceProvider: register JWT credential
  else legacy fallback
    HermesProviderAuth->>OpenShellInferenceProvider: register minted agent key
  end
Loading

Suggested reviewers: ericksoa

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: Hermes now prefers invoke JWTs for inference.
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
  • Commit unit tests in branch codex/salvage-pr-4627

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

@cv cv mentioned this pull request Jul 14, 2026
12 tasks
@github-code-quality

github-code-quality Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit 7170ee5 in the codex/salvage-pr-462... branch remains at 96%, unchanged from commit 6a838ff in the main branch.

TypeScript / code-coverage/cli

The overall coverage in commit 7170ee5 in the codex/salvage-pr-462... branch remains at 81%, unchanged from commit a7237ea in the main branch.

Show a code coverage summary of the most impacted files.
File main a7237ea codex/salvage-pr-462... 7170ee5 +/-
src/lib/onboard...box-prebuild.ts 92% 74% -18%
src/lib/state/sandbox.ts 85% 81% -4%
src/lib/onboard...-transaction.ts 80% 78% -2%
src/lib/policy/index.ts 59% 61% +2%
src/lib/onboard...mage-runtime.ts 48% 50% +2%
src/lib/shields/index.ts 69% 73% +4%
src/lib/onboard...-run-attempt.ts 88% 95% +7%
src/lib/onboard/docker-cdi.ts 70% 80% +10%
src/lib/oauth-device-code.ts 51% 62% +11%
src/lib/onboard...ndbox-create.ts 78% 91% +13%

Updated August 04, 2026 02:02 UTC

Signed-off-by: Carlos Villela <cvillela@nvidia.com>
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — Blocking findings reported

Advisor assessment: Blockers require maintainer review
Next action: Review the blockers below.
Findings: 1 blocker · 1 warning · 0 suggestions
Status: Partial review preserved 2 canonical finding(s) before the advisor stopped.

Model lanes

  • GPT-5.6 Terra (primary): Failed after a partial review · low confidence · 1 blocker · 1 warning · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Completed · high confidence · 2 blockers · 0 warnings · 0 suggestions

Nemotron output stays in workflow artifacts and does not change the assessment above.

E2E guidance

Advisory only. E2E / PR Gate selects and runs jobs independently.

Recommended E2E: full-e2e, hermes-e2e, security-posture

Blockers

PRA-2 Blocker — Reject untrusted inference URLs before registering the invoke JWT

  • Location: src/lib/hermes-provider-auth.ts:167
  • Category: security
  • Problem: OAuth onboarding uses tokens.inference_base_url directly when it registers an invoke JWT with OpenShell. Unlike the broker fallback, this path has no trusted-base-URL allowlist.
  • Impact: A compromised or unexpected OAuth token response can configure OpenShell to send the bearer invoke JWT to an attacker-controlled inference endpoint.
  • Fix: Apply the same trusted inference-base-URL policy used by the broker before provider registration, or ignore the response URL and use the supported default. Do not pass the invoke JWT to OpenShell with an untrusted URL.
  • Verification: Inspect src/lib/hermes-provider-auth.ts:167-190 and compare it with trustedInferenceBaseUrl in agents/hermes/host/tool-gateway-broker.ts:447-456.
  • Test coverage: Add an onboarding test whose usable invoke JWT response includes an untrusted inference_base_url and assert that provider registration uses the trusted default or rejects the response without registering the token.
  • Evidence: src/lib/hermes-provider-auth.ts:167 assigns tokens.inference_base_url to inferenceBaseUrl and line 190 passes it to registerHermesInferenceProvider for the invoke-JWT path. agents/hermes/host/tool-gateway-broker.ts:447-456 allowlists inference URLs before its provider update. src/lib/hermes-provider-auth.test.ts covers a staging URL with a direct JWT but does not reject or normalize an untrusted URL.
1 warning · 0 suggestions

Warnings

Warnings do not block.

PRA-1 Warning — Document the legacy agent-key fallback contract

  • Location: src/lib/hermes-provider-auth.ts:169
  • Category: correctness
  • Problem: OAuth onboarding falls back to minting an agent key whenever the returned access token is not a usable invoke JWT, but the changed code does not identify the external token condition that requires this compatibility path, why that source cannot be corrected here, or when the fallback can be removed.
  • Impact: The fallback can become a permanent alternate credential path if the upstream token contract changes or regresses, making it harder to distinguish an expected compatibility case from a provider defect.
  • Recommendation: Document the unsupported or transitional token condition that requires the fallback, its upstream owner, and its removal condition. Delete the fallback when the provider contract guarantees usable invoke JWTs.
  • Verification: Read src/lib/hermes-provider-auth.ts:169-190 with src/lib/hermes-provider-auth.test.ts fallback cases and the corresponding broker fallback implementation.
  • Test coverage: Keep the existing malformed, expiry, and scope fallback tests. Add a contract test or provider fixture that identifies the supported non-invoke token response requiring the fallback.
  • Evidence: src/lib/hermes-provider-auth.ts:169-190 selects legacy minting when isNousInvokeAccessToken returns false. agents/hermes/host/tool-gateway-broker.ts:477-505 independently preserves the same fallback. The available context contains no linked issue, maintainer decision, or upstream contract citation that defines the fallback condition or removal boundary.

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

@cv cv added the v0.0.84 label Jul 14, 2026
@wscurran wscurran added area: onboarding Onboarding FSM, provider setup, sandbox launch, or first-run flow area: providers Inference provider integrations and provider behavior area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery area: security Security controls, permissions, secrets, or hardening feature PR adds or expands user-visible functionality integration: hermes Hermes integration behavior needs: design Requires product or architecture direction labels Jul 14, 2026
@cv
cv requested a review from ericksoa July 14, 2026 21:13
@cv
cv marked this pull request as ready for review July 14, 2026 21:13

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/hermes-tool-gateway-broker.test.ts (1)

342-365: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Add broker-level fallback and rejection coverage.

Add a separate case where refresh returns an opaque, expired, or wrong-scope token; assert exactly one agent-key request, legacy_agent_key state, the trusted base URL, and no credential leakage in output. The current test only proves the positive invoke path.

As per path instructions, agent-boundary changes require negative-path tests proving fallback cannot bypass guards or expose credentials.

🤖 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 `@test/hermes-tool-gateway-broker.test.ts` around lines 342 - 365, Add a
separate broker-level test case alongside the existing positive invoke-path
test, configuring refresh to return an opaque, expired, or wrong-scope token and
exercising the fallback path. Assert exactly one agent-key request, persisted
legacy_agent_key state, the trusted base URL, and that broker output contains no
credentials; preserve the existing successful-path assertions.

Source: Path instructions

🧹 Nitpick comments (1)
src/lib/oauth-device-code.test.ts (1)

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

Cover the credential classifier’s rejection boundaries.

Add behavior tests for an expired token, a token inside the 120-second skew, a wrong/missing scope, and the supported scp formats. The opaque-token assertion alone will not catch regressions that route unusable JWTs into invoke_jwt.

As per path instructions, tests should provide behavioral confidence through observable credential-selection outcomes.

🤖 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 `@src/lib/oauth-device-code.test.ts` around lines 77 - 86, Add behavioral tests
in the isNousInvokeAccessToken suite covering expired tokens, tokens within the
120-second expiry skew, wrong or missing scopes, and each supported scp
representation. Assert credential-selection outcomes rather than only boolean
classification, while preserving the existing valid JWT and opaque-token cases.

Source: Path instructions

🤖 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 `@agents/hermes/host/tool-gateway-broker.ts`:
- Around line 251-306: Replace the payload-only logic in decodeJwtClaims,
jwtScopes, jwtExpiresAtIso, and isUsableInvokeJwt with one authoritative JWT
verification/classification contract that validates signature, issuer, audience,
required scope, and expiry before selecting invoke_jwt. In
src/lib/oauth-device-code.ts lines 151-201, expose and reuse this same verified
classification contract rather than maintaining separate JWT semantics; update
both sites consistently, with no direct change required elsewhere.

In `@src/lib/hermes-provider-auth.ts`:
- Around line 168-189: Validate the effective inference endpoint before calling
registerHermesInferenceProvider or persisting credentials, applying the broker’s
existing explicit allowlist and fail-closed policy. Cover
tokens.inference_base_url, minted.inference_base_url, and caller-supplied
baseUrl, and reject any untrusted or attacker-controlled URL before it can be
assigned to OPENAI_BASE_URL.

In `@src/lib/oauth-device-code.ts`:
- Around line 184-190: Guard the Date conversion in jwtExpiresAt in
src/lib/oauth-device-code.ts at lines 184-190 by validating the constructed
date’s time value before calling toISOString(), returning null when invalid.
Apply the same invalid-Date guard in agents/hermes/host/tool-gateway-broker.ts
at lines 291-296 so malformed expiration data cannot abort broker rotation.

---

Outside diff comments:
In `@test/hermes-tool-gateway-broker.test.ts`:
- Around line 342-365: Add a separate broker-level test case alongside the
existing positive invoke-path test, configuring refresh to return an opaque,
expired, or wrong-scope token and exercising the fallback path. Assert exactly
one agent-key request, persisted legacy_agent_key state, the trusted base URL,
and that broker output contains no credentials; preserve the existing
successful-path assertions.

---

Nitpick comments:
In `@src/lib/oauth-device-code.test.ts`:
- Around line 77-86: Add behavioral tests in the isNousInvokeAccessToken suite
covering expired tokens, tokens within the 120-second expiry skew, wrong or
missing scopes, and each supported scp representation. Assert
credential-selection outcomes rather than only boolean classification, while
preserving the existing valid JWT and opaque-token cases.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 05c5c6d9-34f8-4b33-9e8c-7a385d51b8ac

📥 Commits

Reviewing files that changed from the base of the PR and between ea143b8 and 0b9098d.

📒 Files selected for processing (6)
  • agents/hermes/host/tool-gateway-broker.ts
  • src/lib/hermes-provider-auth.test.ts
  • src/lib/hermes-provider-auth.ts
  • src/lib/oauth-device-code.test.ts
  • src/lib/oauth-device-code.ts
  • test/hermes-tool-gateway-broker.test.ts

Comment on lines +251 to +306
function decodeBase64UrlJson(value) {
try {
const base64 = String(value || "")
.replace(/-/g, "+")
.replace(/_/g, "/");
const parsed = JSON.parse(Buffer.from(base64, "base64").toString("utf8"));
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
} catch {
return null;
}
}

function decodeJwtClaims(token) {
if (typeof token !== "string") return null;
const parts = token.split(".");
if (parts.length < 2) return null;
return decodeBase64UrlJson(parts[1]);
}

function jwtScopes(claims) {
const scopes = new Set();
const scope = claims?.scope;
if (typeof scope === "string") {
for (const value of scope.split(/\s+/)) {
if (value) scopes.add(value);
}
}
const scp = claims?.scp;
if (typeof scp === "string") {
for (const value of scp.split(/\s+/)) {
if (value) scopes.add(value);
}
} else if (Array.isArray(scp)) {
for (const value of scp) {
if (typeof value === "string" && value) scopes.add(value);
}
}
return scopes;
}

function jwtExpiresAtIso(token) {
const exp = decodeJwtClaims(token)?.exp;
const seconds =
typeof exp === "number" ? exp : typeof exp === "string" && exp.trim() ? Number(exp) : NaN;
if (!Number.isFinite(seconds)) return null;
return new Date(seconds * 1000).toISOString();
}

function isUsableInvokeJwt(token) {
const claims = decodeJwtClaims(token);
if (!claims || !jwtScopes(claims).has(NOUS_INFERENCE_INVOKE_SCOPE)) return false;
const expiresAt = jwtExpiresAtIso(token);
return Boolean(
expiresAt && !timestampExpiresSoon(expiresAt, INFERENCE_CREDENTIAL_REFRESH_SKEW_MS),
);
}

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 | 🏗️ Heavy lift

Replace payload-only JWT classification with one authoritative verifier. Both implementations trust unsigned claims to select the direct credential path.

  • agents/hermes/host/tool-gateway-broker.ts#L251-L306: verify signature, issuer, audience, scope, and expiry before selecting invoke_jwt.
  • src/lib/oauth-device-code.ts#L151-L201: expose and reuse the same verified classification contract rather than maintaining separate semantics.
📍 Affects 2 files
  • agents/hermes/host/tool-gateway-broker.ts#L251-L306 (this comment)
  • src/lib/oauth-device-code.ts#L151-L201
🤖 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 `@agents/hermes/host/tool-gateway-broker.ts` around lines 251 - 306, Replace
the payload-only logic in decodeJwtClaims, jwtScopes, jwtExpiresAtIso, and
isUsableInvokeJwt with one authoritative JWT verification/classification
contract that validates signature, issuer, audience, required scope, and expiry
before selecting invoke_jwt. In src/lib/oauth-device-code.ts lines 151-201,
expose and reuse this same verified classification contract rather than
maintaining separate JWT semantics; update both sites consistently, with no
direct change required elsewhere.

Source: Path instructions

Comment on lines +168 to +189
const inferenceBaseUrl = tokens.inference_base_url || baseUrl;
const useInvokeJwt = oauth.isNousInvokeAccessToken(tokens.access_token);
const minted = useInvokeJwt
? null
: await oauth.mintAgentKeyWithAccessToken(tokens.access_token, {
fetch,
minTtlSeconds: AGENT_KEY_MIN_TTL_SECONDS,
});
const credential = useInvokeJwt ? tokens.access_token : minted?.api_key;
if (!credential) {
throw new Error("Hermes Provider credential is empty");
}
const credentialExpiresAt = useInvokeJwt
? oauth.jwtExpiresAt(tokens.access_token)
: minted
? agentKeyExpiresAt(minted)
: null;
registerHermesInferenceProvider(
minted.api_key,
credential,
runOpenshell,
HERMES_INFERENCE_CREDENTIAL_ENV,
inferenceBaseUrl,
minted?.inference_base_url || inferenceBaseUrl,

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 | 🔴 Critical | 🏗️ Heavy lift

Validate the inference endpoint before registering the credential.

tokens.inference_base_url, minted.inference_base_url, and caller-supplied baseUrl flow directly into OPENAI_BASE_URL. An untrusted value can make OpenShell send the invoke JWT or agent key to an attacker-controlled host. Apply the same explicit allowlist/fail-closed policy used by the broker before registration and persistence.

This matches the PR’s listed untrusted inference-base-URL credential-boundary blocker.

Also applies to: 206-209

🤖 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 `@src/lib/hermes-provider-auth.ts` around lines 168 - 189, Validate the
effective inference endpoint before calling registerHermesInferenceProvider or
persisting credentials, applying the broker’s existing explicit allowlist and
fail-closed policy. Cover tokens.inference_base_url, minted.inference_base_url,
and caller-supplied baseUrl, and reject any untrusted or attacker-controlled URL
before it can be assigned to OPENAI_BASE_URL.

Comment thread src/lib/oauth-device-code.ts Outdated
Comment on lines +184 to +190
export function jwtExpiresAt(token: unknown): string | null {
const claims = decodeJwtClaims(token);
const exp = claims?.exp;
const seconds =
typeof exp === "number" ? exp : typeof exp === "string" && exp.trim() ? Number(exp) : NaN;
if (!Number.isFinite(seconds)) return null;
return new Date(seconds * 1000).toISOString();

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard JWT expiration conversion against invalid Date ranges.

  • src/lib/oauth-device-code.ts#L184-L190: return null when the constructed date has an invalid time value.
  • agents/hermes/host/tool-gateway-broker.ts#L291-L296: apply the same guard so malformed OAuth data cannot abort broker rotation.
📍 Affects 2 files
  • src/lib/oauth-device-code.ts#L184-L190 (this comment)
  • agents/hermes/host/tool-gateway-broker.ts#L291-L296
🤖 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 `@src/lib/oauth-device-code.ts` around lines 184 - 190, Guard the Date
conversion in jwtExpiresAt in src/lib/oauth-device-code.ts at lines 184-190 by
validating the constructed date’s time value before calling toISOString(),
returning null when invalid. Apply the same invalid-Date guard in
agents/hermes/host/tool-gateway-broker.ts at lines 291-296 so malformed
expiration data cannot abort broker rotation.

@cv
cv marked this pull request as draft July 14, 2026 21:30
@cv

cv commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

Salvage follow-up: normal CI and the selected live E2E lanes now pass on the unchanged head after one bounded rerun of transient live-runner failures.

This PR remains draft. The CodeRabbit findings confirm the existing approval blockers:

  • define and implement an authoritative JWT verification contract before selecting the invoke credential path;
  • validate every effective inference endpoint against a fail-closed allowlist before registering credentials;
  • add malformed-expiration guards and negative fallback/rejection coverage once that contract is selected;
  • obtain the required product/design decision for this supported auth surface.

No security-design changes were made during salvage. Documentation review found no update needed for the test-only salvage follow-up.

@cv cv added v0.0.85 and removed v0.0.84 labels Jul 15, 2026
@NVIDIA NVIDIA deleted a comment from github-actions Bot Jul 16, 2026
@cjagwani cjagwani added v0.0.86 and removed v0.0.85 labels Jul 16, 2026
@apurvvkumaria apurvvkumaria self-assigned this Jul 17, 2026

@apurvvkumaria apurvvkumaria left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Exact-head security review for 0b9098db3eb01d9b3e3261d63d82ac7fca65c22b: changes requested. Please keep this draft blocked until the authentication and credential-lifecycle design is defined and implemented.

The current invoke_jwt selection decodes claims without verifying the JWT signature, algorithm, issuer, audience, key ID/rotation, or nbf; the tests even use alg: none. Separately, inference_base_url returned by the token or agent-key response can override the caller's validated endpoint before the credential is registered, which can redirect a credential to an attacker-controlled endpoint. A direct invoke JWT also has no refresh owner when managed tools are absent, so the configured provider becomes unrecoverable after expiry. Malformed but finite exp values can additionally throw during toISOString().

Before implementation proceeds, please define one authoritative verification contract: trusted issuer/audience/algorithms/JWKS and outage behavior; endpoint allowlist and precedence for every URL source; refresh ownership without managed tools; least-privilege scope; fail-closed versus legacy fallback behavior; and state migration/rollback. Then reuse that contract in onboarding and the broker, and add negative tests for signature/claims/key rotation, endpoint injection, malformed expiry, no-tools refresh, fallback, concurrency, crash recovery, migration, and redaction.

CI being green does not cover these trust-boundary failures; three related automated-review threads are also still unresolved.

@wscurran wscurran added v0.0.88 and removed v0.0.86 labels Jul 17, 2026
@cv

cv commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

v0.0.88 disposition: deferred while this remains a draft and the authentication design is incomplete. Approval requires an authoritative JWT verification and lifecycle contract covering issuer, audience, JWKS trust, endpoint validation, expiration/clock behavior, and refresh ownership. This is a security-design dependency, not a missing-test disposition.

apurvvkumaria and others added 2 commits July 20, 2026 11:45
Co-authored-by: Shannon Sands <shannon.sands.1979@gmail.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Comment thread test/hermes-tool-gateway-broker.test.ts Fixed
Co-authored-by: Shannon Sands <shannon.sands.1979@gmail.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
@jyaunches jyaunches added v0.0.91 and removed v0.0.90 labels Jul 20, 2026
@apurvvkumaria apurvvkumaria removed their assignment Jul 24, 2026
@copy-pr-bot

copy-pr-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@cv cv added v0.0.102 and removed needs: design Requires product or architecture direction labels Aug 4, 2026
@cv

cv commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

@ericksoa, could you review this draft and provide the product-scope and authentication decisions needed before implementation continues?

The current reviews block direct inference:invoke JWT registration because the supported trust and lifecycle contract is undefined. Please confirm either that this surface is approved with the contract below, or that the PR should be descoped to the existing legacy agent-key path:

  • trusted JWT issuer, audience, algorithms, and JWKS source;
  • key rotation and JWKS outage behavior;
  • allowed inference endpoints and precedence for configured, token-returned, and minted URLs;
  • refresh ownership when managed tools are absent;
  • expiration, clock-skew, and nbf handling;
  • when legacy agent-key fallback is allowed and its removal condition;
  • migration, rollback, redaction, and required live validation.

The unresolved security findings are the payload-only JWT classification and untrusted inference endpoint registration. We are holding code changes until an accepted decision defines this supported credential surface.

@ericksoa

ericksoa commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

I approve direct inference:invoke authentication as a supported Hermes surface, but its lifecycle should be implemented as an OpenShell refresh-backed provider rather than as JWT classification and refresh logic inside NemoClaw.

This should follow the OpenShell Providers v2 model used for refresh-backed providers such as Microsoft Graph/Entra:

  • NemoClaw owns the interactive device-code bootstrap and configures the provider.
  • OpenShell owns non-injectable refresh material, access-token rotation, expiry, persistence, concurrency, restart recovery, redaction, and sandbox placeholder resolution.
  • The Nous inference service owns JWT signature, issuer, audience, algorithm, nbf, and exp validation. NemoClaw should treat the access token as opaque and must not branch on decoded JWT claims.
  • The production provider profile pins the Portal token endpoint, hermes-cli client ID, inference:invoke scope, refresh timing, and exactly https://inference-api.nousresearch.com/v1. Token-returned or minted URLs must not override that profile.
  • Do not preserve automatic legacy agent-key fallback without a concrete currently supported consumer and explicit removal condition. The bundled Hermes version is already invoke-JWT-only.

One compatibility question must be resolved before implementation: OpenShell's generic oauth2_refresh_token provider currently sends refresh_token in the form body, while the current Nous/Hermes client sends it via x-nous-refresh-token. Please first confirm whether the Nous token endpoint accepts the standard form-body refresh token. If it does, this can largely be expressed as an OpenShell provider profile. If it does not, define the required OpenShell provider-refresh extension before continuing the NemoClaw implementation.

Please revise this PR around that provider boundary. Live validation should cover inference-only use across multiple token lifetimes, managed-tools use, gateway/host restart, refresh-token rotation and concurrency, endpoint injection rejection, terminal reauthentication, rollback, and confirmation that no access or refresh token appears in logs or persisted NemoClaw state.

@github-actions github-actions Bot added v0.0.103 and removed v0.0.102 labels Aug 4, 2026
@cjagwani cjagwani added v0.0.104 Release target and removed v0.0.103 labels Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: onboarding Onboarding FSM, provider setup, sandbox launch, or first-run flow area: providers Inference provider integrations and provider behavior area: sandbox OpenShell sandbox lifecycle, runtime, config, or recovery area: security Security controls, permissions, secrets, or hardening feature PR adds or expands user-visible functionality integration: hermes Hermes integration behavior v0.0.104 Release target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants