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

@synoi/gateway-lite

v0.1.1

Published

Self-hosted SynOI lite daemon: local governed-action approval with operator-owned self-signed receipts. No account, no managed custody, no network call to run it.

Readme

@synoi/gateway-lite

SynOI Gateway Lite: a self-hosted local daemon for governed-action approval with operator-owned, self-signed receipts. No account, no managed key custody, no network call required to run it.

Status: PARTIAL-against-test-keys, PARTIAL-pending-Security+Adversary-panel. See "What gets signed, and by whom" and "Where the key lives, and who else can use it" below before relying on this for anything beyond local dogfood.

What this is

A single process that:

  1. Accepts a governed action from your code (gate() via @synoi/sdk in local mode, or a raw POST /local/gate).
  2. Shows it to you, the operator, in a local dashboard for approve/deny.
  3. Signs a receipt for the decision with a key generated once on your own machine and never transmitted anywhere.

It excludes, at the architecture level (not just by policy), every premium channel adapter (Composio, Slack, SMS, voice, n8n/Zapier, home automation, AR overlay, and the 23 MCP shim integrations), the control-plane / license client, and the KMS managed-custody signing tier (the AWS Secrets Manager-backed key provider the full gateway uses in production). Those are the paid, hosted product; this package literally cannot reach any of that code -- verified two ways in the source repo's CI, not by inspection: (1) a dependency-graph assertion over daemon.ts's actual TypeScript import graph, and (2) a byte-level content scan of the REAL packed tarball (the exact bytes npm install @synoi/gateway-lite would fetch) that fails if any shipped JavaScript (.js) file contains an AWS SDK reference anywhere, comments included. The KMS provider is a complete, working AWS Secrets Manager client -- it lives in its own source module, reached by the full gateway only through a runtime require() call, and that module is never compiled into this package: it is not merely unused, it is structurally absent from what you install.

Quickstart

npm i @synoi/sdk
npx @synoi/gateway-lite

The daemon listens on http://127.0.0.1:8787 by default (override with SYNOI_LITE_PORT). Open http://127.0.0.1:8787/local/dashboard in a browser to enroll your operator identity (a keypair generated in the browser via the Web Crypto API; the private key never leaves it) and approve/deny pending actions.

From your application:

import { gate } from '@synoi/sdk'

await gate({
  action_kind: 'command',
  args: { to: '[email protected]', subject: 'deploy complete' },
  daemonUrl: 'http://127.0.0.1:8787', // or SYNOI_DAEMON_URL env
}, async () => {
  // runs only if the operator approves
  await sendEmail(...)
})

What gets signed, and by whom

The first time the daemon runs, it generates a single Ed25519 keypair for you, the operator. Every decision receipt after that is self-signed with that SAME key using @synoi/gap's receipt() one-liner -- the single-Ed25519 self-sign CDRO shape, receipt_scheme: "synoi.receipt/gap-selfsign". Verify any receipt offline with the published @synoi/verify package:

import { verifyReceiptByScheme } from '@synoi/verify'

const result = await verifyReceiptByScheme({
  receipt,                 // fetched from GET /local/receipts/:oid
  gap_ed25519_pub: pubkey, // the 32-byte raw public key (see "Where the key lives" below)
})
// result.valid === true, result.scheme === 'gap-selfsign'

Honest disclosure: this proves the receipt was signed by, and has not been altered since, the key on YOUR machine. It does not yet prove that key belongs to a trustworthy party to anyone else -- a neutral, third-party-hosted resolver (oid.synoi.systems) is planned but not live. Do not describe a self-signed receipt as "independently verified."

Where the key lives, and who else can use it

Any process running as your OS user account can sign as the operator using this key. This key has no managed custody and no account recovery: if it is lost, it is gone, and there is no SynOI-side way to get it back or re-sign past receipts under a continuous identity. This is the tradeoff of self-hosting with no managed service in the loop; it is not a bug to be fixed later, it is what "no account, no network call" means.

The daemon tries to reduce the blast radius of that tradeoff by wrapping the key with your OS's own credential store where one is available:

  • Windows -- DPAPI (System.Security.Cryptography.ProtectedData, current-user scope). The private key is written to SYNOI_DATA_DIR/lite-signing-key.dpapi as ciphertext only your Windows user account can decrypt; SYNOI_DATA_DIR/lite-signing-key.json holds only the public key and metadata.
  • macOS -- the system Keychain, via the security command-line tool every macOS install ships with (implemented per documented behavior; not yet executable-tested on this platform; the daemon prints the actual path taken, and any fallback, on every boot).
  • Linux -- secret-tool (libsecret / gnome-keyring). Requires a running secret-service provider, which is normal on a desktop session and often ABSENT on a minimal/headless/server install -- that is an expected reason to hit the fallback below, not a bug (implemented per documented behavior; not yet executable-tested on this platform; the daemon prints the actual path taken, and any fallback, on every boot).

Important: the OS keystore protects the key from OTHER user accounts on the same machine. It does NOT protect the key from other processes running as YOUR OWN account -- any of those can still ask the OS to unwrap it, same as they could read any other file you own. Treat the machine and your OS login as the trust boundary, not this daemon.

If no OS keystore is available (or it fails), the daemon falls back to a plaintext JSON file at SYNOI_DATA_DIR/lite-signing-key.json (private_key_hex present directly), protected by whatever your OS's own file permissions provide: POSIX mode 0600 on macOS/Linux (kernel-enforced), or a Windows ACL restricted to your account only (icacls ... /grant:r <you>:F, applied automatically -- note that POSIX mode bits like 0600 are silently a no-op on Windows NTFS, which is why this is a real ACL call, not just a mode flag). The daemon prints which path it took, and any fallback reason, on every boot.

Environment variables

| Variable | Purpose | Default | |---|---|---| | SYNOI_LITE_PORT / PORT | Listen port | 8787 | | SYNOI_DATA_DIR | Where the operator signing key, enrollment record, and SQLite store live | ~/.synoi | | SYNOI_ALLOW_EPHEMERAL_KEYS | Currently required for POST /local/gate to accept the bootstrap hero-bundle OID capbundle/1 without a real capability-grant record (lite has no grant-issuance ceremony yet). NOT needed for key generation, receipt signing, or the operator-enrollment record, which survive a restart on the persisted operator key regardless of this variable. Set true; this requirement is expected to be removed once a real grant ceremony ships. | required for POST /local/gate, no default | | SYNOI_DASHBOARD_DIR | Serve a built portal SPA instead of the built-in self-contained HTML dashboard | unset (uses the built-in dashboard) |

The daemon boots with zero AWS credentials, zero control-plane URL, and zero license key -- there is nothing in the shipped package that could reach any of those: no reachable import, and (independently checked) no AWS-SDK-related byte sequence anywhere in the packed tarball (enforced by CI, see "Architecture" below).

Architecture (why this is small)

This package is NOT a slice of the full SynOI gateway shipped to a subdirectory. It bundles only the source files src/daemon.ts actually imports, transitively, in the gateway's monorepo -- 18 files, six runtime npm dependencies (express, better-sqlite3, @synoi/gap, @synoi/sraid, @noble/curves, @noble/post-quantum), no channel adapters, no license client, no cloud SDK of any kind. The gateway repo's CI checks this two ways, both required to pass before a build is produced:

  1. A dependency-graph assertion over daemon.ts's real TypeScript import graph (ts.createProgram, not a regex), asserting it never reaches the channel adapters, the license client, the KMS-hybrid signing tier, or the AWS Secrets Manager key provider module.
  2. A byte-level content scan of the ACTUAL packed tarball (a real npm pack, extracted and read file by file, not --dry-run's file-name listing): fails if any shipped .js file contains an AWS-SDK-related string anywhere, including inside comments. This is the check that would have caught what the dependency-graph assertion alone could not: a file can be legitimately reachable (this package's own key-provider module is; it backs the self-host key-generation path) while a DIFFERENT class defined in the same file carries a cloud-SDK reference that a pure import-graph walk cannot see. The fix was to move that class into its own module, reached only by a runtime require() the static graph walk does not follow -- so its compiled output, and the SDK reference inside it, never enter this package's dist/ at all.

This package's own build step refuses to produce a build if either assertion would fail.

Repository

This package currently lives in packages/gateway-lite/ of the synoi-gateway repository (private) pending extraction to a dedicated public repository before first publish. That extraction, and the first npm publish, are human-gated steps not taken by this build.

License

Apache-2.0