@agimon-ai/doompi-web-security

Shared security primitives for the DoomPi web cockpit: sealed channels and signed bundle manifests for independently trusted verifiers.

Packages

Package details

package

Install @agimon-ai/doompi-web-security from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@agimon-ai/doompi-web-security
Package
@agimon-ai/doompi-web-security
Version
0.0.1-alpha.5
Published
Aug 30, 2026
Downloads
343/mo · 343/wk
Author
agiflow-ai
License
MIT
Types
package
Size
215.6 KB
Dependencies
0 dependencies · 0 peers

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

@agimon-ai/doompi-web-security

Shared security primitives for the DoomPi web cockpit: the sealed-channel envelope, its node:crypto and WebCrypto halves, and the signed bundle manifest.

This package holds no policy and starts nothing. It exists because three different programs need to agree byte for byte on the same crypto: the cockpit hub (Node), the cockpit page (browser), and every web plugin bundled into that page.

Why it is a package

A plugin in another package cannot import from a client application, and the sealed transport is a module singleton whose nonce counters every caller must share. A second copy would start counting at zero and the receiver would reject everything it sent as a replay. One package, deduped in the bundle, is what makes that impossible.

Subpaths

Import Runs where What it carries
@agimon-ai/doompi-web-security both The envelope contract, the bundle manifest shape, the canonical serialization both sides sign and verify
@agimon-ai/doompi-web-security/browser the page The WebCrypto channel and sealedTransport, the shared instance plugins use
@agimon-ai/doompi-web-security/node the hub The node:crypto channel, the host handshake, and the bundle signer

The sealed channel

A hosted tunnel terminates TLS at its provider's edge, so without application sealing everything the cockpit carries is plaintext to them. Sealing the payload underneath their TLS leaves them a relay that sees timing and sizes but not content.

The QR pairing path anchors key exchange out of band. The host's ephemeral P-256 public key is printed on the screen the user is holding, so the relay cannot substitute its own. A returning device instead receives the current public key after proving a passkey. That path depends on the trusted code-delivery edge described above, and the key itself is public rather than secret.

Three properties worth knowing, because getting any of them wrong is silent:

  • Separate keys per direction. Derived with different HKDF info strings, so a message the server sent can never be replayed back at it as though the client had sent it.
  • Nonces are a random per-channel prefix plus a monotonic counter, never random per message. A repeated nonce under one AES-GCM key leaks the XOR of two plaintexts and the authentication subkey; 96 bits is small enough that random nonces collide at a rate worth caring about.
  • Both directions are serialized. Sealing advances the counter and opening demands it strictly increase, so overlapping asynchronous calls are a correctness bug, not a race worth tolerating. createSerialQueue is what prevents a burst of socket sends from reordering into a self-inflicted replay.

Every failure names itself. A decryption failure otherwise shows up as a blank page with nothing to go on.

The signed bundle-manifest primitive

The Node signer traverses a public asset root without following symlinks, requires /index.html, hashes each byte sequence, and signs a strict canonical manifest v2 with ECDSA P-256. Revision state and the private key are persisted atomically at mode 0600; malformed or replaced state fails closed instead of rotating silently.

The browser verifier accepts only the public SPKI and minimum revision pinned by the physical QR. DoomPi's package-owned service worker verifies the envelope and every asset before committing a Cache Storage revision, then atomically records the active bundle in IndexedDB. A same-revision digest conflict, signer mismatch, missing cache, bad MIME metadata, length mismatch, or hash mismatch is refused. The last-known-good bundle remains active during a failed refresh.

This does not make the package-owned bootstrap self-protecting. A TLS edge that replaces /pair or /sw.js could remove the verifier before it runs. Once the intended bootstrap is installed, however, host and plugin JavaScript cannot execute until its signed bytes pass verification.

canonicalManifest is hand-rolled rather than JSON.stringify of the whole object, because a signature is only worth anything if signer and verifier agree byte for byte, and key order in a JSON object is an implementation detail.

Public API

import { canonicalManifest, describeSealedFailure, isSealedEnvelope } from '@agimon-ai/doompi-web-security';
import { sealedTransport } from '@agimon-ai/doompi-web-security/browser';
import { createBundleSigner, createHostHandshake } from '@agimon-ai/doompi-web-security/node';

Development

pnpm build
pnpm typecheck
pnpm test
pnpm lint

The round-trip test is the one that matters: it seals with node:crypto and opens with real WebCrypto, so the two implementations cannot drift apart without a failure.

Maintained by Agimon.