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

@shelby-protocol/s3-gateway

v1.12.0

Published

S3-compatible gateway for Shelby Protocol blob storage

Readme

Shelby S3 Gateway

An S3-compatible API gateway for Shelby Protocol blob storage.

Use familiar S3 tools and libraries (AWS SDK, rclone, Cyberduck, DuckDB, etc.) to access blobs stored on Shelby.

Quick Start

  1. Create a shelby.config.yaml file:
network:
  name: shelbynet
  rpcEndpoint: https://api.shelbynet.shelby.xyz/shelby
  aptosFullnode: https://api.shelbynet.shelby.xyz/v1
  aptosIndexer: https://api.shelbynet.shelby.xyz/v1/graphql
  locationHint: shelbynet-1
  apiKey: your-api-key

server:
  port: 9000

credentials:
  - accessKeyId: AKIAIOSFODNN7EXAMPLE
    secretAccessKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

buckets:
  - name: clickhouse-data
    accountAddress: "0x0694a79e492d268acf0c6c0b01f42654ac050071a343ebc4226cb6717d63e4ea"

network.locationHint selects the Shelby location used for blob writes. It defaults to the SDK's location for the selected network (shelbynet-1 on Shelbynet and localnet-1 locally). server.region is only the S3 region used for SigV4 compatibility and does not control where Shelby stores blobs. The gateway does not read the CLI's ~/.shelby/config.yaml; when it runs in Docker, use the gateway's mounted shelby.config.yaml instead.

  1. Start the gateway:
npx @shelby-protocol/s3-gateway --config shelby.config.yaml
  1. Connect with any S3 client:
# Using rclone
rclone config create shelby s3 \
  provider=Other \
  access_key_id=AKIAIOSFODNN7EXAMPLE \
  secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \
  endpoint=http://localhost:9000 \
  force_path_style=true

rclone ls shelby:clickhouse-data

Bucket aliases must follow the Amazon S3 general-purpose bucket naming rules. For backward compatibility, a quoted account address can still be used directly:

buckets:
  - "0x0694a79e492d268acf0c6c0b01f42654ac050071a343ebc4226cb6717d63e4ea"

If server-side encrypted objects were previously written through an address-style bucket, accountAddress must preserve the exact address spelling used in that bucket path. The encryption identity is byte-sensitive.

Replicated deployments such as Geomi should implement the exported BucketResolver interface against their shared control-plane database and inject it as bucketResolver. A gateway-local database is suitable only for a single self-hosted instance: aliases must resolve consistently across replicas, restarts, and deployments.

CLI Options

npx @shelby-protocol/s3-gateway --help

Options:
  -c, --config <path>  Path to config file
  -p, --port <port>    Port to listen on (overrides config)
  -h, --help           Show help message

Managed Encryption With ACE

Encrypted reads and writes recover a bucket key-encryption key (KEK) from ACE. The gateway encrypts bytes locally using a fresh data-encryption key (DEK) per blob, including each multipart upload part. That DEK is wrapped with the bucket KEK and stored in the encrypted container; reusing a KEK does not reuse a DEK.

The package exposes the runtime adapter under @shelby-protocol/s3-gateway/ace. The companion Move policy lives in this repository at move/s3_gateway_ace_policy. For each network bundle, deploy and initialize that policy with at least one S3 gateway Ed25519 public key, then configure the gateway with:

  • ACE deployment chain id, endpoint, contract address, and IBE keypair id.
  • Policy contract chain id and module address. This is the policy admin account that deploys the Move module and manages allowed gateway public keys.
  • Gateway signing account private key, held by each S3 gateway server process only. Its Ed25519 public key must be registered in the policy by the admin.

YAML local-server example:

encryption:
  enabled: true
  bucketDefaults:
    "0xYOUR_SHELBY_ACCOUNT": AES256
  ace:
    aceChainId: 118
    apiEndpoint: "https://api.shelbynet.shelby.xyz/v1"
    aceContractAddress: "0x2a800d06b231476e045e874b5319409f80aa4449d7cabcdc68d2e0b5a66ee43d"
    ibeKeypairId: "0xa36e6db16b015c6c2c9a376afe3075b11031ee0df393c226e7d599f615759a17"
    policyChainId: 118
    policyModuleAddress: "0xYOUR_POLICY_ADMIN_ACCOUNT"
    policyModuleName: "s3_gateway_ace_policy"
    accountFile: "./gateway-signing-account.json"

The gateway runtime config contains both identities needed by ACE:

  • ACE deployment identity: aceChainId, aceContractAddress, and ibeKeypairId. apiEndpoint tells the SDK where to fetch that deployment's public key, but it is not persisted in Shelby blob metadata.
  • Policy identity: policyChainId, policyModuleAddress, and policyModuleName. The gateway passes this to IBE_Aptos.fetchIdentityKeySharesCustomFlow when recovering the bucket KEK on either path.

An optional clientConfig block is forwarded verbatim to the Aptos client ACE reads the chain with, using that SDK's key names. Runtimes that cannot use the defaults need it — on Bun, HTTP/2 leaves every ACE read hanging until it times out, so it has to be turned off:

  ace:
    # ...
    clientConfig:
      http2: false

Unknown keys here are dropped rather than rejected, so check the spelling: a misspelled http2 reads as a hang, not as a config error.

Use the same ACE deployment and bucket identity for reads and writes: recovering a different KEK cannot unwrap an existing object's DEK.

The account file is local-only and should not be committed:

{
  "accountAddress": "0xYOUR_GATEWAY_SIGNING_ACCOUNT",
  "privateKey": "ed25519-priv-0x..."
}

The accountAddress must be the actual gateway signing account address. Do not derive it from the private key at startup; Aptos account auth keys can be rotated, so the account address may differ from the address derived from the current signing key.

The policy uses ACE custom flow and verifies a gateway payload shaped as public_key || signature, where public_key must already be registered in the policy and signature covers the framework chain id, policy module address, policy module name, ACE label, and caller encryption public key. Registered gateway public keys can be added or removed without scanning the key set. SigV4 authorization remains in the S3/API gateway layer; it is not re-verified by ACE.

Optional bucket KEK cache

Caching is disabled by default. To opt in for the standard gateway signer:

encryption:
  # ...existing encryption settings...
  ace:
    # ...existing ACE deployment and signing settings...
    kekCache:
      ttlMs: 60000
      maxEntries: 1024

The same setting is available to createAceKekProvider and createAceKekProviderFromEnvironment. backend: memory makes the local backend explicit; omitting backend preserves existing behavior. maxEntries defaults to 1024 and applies only to memory. Custom decryptionSigner callbacks cannot be cached because they may perform request-specific authorization.

To share KEKs across replicas, use a dedicated Redis 7+ instance:

encryption:
  ace:
    # ...existing ACE deployment and signing settings...
    kekCache:
      backend: redis
      ttlMs: 60000
      redis:
        namespace: shelby-beta-ams2
        urlEnv: KEK_CACHE_REDIS_URL
        encryptionKeyEnv: KEK_CACHE_ENCRYPTION_KEY
        caFile: /etc/kek-cache/ca.crt # optional for a publicly trusted CA
        timeoutMs: 200
        maxInFlight: 128

KEK_CACHE_REDIS_URL contains a rediss:// URL with Redis credentials. KEK_CACHE_ENCRYPTION_KEY contains a dedicated 32-byte key encoded as 64 hex characters, shared by these replicas. Generate it with openssl rand -hex 32 and distribute it through a secret manager. Do not reuse an ACE signing key or managed-account encryption key. redis:// is supported for local tests; use TLS and network access controls in production. The CA option never disables certificate verification. Missing or invalid configuration fails startup.

Redis stores AES-256-GCM ciphertext, authenticated against the complete cache identity and a fixed expiry. Network access controls restrict who can connect; wrapping also keeps plaintext KEKs out of Redis memory and binds cached values to their identity. It does not protect a compromised gateway that holds the wrapping key. The scope includes the network/cluster namespace, ACE chain, contract, IBE keypair, policy, gateway account and signing public key, TTL and wrapping-key fingerprint. Changing any of these creates a cold cache. The account/bucket label separates keys within that scope. Wrapping-key rotation does not change stored objects or their KEKs; roll it across replicas to stop sharing with the old scope. Object encryption still creates a fresh DEK per blob.

Each seal uses a random 96-bit IV. Rotate the wrapping key well before 2^32 seal invocations across all replicas and scopes using that key, following NIST SP 800-38D, section 8.3. Count seals even if publication fails or another fill wins. Cache TTL, live entry count, namespace changes and restarts do not reset this budget; only a fresh wrapping key does. Before production enablement, establish an aggregate workload bound and a security-reviewed rotation schedule with margin for bursts and rollout overlap. Production volume has not been measured by this change, and the adapter does not enforce a lifetime invocation limit. The miss event count is a conservative upper bound on seals when collected completely (each miss seals at most once); monitoring must account for all replicas, counter resets and gaps.

Redis uses its own clock for expiry. Hits never extend the deadline, which starts before ACE recovery. Expired or malformed entries are misses; ACE errors never permit serving stale keys. A configured TTL allows reuse after ACE gateway-key revocation for up to that TTL. SigV4 and tenant read/write authorization must still run before every key lookup. Do not enable caching for policies requiring a fresh ACE decision on every request.

Concurrent misses recover from ACE independently, with no distributed lock or polling. The first completed recovery fills the cache with SET ... NX; later fills cannot overwrite that entry or renew its fixed expiry. Redis commands have bounded timeouts and offline queuing is disabled. maxInFlight caps outstanding ACE recoveries per provider, including concurrent requests for the same bucket and fallbacks; exceeding it fails the request. There is no second local KEK cache in Redis mode. The onKekCacheEvent runtime callback reports hit, miss, fallback and invalid without account or key labels.

Library callers should await provider.clearCache() for fleet-wide invalidation of that provider scope. It changes a random generation and fences older recoveries; old entries expire naturally. Invalidation fails visibly if Redis is unavailable. It does not cancel requests or reclaim keys already handed to callers. Restarting one gateway only clears its local memory cache; it does not invalidate Redis. Call await provider.close() at shutdown to release the Redis connection and wipe locally owned wrapping-key bytes. JavaScript cannot guarantee erasure of all copies.

Use a standalone Redis primary (the adapter does not support Redis Cluster or Sentinel discovery). Disable persistence, bound memory, and prefer volatile-lru: values expire, while generation keys intentionally have no TTL and are not evicted by that policy. There is one generation key per provider scope, not per bucket. Clearing a scope replaces its generation value without creating another generation key. Scope changes (including key or TTL changes) leave the old generation key behind. Once all replicas using an old scope have stopped and its maximum TTL has elapsed, an operator may delete that exact retired scope's generation key. Do not delete active scopes or use FLUSHDB for cleanup. Restrict its ACL to ~shelby:kek:v1:<namespace>:* and +@connection +get +set +del +eval +time. A Redis restart gives a cold cache and ACE fallback. Failover can restore a prior generation, so the TTL remains the revocation bound even after explicit clearing.

Run the cross-client integration tests against an isolated Redis:

TEST_KEK_REDIS_URL=redis://localhost:6379 pnpm exec vitest run src/ace

CI runs these tests over TLS with the restricted cache ACL above. Set TEST_KEK_REDIS_CA_FILE to a private CA file and TEST_KEK_REDIS_ADMIN_URL to a separate test-only administrative URL when reproducing that setup; inspection and tampering tests need permissions the gateway does not. The suite also verifies certificate rejection, ACL isolation and overlapping wrapping-key rotations.