CNothing v4
CNothing currently provides OAuth API delegation, not browser login to arbitrary websites. Normally request access with only provider (from list_providers) and reason; the user chooses their connected account on the approval page. user_id is optional CNothing approval routing, not a website account selector. When the user provides their login account, pass user_id: "google:[email protected]" (general form provider:account) to route approval to their paired phone. CNothing resolves the stored login identity; do not invent an account. Keep the same approval request while waiting.
CNothing is authentication infrastructure for AI agents. A user connects an OAuth provider and approves an Agent grant; CNothing then injects the provider credential at the HTTP proxy boundary. The Agent never receives passwords, access tokens, refresh tokens, cookies, or provider client secrets.
A Grant is the current representation of a Mandate: delegated authority from a principal (today, a User) to an agent, constrained to hosts, methods, and an expiry. Agents keep calling list_grants and proxy_request; they do not need to know the Mandate name.
An AccessRequest is the current representation of an ApprovalRequest. Delegation mints a Mandate. When a Mandate opts into constraints.require_approval — from the Console approval checkbox, the Grants page toggle, or the approve API — a side-effecting proxy_request (for example creating a GitHub issue) becomes a Transaction Intent: the agent still calls proxy_request, receives approval_required with an approval_url, and retries the same call after the principal approves. GET traffic is never gated. Tokens never leave CNothing.
Identity
CNothing has three principals. Their credentials are not interchangeable.
| Principal | Credential | Authorization |
|---|---|---|
| Human | Browser/iOS session after /login (GitHub / OIDC / configured IdP) | role=user or role=admin on the server-side user row |
| Agent | One-time agent token (agent_…) | Mandate / Grant / Policy / Approval / Transaction Intent |
| Service | KEYSERVICE_BEARER_TOKEN (alias CN_SERVICE_TOKEN) | Bootstrap, recovery, trusted automation |
Admins and ordinary users share the same /login. There is no /admin/login and no second password system. Console navigation hides admin pages for role=user; every admin mutation is still enforced by requireAdmin() on the server.
The service bearer token is not a daily human admin login. After a fresh deploy, sign in normally, then promote the first admin:
curl -X POST "$CN_URL/v4/admin/bootstrap" \
-H "Authorization: Bearer $KEYSERVICE_BEARER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"user_id":"github:your-login"}'
Bootstrap is refused once any admin exists. Later promote/demote calls require a Human admin session (POST /v4/admin/users/promote and /demote). The last remaining admin cannot be demoted.
Production upgrade from Admin Bearer Token
- Deploy this version and run
bun run migrate(007_user_roles.sqladdscap_users.role, defaultuser). - Sign in at
/loginwith the existing identity provider. Existing sessions keep working. - Call
POST /v4/admin/bootstrapwith the service token and that user'suser_id(from/v4/auth/me). - Reload the Console; Agents and Providers appear only for
role=admin. - Stop using the old Console Admin Token field. The Console no longer stores or sends it.
Supported workflow
- The host plugin either already has an agent token, or it calls
POST /v4/agent-enrollmentsand the user approves the runtime at/approve-agent/{id}. The token is stored in the host secret store, never in the model context. Spec:https://cnothing.com/plugin.md. - The user signs in at
/loginand connects a provider at/connect. - The Agent calls
list_grantsand reuses a matching active grant when possible. - Otherwise the Agent calls
list_providers, thenrequest_access. - CNothing sends a push to a paired iOS device when the user identity is known. The returned
approval_urlis always the fallback. - The Agent checks
get_access_status, then calls the third-party API withproxy_request. A write whose mandate setrequire_approvalreturnsapproval_required; after the user approves, the agent retries the sameproxy_request.
Provider registry
cap_oauth_providers is the single provider entity. One registry entry covers both roles a provider can play: the API an agent connects to through the credential proxy, and — when login_enabled is set — the identity provider a user signs in to the Console with. Rows record source (manual | discovered | imported) and a stored status of active | unconfigured | disabled. The Console maps those onto DISCOVERED / UNVERIFIED / REVIEWED / ACTIVE / DISABLED. Discovered metadata is untrusted and is fetched through the same SSRF checks as the proxy. Provider client secrets live in the Vault, never in the provider row.
Hosted MCP: https://cnothing.com/mcp
Agent skill: https://cnothing.com/skill.md
Plugin contract: https://cnothing.com/plugin.md
OpenAPI: https://cnothing.com/openapi.json
Components
src/v4: Agent identity and user-approved enrollment, Human users and roles, user sessions, the provider registry and connections, encrypted vault, access grants, transaction intents, credential-injecting proxy, APNs, device pairing, signed device approvals, and share codes.src/mcp: hosted MCP transport and tool execution.agenttools: host-side Agent SDK (cnothing-agent), stdio MCP, and DeepSeek Harness plugin. Enrollment and the agent token stay in the host process.packages/cnothing-mcp: compatibility entry that startsagenttools/mcp.console: human sign-in, provider connection, role-aware Agent/provider administration, approval, grant, and device management.iOS: CNothing authenticator app with account pairing, APNs, and Secure Enclave approval signatures.
Local development
bun install
bun run generate-secrets
# Copy .local-keys/generated.env into your environment, then set DATABASE_URL.
bun run migrate
bun run dev
For an upgrade, first let V4 migrate OAuth credentials and verify a database backup. After the migration state is complete, an operator may explicitly run migrations/manual/cleanup_pre_v4.sql; normal migrations never execute this destructive cleanup.
Run verification:
bun test
bun run typecheck
bun run console:typecheck
The integration suites need Postgres. They create and migrate their own database, cnothing_test by default; override with TEST_DATABASE_URL. Without a reachable Postgres they are skipped, so set CNOTHING_REQUIRE_DB_TESTS=1 in CI to make an unavailable database a failure instead.
Production OAuth callback URLs must use the exact v4 paths documented in openapi-v4.json. GitHub Console login also accepts the historically registered https://cnothing.com/v2/auth/github/callback; set KEYSERVICE_GITHUB_OAUTH_REDIRECT_URI to the Authorization callback URL on the GitHub OAuth App.
