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

@scenesystems/seal

v0.2.3

Published

Authenticated encryption for Effect

Readme

@scenesystems/seal

@scenesystems/seal provides authenticated encryption for programs built with Effect. Use it when bytes must stay confidential at rest or in transit and any modification must be detected on decryption: a session record in a cache, a token handed to a browser, or a payload stored by a third party.

The model is a single envelope. seal encrypts bytes with a chosen AEAD algorithm and returns a SealedEnvelope that carries the algorithm identifier, a fresh base64url nonce, and the base64url ciphertext with its authentication tag. unseal reads the algorithm from the envelope, authenticates, and returns plaintext or a typed error. Every algorithm uses a 32-byte key, and the package generates a new nonce for each call. Primitive implementations come from Noble Ciphers.

The package encrypts and nothing more. Identity, authorization, key storage, key rotation, envelope versioning, and protocol policy belong to the application. @scenesystems/sign provides signatures and key agreement for the identity half of a protocol, and @scenesystems/digest derives keys with HKDF or BLAKE3 when a shared secret must become a sealing key.

Installation

npm install @scenesystems/seal effect

Effect ^3.22.1 is a required peer dependency. The package has one entrypoint, @scenesystems/seal.

Basic use

generateKey draws 32 bytes from the platform's cryptographically secure random source. seal and unseal round-trip bytes through an envelope; utf8ToBytes and utf8FromBytes convert text at the edges.

import { generateKey, seal, unseal, utf8FromBytes, utf8ToBytes } from "@scenesystems/seal"
import { Effect } from "effect"

export const program = Effect.gen(function* () {
  const key = yield* generateKey()
  const envelope = yield* seal("xchacha20-poly1305", key, utf8ToBytes("private data"))
  const plaintext = yield* unseal(key, envelope)
  return utf8FromBytes(plaintext)
})

Store and transport the whole envelope. The algorithm field drives decryption dispatch, so the surrounding storage or protocol must protect it from substitution just as it protects the key.

Algorithm selection

SealAlgorithm is a literal union of three AEAD constructions. All three take a 32-byte key and differ in nonce size and in how they fail when a nonce repeats under one key.

| Algorithm | Nonce | When to choose it | | -------------------- | -------: | ------------------------------------------------------------------------------------------------------- | | xchacha20-poly1305 | 24 bytes | Default. The large nonce makes random generation safe for any practical number of messages per key. | | aes-256-gcm-siv | 12 bytes | Nonce reuse leaks only whether two plaintexts are equal, rather than breaking confidentiality outright. | | aes-256-gcm | 12 bytes | Interoperability with existing AES-GCM systems. Nonce reuse under one key is catastrophic. |

With 12-byte random nonces, keep the number of messages per AES key well below 2^32 and rotate keys on an application-owned schedule. AES-256-GCM-SIV tolerates accidental reuse but is still subject to per-key usage bounds. Choose one algorithm per protocol and treat a change of algorithm as a versioned migration rather than a per-message option.

Envelopes and direct operations

SealedEnvelope is a Schema.Class, so it decodes from and encodes to plain JSON with Effect's Schema functions and validates the base64url fields on the way in.

import { SealedEnvelope, unseal } from "@scenesystems/seal"
import { Effect, Schema } from "effect"

export const openStored = (key: Uint8Array, stored: unknown) =>
  Effect.gen(function* () {
    const envelope = yield* Schema.decodeUnknown(SealedEnvelope)(stored)
    return yield* unseal(key, envelope)
  })

The direct functions xchacha20Encrypt, aesgcmsivEncrypt, and aesgcmEncrypt return nonce-prefixed ciphertext bytes instead of an envelope, and their Decrypt counterparts consume the same layout. Use them when a wire format already fixes the algorithm and you only need the bytes. packEnvelope(algorithm, raw) splits nonce-prefixed bytes into an envelope and unpackEnvelope(envelope) reverses it, so the two representations convert without re-encrypting.

import { packEnvelope, xchacha20Encrypt } from "@scenesystems/seal"
import { Effect } from "effect"

export const sealForWire = (key: Uint8Array, plaintext: Uint8Array) =>
  Effect.gen(function* () {
    const raw = yield* xchacha20Encrypt(key, plaintext)
    const envelope = yield* packEnvelope("xchacha20-poly1305", raw)
    return { raw, envelope }
  })

Public surface

The package exports plain functions and schemas from a single entrypoint.

| Area | Exports | | ----------------- | ------------------------------------------------------------------------------------- | | Envelope pipeline | seal, unseal | | Direct AEAD | xchacha20Encrypt/Decrypt, aesgcmsivEncrypt/Decrypt, aesgcmEncrypt/Decrypt | | Envelope encoding | packEnvelope, unpackEnvelope | | Keys and bytes | generateKey, utf8ToBytes, utf8FromBytes, equalBytes | | Schemas | SealAlgorithm, SealedEnvelope, InvalidKey, DecryptionFailed |

The full list with signatures is in the API reference.

Errors and boundaries

InvalidKey reports the expected and received key lengths and is raised before any cryptographic work. DecryptionFailed carries one of two reasons: invalid envelope encoding for malformed base64url, and authentication failed for a wrong key, modified or truncated ciphertext, a corrupted nonce, or a tag mismatch. The second reason deliberately does not say which condition occurred. Treat every decryption failure the same way in application logic so that error handling does not become a decryption oracle. unpackEnvelope on its own fails with Effect's Encoding.DecodeException.

Authenticated encryption protects confidentiality and integrity under the supplied key. It does not identify who produced an envelope or say what the envelope means. Keys must come from secure storage, stay separate from ciphertext, and rotate on a policy the application owns. Do not reuse a key across protocols without an explicit domain and lifecycle analysis. generateKey depends on the runtime's crypto.getRandomValues, and equalBytes compares same-length arrays without early exit, though timing at the protocol level also includes the surrounding control flow and I/O.

Standards

The algorithms follow RFC 8439 with the XChaCha20 extension, RFC 8452, and NIST SP 800-38D. Noble's audits cover the primitive implementations; key handling, envelope semantics, and protocol integration are reviewed separately in this package and in your application.

Examples

The examples directory contains two runnable programs: encryption and decryption through an envelope, and algorithm comparison, which seals one message under each algorithm and handles DecryptionFailed and InvalidKey with Effect.catchTag.

Status

This package is pre-1.0. Minor releases may change public APIs; pin a compatible version and review the changelog when upgrading.

Contributing and support

Read the repository contributing guide before opening a pull request. Report defects and request changes through GitHub issues. For security concerns, follow the security policy.

License

MIT. Copyright 2026 Scene Systems.