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

tecto

v1.1.0

Published

Transport Encrypted Compact Token Object — an opaque, XChaCha20-Poly1305 encrypted token protocol

Downloads

159

Readme

TECTO

Transport Encrypted Compact Token Object

An ultra-secure, opaque token protocol powered by XChaCha20-Poly1305 authenticated encryption. Unlike JWTs, TECTO tokens are fully encrypted — their contents are mathematically unreadable without the 32-byte secret key.

Why Not JWT?

| Property | JWT | TECTO | |---|---|---| | Payload visibility | Base64-encoded (readable by anyone) | Fully encrypted (opaque noise) | | Cipher | None (signed, not encrypted) | XChaCha20-Poly1305 (AEAD) | | Nonce | N/A | 24-byte CSPRNG per token | | Key size | Variable | Exactly 256-bit (enforced) | | Tamper detection | HMAC/RSA signature | Poly1305 authentication tag | | Error specificity | Reveals failure reason | Generic "Invalid token" (prevents oracles) |

Installation

bun add tecto

Quick Start

import {
  generateSecureKey,
  MemoryKeyStore,
  TectoCoder,
} from "tecto";

// 1. Generate a 256-bit key
const key = generateSecureKey();

// 2. Set up the key store
const store = new MemoryKeyStore();
store.addKey("my-key-2026", key);

// 3. Create a coder (with optional custom maxPayloadSize)
const coder = new TectoCoder(store);
// or with custom config: new TectoCoder(store, { maxPayloadSize: 10 * 1024 * 1024 })

// 4. Encrypt a payload
const token = coder.encrypt(
  { userId: 42, role: "admin" },
  { expiresIn: "1h", issuer: "my-app" }
);

console.log(token);
// → tecto.v1.my-key-2026.base64url_nonce.base64url_ciphertext

// 5. Decrypt it
const payload = coder.decrypt(token);
console.log(payload.userId); // 42

Token Format

tecto.v1.<kid>.<nonce>.<ciphertext>

| Segment | Description | |---|---| | tecto | Protocol identifier | | v1 | Protocol version | | <kid> | Key identifier (for key rotation) | | <nonce> | 24-byte Base64URL-encoded CSPRNG nonce | | <ciphertext> | Base64URL-encoded XChaCha20-Poly1305 ciphertext + auth tag |

API Reference

Security Utilities

generateSecureKey(): Uint8Array

Generates a 32-byte cryptographically random key using the platform CSPRNG.

constantTimeCompare(a: Uint8Array, b: Uint8Array): boolean

Constant-time byte comparison. Prevents timing side-channel attacks when comparing secrets.

assertEntropy(key: Uint8Array): void

Validates a key has sufficient entropy. Rejects all-zeros, repeating bytes, and keys with fewer than 8 unique byte values.

KeyStoreAdapter (Interface)

The contract for all key store implementations. TectoCoder accepts any KeyStoreAdapter.

interface KeyStoreAdapter {
  addKey(id: string, secret: Uint8Array): void | Promise<void>;
  getKey(id: string): Uint8Array;
  rotate(newId: string, newSecret: Uint8Array): void | Promise<void>;
  removeKey(id: string): void | Promise<void>;
  getCurrentKeyId(): string;
  readonly size: number;
}

MemoryKeyStore

Built-in adapter. Keys live in memory and are lost on restart.

const store = new MemoryKeyStore();
store.addKey("key-id", key);           // Add a key (first key becomes current)
store.getKey("key-id");                // Retrieve by ID
store.rotate("new-key-id", newKey);    // Add new key + set as current
store.removeKey("old-key-id");         // Revoke and zero memory
store.getCurrentKeyId();               // Get current key ID

Custom Adapter

Implement KeyStoreAdapter to use any storage backend:

import { MemoryKeyStore, assertEntropy } from "tecto";
import type { KeyStoreAdapter } from "tecto";

class MyDatabaseKeyStore implements KeyStoreAdapter {
  private mem = new MemoryKeyStore();

  addKey(id: string, secret: Uint8Array): void {
    assertEntropy(secret);
    this.mem.addKey(id, secret);
    // persist to your DB here
  }

  getKey(id: string): Uint8Array { return this.mem.getKey(id); }
  rotate(newId: string, s: Uint8Array): void { this.mem.rotate(newId, s); }
  removeKey(id: string): void { this.mem.removeKey(id); }
  getCurrentKeyId(): string { return this.mem.getCurrentKeyId(); }
  get size(): number { return this.mem.size; }
}

TectoCoder

const coder = new TectoCoder(store, options?); // any KeyStoreAdapter + optional config

// Encrypt
const token = coder.encrypt(payload, signOptions?);

// Decrypt
const payload = coder.decrypt<MyType>(token);

TectoCoderOptions

Configuration for the TectoCoder instance:

| Option | Type | Default | Description | |---|---|---|---| | maxPayloadSize | number | 1048576 (1 MB) | Maximum encrypted payload size in bytes. Prevents DoS via oversized tokens. |

// Custom 10 MB limit
const coder = new TectoCoder(store, { maxPayloadSize: 10 * 1024 * 1024 });

SignOptions

Options for individual token encryption:

| Option | Type | Default | Description | |---|---|---|---| | expiresIn | string | undefined | Duration string: "1h", "30m", "7d", "120s". If omitted, token never expires. | | issuer | string | undefined | Sets the iss claim | | audience | string | undefined | Sets the aud claim | | jti | string | auto-generated | Custom token ID (128-bit random UUID if omitted) | | notBefore | string | undefined | Duration string for nbf delay (token valid after N duration from iat) |

// Token that expires in 1 hour
coder.encrypt({ userId: 42 }, { expiresIn: "1h" });

// Token that never expires
coder.encrypt({ userId: 42 });

// Token that's valid 5 minutes from now
coder.encrypt({ userId: 42 }, { notBefore: "5m", expiresIn: "1h" });

Error Classes

| Error | Code | When | |---|---|---| | TectoError | TECTO_* | Base class for all errors | | TokenExpiredError | TECTO_TOKEN_EXPIRED | exp claim is in the past | | TokenNotActiveError | TECTO_TOKEN_NOT_ACTIVE | nbf claim is in the future | | InvalidSignatureError | TECTO_INVALID_TOKEN | Any decryption/auth failure (generic) | | KeyError | TECTO_KEY_ERROR | Invalid or missing key |

Security Properties

Core Guarantees

  • Opacity: Tokens are encrypted, not just signed. Without the key, the payload is indistinguishable from random noise.
  • Authenticated Encryption: Poly1305 tag ensures integrity. Any modification to the ciphertext, nonce, or key ID causes immediate rejection.
  • Generic Errors: All decryption failures produce the same InvalidSignatureError to prevent padding/authentication oracles.
  • Entropy Enforcement: Keys are validated for length (32 bytes), non-zero, non-repeating, and minimum byte diversity.
  • Timing-Safe Comparison: constantTimeCompare() prevents timing side-channels when comparing secrets.
  • Type Validation: Registered claims (exp, nbf, iat, jti, iss, aud) are type-checked during deserialization to prevent type confusion attacks.
  • Payload Size Limits: Configurable maximum payload size (default 1 MB) prevents DoS via oversized tokens.
  • Plaintext Cleanup: Plaintext buffers are zeroed after encryption/decryption (best-effort).
  • Key Cloning: getKey() returns defensive clones to prevent accidental mutation of stored keys.

Nonce Collision Bounds

Every encrypt() call generates a fresh 24-byte nonce from CSPRNG. XChaCha20's 192-bit nonce space provides:

  • Birthday bound: ~2^96 messages per key before collision becomes statistically likely
  • Recommendation: Rotate keys before ~1 billion encryptions or at least daily (whichever comes first)
  • Without rotation, nonce collision risk increases significantly after exceeding the birthday bound

JTI & Replay Protection

The jti claim provides detection, not prevention, of token replay:

  • Generated as 128-bit random UUID (~2^64 collision birthday bound)
  • For robust replay protection, implement a separate blacklist/allowlist mechanism
  • Track JTI values within the token's expiration window (exp - iat)

Key Rotation

store.addKey("key-2026-01", key1);
// ... time passes ...
store.rotate("key-2026-06", key2);

// New tokens use key-2026-06, old tokens still decrypt via key-2026-01
store.removeKey("key-2026-01"); // after all old tokens expire

Testing

bun test

Examples

Each example implements KeyStoreAdapter with a different storage backend.

Memory (Default)

bun run examples/memory/index.ts

SQLite

bun run examples/sqlite/index.ts

MariaDB / MySQL

bun add mysql2
bun run examples/mariadb/index.ts

PostgreSQL

bun add pg @types/pg
bun run examples/postgres/index.ts

Architecture

All adapters follow the same pattern — compose a MemoryKeyStore internally for runtime lookups and sync writes to your database:

┌──────────┐   load    ┌────────────────┐   encrypt/decrypt   ┌────────────┐
│ Database │ ───────→  │ KeyStoreAdapter│ ←────────────────→  │ TectoCoder │
│ (persist)│ ←───────  │   (runtime)    │                     │            │
└──────────┘   save    └────────────────┘                     └────────────┘

License

MIT