npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ciba-cli

v2.3.4

Published

CIBA-like backchannel auth CLI with Hocuspocus token delivery

Readme

CIBA Auth Flow — Usage & Security

Install

npm i -g ciba-cli

Quick Start

# Start background session (opens browser once for login)
ciba start --url <server-url>

# Call an API (no browser, reads token from Yjs via keychain)
curl -H "Authorization: Bearer $(ciba token)" https://api.example.com/endpoint

# Call again — instant, no re-auth
curl -X POST \
  -H "Authorization: Bearer $(ciba token)" \
  -H "Content-Type: application/json" \
  -d '{"message":"Hello","contextId":"ctx-1"}' \
  https://api.example.com/agents/my-agent

# Done
ciba stop

One-Shot Mode

curl -H "Authorization: Bearer $(ciba --url <server-url>)" https://api.example.com/endpoint

Commands

| Command | Description | |---------|-------------| | ciba start --url <server> | Authenticate, detach, save session | | ciba token | Read token (connects to Yjs, decrypts with keychain) | | ciba token --resource <urn> | Read token for a specific resource (auto-exchanges if missing) | | ciba add --resource <urn> | Pre-fetch an additional resource token | | ciba status | Check if session is active | | ciba stop | Kill background process, clear keychain | | ciba --url <server> | One-shot: authenticate, print, exit |

Security Model

Layers of Protection

┌─────────────────────────────────────────────────────────────────┐
│ Layer 1: Transport — only proof-of-possession (PoP) connects    │
│                                                                 │
│   CLI connects to /sync/device via Hocuspocus WebSocket.        │
│   Auth: onAuthenticate verifies SHA-256(challenge) === verifier  │
│   Without the challenge, you can't connect to the device doc.   │
│   → Eavesdroppers on local network can't even see the doc.      │
├─────────────────────────────────────────────────────────────────┤
│ Layer 2: Encryption — token encrypted at rest in device doc     │
│                                                                 │
│   Server encrypts the token with AES-256-GCM keyed by the      │
│   challenge before writing to the Yjs doc.                      │
│   Even if someone accessed the raw Yjs data, they can't decrypt │
│   without the challenge.                                        │
├─────────────────────────────────────────────────────────────────┤
│ Layer 3: Keychain — challenge protected by OS                   │
│                                                                 │
│   The challenge (decryption key) is stored in:                  │
│   • macOS: Keychain (security add-generic-password)             │
│   • Linux: GNOME Keyring (secret-tool)                          │
│                                                                 │
│   Protected by the OS login session. Only the authenticated     │
│   user on this machine can read it. Not exportable cross-machine│
│   Other processes on the same machine CAN read it if they run   │
│   as the same user (same as any keychain-stored credential).    │
├─────────────────────────────────────────────────────────────────┤
│ Layer 4: Server-side — challenge never in shared/global state   │
│                                                                 │
│   Global state only stores the verifier = SHA-256(challenge).   │
│   Can't reverse to get challenge.                               │
│   The challenge is written encrypted (server-only key) into     │
│   the ephemeral device doc during connection setup.             │
└─────────────────────────────────────────────────────────────────┘

What's stored where

| Secret | Storage | Who can read | |--------|---------|-------------| | Challenge (decryption key) | OS Keychain | Same user, same machine | | Verifier (SHA-256 of challenge) | Global Yjs doc | Anyone with access (safe — can't reverse) | | Encrypted challenge (server key) | Device doc _server map | Server only (AES-256-GCM with server salt) | | Encrypted token | Device doc tokens map | Anyone who connects (but useless without challenge) | | Token (plaintext) | Never stored | Only in memory after decrypt |

Connection security

  • Only a client that knows the challenge can connect to the device doc (Hocuspocus onAuthenticate — proof of possession)
  • The device doc is ephemeral (in-memory, no persistence) — gone when the server restarts
  • ciba start keeps the Hocuspocus connection alive (holds the doc in memory)
  • ciba token re-connects using the challenge from keychain — same PoP auth required
  • On ciba stop: keychain entries deleted, background process killed, doc expires

Threat model

| Threat | Mitigation | |--------|-----------| | Network eavesdropper | WSS (TLS) + can't connect without challenge (PoP) | | Local process snooping Yjs | Token is encrypted — useless without challenge | | Stolen keychain entry | Requires OS-level compromise (login password / biometric) | | Server compromise | Server only has encrypted challenge (its own key) — not the plaintext token | | Replay of old tokens | Tokens have expiry (exp claim) | | Different machine | Keychain is machine-local — can't use session from another device |

Architecture

CLI (ciba)                       Server                              IDP
 │                                │                                   │
 │─ GET /auth/par/authorize ─────▶│                                   │
 │  (sends challenge)             │ stores verifier=S256(challenge)   │
 │◀── {requestId, url} ──────────│                                   │
 │                                │                                   │
 │─ WSS /sync/device ────────────▶│ onAuthenticate: S256(ch)==verifier │
 │  (token=challenge, PoP)        │ onLoadDocument: encrypt(ch,salt)  │
 │                                │                                   │
 │  [user opens url in browser]   │                                   │
 │                                │─── login redirect ──────────────▶│
 │                                │◀── code ────────────────────────│
 │                                │─── token exchange ──────────────▶│
 │                                │◀── access_token ────────────────│
 │                                │                                   │
 │                                │ encrypt(token, challenge)         │
 │                                │ write to device doc                │
 │                                │                                   │
 │◀── Yjs update (encrypted) ────│                                   │
 │  decrypt(token, challenge)     │                                   │
 │  ✓ done                        │                                   │
 │                                │                                   │
 │  [ciba token — later]          │                                   │
 │  read challenge from keychain  │                                   │
 │─ WSS /sync/device ────────────▶│ onAuthenticate: same PoP check    │
 │◀── Yjs sync (encrypted) ──────│                                   │
 │  decrypt with keychain challenge│                                   │
 │  ✓ token                       │                                   │

CIBA Protocol Mapping

| CIBA Standard | This Implementation | |---|---| | Backchannel Authentication Endpoint | GET /auth/par/authorize | | auth_req_id | requestId | | Token Delivery (poll/push/ping) | Hocuspocus WebSocket (push) | | Authentication Device | User's browser | | Token Endpoint | Yjs device doc (encrypted) | | Client Authentication | PKCE challenge as PoP |

Environment Variables

| Variable | Description | |----------|-------------| | CIBA_URL | Default server URL (instead of --url) | | CIBA_RESOURCE | Default resource (instead of --resource) |

Resource Types

| Resource URN | Token type | Use case | |---|---|---| | urn:d:<destination> (default: unified-gateway) | User session token | Calls through API proxy | | urn:sap:identity:api:name:<name> | Exchanged user token | API with specific audience | | urn:sap:identity:application:provider:name:<app> | Exchanged user token | Direct service call (needs IAS dependency) |

Multi-Resource Sessions

# Authenticate once
ciba start --url <server>

# Default token
curl -H "Authorization: Bearer $(ciba token)" https://api/default

# Request additional resource (auto-exchanges if not cached)
curl -H "Authorization: Bearer $(ciba token --resource urn:sap:identity:api:name:my-api)" https://other-api/

# Pre-fetch a resource
ciba add --resource "urn:sap:identity:application:provider:name:my-service"

ciba stop

How It Works — Step by Step

  1. ciba start generates a random challenge (the shared secret)
  2. Calls /auth/par/authorize?challenge=<ch> — server stores verifier = SHA-256(challenge)
  3. CLI connects to /sync/device via HocuspocusProvider (doc = requestId, token = challenge)
  4. Hocuspocus onAuthenticate verifies SHA-256(token) === verifier — only the CLI can connect
  5. onLoadDocument encrypts the challenge with a server-only key and stores it in the device doc
  6. Browser opens the auth URL — user logs in via standard OAuth
  7. After login, server reads encrypted challenge from device doc, decrypts with server key
  8. Server encrypts the access token with the challenge, writes to device doc tokens map
  9. CLI observes the Yjs update, decrypts with challenge — has the token
  10. Challenge saved to OS Keychain, background process stays connected
  11. ciba token reads challenge from Keychain, reconnects to Hocuspocus (same PoP), reads + decrypts token
  12. ciba stop kills process, clears Keychain