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

@dotrino/passmanager

v0.10.1

Published

Modelo de bóveda, cifrado e interfaz de credenciales del gestor de contraseñas de Dotrino

Readme

@dotrino/passmanager

Parte del ecosistema Dotrino. Dotrino es un ecosistema de aplicaciones centradas en la privacidad de los datos: tu información es tuya, y las decisiones sobre ella también — qué compartes, con quién, cuándo y por qué. Sin anuncios, sin cookies, sin rastreo de datos, sin vender tu identidad a nadie.

El modelo de datos, el cifrado y las dos puntas del gestor de contraseñas de Dotrino: la bóveda que custodia y el aparato que pide.

La idea que lo ordena: el aparato pide una credencial por dominio y recibe esa sola. Nunca la bóveda entera. Un compromiso del navegador no es un compromiso total.

npm install @dotrino/passmanager

Las dos puntas

import { LocalVault, VaultResponder } from '@dotrino/passmanager'

// Donde vive la bóveda: tiene la llave, guarda, y decide quién puede pedir.
const vault = new LocalVault(store)
vault.unlock(cek)

new VaultResponder({
  client,                                   // @dotrino/proxy-client, ya identificado
  vault,
  isAllowed: pub => misAparatos.has(pub),   // por defecto NADIE
  needsApproval: op => op === 'get',        // una vez por aparato, no por credencial
  approve: async ({ pubkey }) => preguntarAlUsuario(pubkey),
  encPubOf: pub => llaveDeCifradoDe(pub),
  onRequest: apuntarEnLaBitacora,
}).start()
import { RemoteVault, ProxyTransport } from '@dotrino/passmanager'

// Quien pide: no tiene la llave y NO puede listar.
const remota = new RemoteVault(new ProxyTransport({ client, peerPubkey, peerEncPub }))

const hay = await remota.find('https://salesforce.com/')  // metadatos, sin secretos
const una = await remota.get(hay[0].id)                    // UNA credencial
await remota.list()                                        // ✗ falla: no-key, a propósito

list() no está para el aparato, y esa ausencia es el diseño: si pudiera listarlo todo, el «de a una» sería decorativo.

import { LocalVault, GuardedVault, ApprovalGate } from '@dotrino/passmanager'

// Una bóveda LOCAL que además pide autorización: es lo que usa la extensión, donde no
// hay transporte de por medio y por tanto nadie le ponía la puerta.
const gate = new ApprovalGate({ ask: ({ payload }) => preguntarleAlUsuario(payload) })
const vault = new GuardedVault(new LocalVault(store), { gate })

await vault.find(url)                        // público: no pregunta
await vault.get(id, { keys: ['email'] })     // un dato público: tampoco
await vault.get(id, { keys: ['secret'] })    // privado: pide autorización
await vault.patch(id, { fields: [...] })     // escribir no saca nada: no pregunta

get devuelve solo los campos que se piden, y solo pregunta si alguno es privado: la contraseña, el código de dos pasos, las notas, una passkey, o un campo que el usuario marcó. patch fusiona dentro de la bóveda, así que actualizar una entrada ya no exige leerla entera —que era lo que obligaba a autorizar para reemplazar un nombre, y lo que la dejaba a medias si esa lectura no salía—.

La puerta es la misma pieza que usa VaultResponder, y por eso las tres bóvedas del gestor se comportan igual: el sí se recuerda (o no, según con qué llave), el no nunca se recuerda, y dos peticiones a la vez producen un solo aviso.

Y como abrir una entrada cuesta una autorización, find y search traen además un resumen de cada campo —públicos y privados, un solo método— con un nonce nuevo en cada respuesta, para poder decir «esto ya está guardado igual» sin abrir nada:

const [hit] = await vault.find('https://salesforce.com/')
const hash = await fieldHasher(hit.nonce)
const igual = await hash('secret', loQueEscribio) === hit.fieldHashes.secret

El resumen dice si es igual, no qué era: para eso hay que abrir la entrada. Y no se reparte más allá de quien compara — con un valor corto y de forma conocida (un teléfono, un documento), tenerlo delante es poder adivinarlo.

Qué más trae

| | | |---|---| | sealEntry / openEntry | cifrado por entrada, con el AAD atando cada criptograma a su campo | | findForUrl / matchSite | emparejamiento por etiquetas de dominio, nunca por sufijo de cadena | | totpNow | códigos de dos pasos (RFC 6238), y acepta el secreto pelado de Bitwarden | | importAuto | 1Password, Bitwarden (CSV y JSON) y Chrome | | generatePassword | generador sin sesgo de reparto | | normalizeFields | campos libres { label, value, kind } | | SessionCache | recuerdo en memoria de lo ya entregado | | GuardedVault / ApprovalGate | la puerta de autorización, delante de cualquier bóveda | | privateKeysOf | qué campos de una entrada son privados: los marcados y los que lo son siempre | | entryFieldKeys / entryFieldValues | qué campos lleva una entrada, por su nombre | | makeNonce / fieldHasher | los resúmenes con los que se compara sin abrir la entrada |

Dos reglas que el código hace cumplir

Nada viaja en claro. El proxio enruta pero no cifra: sin sellar, vería a qué sitio se le pide credencial y cuál se devuelve. Va sellado con @dotrino/proxy-client ≥ 0.13.0, y lo que llega sin sellar se rechaza — sellar solo de salida no sirve de nada si la otra punta acepta texto plano.

Una entrada sin sites sirve en cualquier parte. Así se guarda el correo o la cédula, sin un tipo aparte y con una sola regla de emparejamiento.

Documentación

Diseño completo, con lo decidido y lo descartado, en docs/DISENO.md.