Skip to content

Enable agent-native Privy login and split human/agent auth flows#328

Merged
benjaminshafii merged 3 commits into
mainfrom
feat/privy-agent-native-auth
Feb 23, 2026
Merged

Enable agent-native Privy login and split human/agent auth flows#328
benjaminshafii merged 3 commits into
mainfrom
feat/privy-agent-native-auth

Conversation

@benjaminshafii

@benjaminshafii benjaminshafii commented Feb 23, 2026

Copy link
Copy Markdown
Member

Summary

  • add a new admin-gated POST /api/cli/agent-login API that provisions/reuses Privy users, issues workspace API keys, returns KYB context, and attempts starter-account setup when possible
  • add a new CLI-first zero auth agentlogin flow (plus zero agentlogin) and harden browser connect so non-interactive sessions fail fast instead of hanging forever
  • split web entrypoints into explicit human and agent paths (Login -> /signin, Agent Login -> /agent-login) and replace landing/onboarding links that previously sent users to /cli/connect

Why

  • agents were getting stuck in browser-only auth loops; they need a deterministic API-native provisioning/login path
  • humans should have a clean login button and should not be routed through CLI-connect pages just to access the app
  • KYB-aware responses make agent orchestration explicit (kyb.status, flow link, starter account state)

Detailed Behavior

  • New endpoint: POST /api/cli/agent-login
    • requires x-admin-token
    • accepts email / phone / privy_user_id plus optional workspace/entity metadata
    • returns:
      • api_key, api_key_id
      • workspace_id, workspace_name
      • privy_user_id
      • kyb object (status, sub_status, flow_link, marked_done)
      • starter_accounts object (attempted, created, skipped_reason, destination_address)
  • CLI:
    • zero auth agentlogin ... and zero agentlogin ... now wrap that endpoint and persist config automatically
    • zero auth connect --manual --no-browser now exits with clear guidance in non-interactive sessions (no infinite wait/prompt hang)
  • UI:
    • header: Login + Agent Login
    • new /agent-login page with API + CLI examples and KYB semantics
    • onboarding/sign-in pages now expose the agent-native path directly

Testing

  • pnpm --filter @zero-finance/web typecheck
  • pnpm --filter agent-bank build
  • node packages/cli/dist/index.js auth agentlogin --help
  • node packages/cli/dist/index.js agentlogin --help
  • node packages/cli/dist/index.js auth connect --manual --no-browser
    • confirms non-interactive flow exits with actionable error instead of hanging
  • POST http://127.0.0.1:3000/api/cli/agent-login without admin token returns 401 with {"error":"Unauthorized: invalid admin token"}

Screenshots / Evidence

  • packages/web/test-artifacts/privy-agent-native-login/landing-login-agent-login.png
  • packages/web/test-artifacts/privy-agent-native-login/agent-login-page.png
  • packages/web/test-artifacts/privy-agent-native-login/signin-with-agent-login-link.png
  • detailed test + behavior report: packages/web/test-artifacts/privy-agent-native-login/REPORT.md

Landing

Landing login split

Agent Login Page

Agent login page

Sign-in Agent Link

Sign-in agent link


Note

High Risk
Adds an admin-gated provisioning endpoint that creates Privy identities and issues workspace API keys, plus changes workspace bootstrapping; mistakes could create unauthorized access or broken onboarding. Also touches CLI auth behavior in non-interactive environments.

Overview
Introduces an agent-native authentication/provisioning flow via POST /api/cli/agent-login (admin-token required) that can create/reuse a Privy user, ensure a workspace/profile, mint a workspace API key, optionally provision a default Ethereum wallet, attempt starter virtual accounts, and return KYB + provisioning metadata.

Updates the CLI to add zero auth agentlogin (and top-level zero agentlogin) to call the new endpoint and persist the returned API key, and hardens browser-based connect to fail fast in non-interactive sessions instead of hanging. The web UI now splits entrypoints into human Login (/signin) vs Agent Login (/agent-login), adds a new /agent-login page with examples, and updates landing/onboarding CTAs accordingly; ensureUserWorkspace is adjusted to bootstrap users + workspaces atomically via SQL to support API-only onboarding.

Written by Cursor Bugbot for commit 24f1df2. This will update automatically on new commits. Configure here.

Add an API-first agentlogin path that provisions Privy users, returns KYB context, and avoids non-interactive CLI hangs while keeping human login straightforward via /signin.
@vercel

vercel Bot commented Feb 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
we-love-your-startup Ready Ready Preview, Comment Feb 23, 2026 5:10pm
zerofinance Ready Ready Preview, Comment Feb 23, 2026 5:10pm

Comment thread packages/cli/src/index.ts
walletsJson: opts.walletsJson,
adminToken: opts.adminToken,
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicated agent login action handler across two registrations

Low Severity

configureAgentLoginCommand omits the .action() handler, unlike configureBrowserConnectCommand which includes it. This forces the 14-property opts-to-runAgentLogin mapping to be duplicated verbatim at both auth agentlogin and top-level agentlogin registration sites. Adding or renaming an option requires updating both copies, which is fragile and inconsistent with the browser-connect pattern.

Additional Locations (1)

Fix in Cursor Fix in Web

Use a single CTE insert for user/workspace bootstrap so circular foreign keys no longer block brand-new agentlogin runs, and document passing E2E evidence in the report.

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

Cursor Bugbot has reviewed your changes and found 5 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

RETURNING id
)
SELECT 1;
`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CTE unconditionally creates orphaned workspaces on user conflict

High Severity

The bootstrapUserWorkspace CTE always executes both the user INSERT and the workspace INSERT independently. When the user already exists (due to a race condition), the user INSERT triggers ON CONFLICT DO NOTHING and produces no rows, but the inserted_workspace CTE still creates a new workspace with the random UUID. This workspace is never linked to any user's primary_workspace_id, becoming an orphaned record. The old code avoided this by checking insertedUsers.length === 0 and skipping workspace creation when the user already existed. The workspace INSERT needs to be conditional on the user INSERT succeeding (e.g., using SELECT ... FROM inserted_user instead of VALUES).

Fix in Cursor Fix in Web

chainType === undefined
) {
return address;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Linked account extraction matches accounts without chain type

Low Severity

extractAddressFromLinkedAccounts treats any linked account entry with a valid Ethereum address as a match when chainType is undefined. The condition chainType === undefined means any account record lacking both chain_type and chainType fields qualifies, potentially matching non-wallet account types that happen to have an address field resembling an Ethereum address. The type === 'wallet' check alone would be more precise.

Fix in Cursor Fix in Web

name: keyName,
createdBy: privyDid,
expiresAt: expiresAtInput ? new Date(expiresAtInput) : undefined,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Agent login creates duplicate API key every call

Medium Severity

The agent-login endpoint calls createApiKey unconditionally on every request without checking for existing keys. Repeated calls for the same user/workspace create unbounded duplicate API keys. Since the endpoint is designed for agent orchestration (which may retry or re-provision), this can lead to significant key proliferation in the api_keys table with no cleanup mechanism.

Fix in Cursor Fix in Web

}

return value;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thrown error causes 500 instead of 400 response

Medium Severity

parseBeneficiaryType throws an Error on invalid input, but the route handler has no try-catch. This causes a raw 500 Internal Server Error instead of a 400 with a clear message. Every other validation in the same agent-login handler (e.g., line 1500) uses return errorResponse(...) to send a proper 400 response. The thrown error bypasses that pattern and loses the helpful error message for API callers.

Additional Locations (1)

Fix in Cursor Fix in Web

if (!starterAccountsCreated) {
starterAccountsSkippedReason =
'Starter account provisioning unavailable';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Uncaught starter-account error loses created API key

High Severity

createStarterVirtualAccounts is awaited without a try-catch, but it runs after createApiKey has already persisted a new key. If the external Align service call throws (timeout, network error, etc.), the entire request fails with a 500, and the already-created API key is never returned to the caller. The other call site in register-primary/route.ts intentionally calls this function fire-and-forget (no await), confirming it can fail. The handler's own design treats starter creation as optional (multiple skip reasons, skipped_reason response field), so an exception here contradicts that intent and causes an active credential to leak.

Fix in Cursor Fix in Web

Ensure agent-native login creates or resolves a Privy wallet by default and add a security report comparing pure agent flows with human interactive auth.
@benjaminshafii

Copy link
Copy Markdown
Member Author

Correction to previous comment (shell escaped my markdown). Follow-up is pushed in commit 24f1df2.\n\n- agentlogin now provisions a wallet by default (Ethereum) when wallets are not specified\n- existing Privy users are fetched; if no wallet address is present, wallet pre-generation is attempted automatically\n- response now includes wallet details: address, requested, provisioned, provisioning_error\n\nSecurity report added at: packages/web/test-artifacts/privy-agent-native-login/SECURITY-REPORT.md\nImplementation/E2E report updated at: packages/web/test-artifacts/privy-agent-native-login/REPORT.md\n\nLatest E2E run without --wallets-json returned a non-null wallet address and matching starter destination address.

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.

1 participant