diff --git a/.dockerignore b/.dockerignore index 935e329f..30568474 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,4 +4,11 @@ browser/resources example-local .github docker-compose.yaml -attestor.dockerfile \ No newline at end of file +attestor.dockerfile +.git +.dockerignore +deploy/build.env +deploy/*.tar +coverage +.env +.env.* \ No newline at end of file diff --git a/.env.sample b/.env.sample index 7ef92ddb..b1eccd68 100644 --- a/.env.sample +++ b/.env.sample @@ -53,4 +53,26 @@ DISABLE_BGP_CHECKS= # # The public key will be of the same signature algorithm # as the attestor's private key. -AUTHENTICATION_PUBLIC_KEY= \ No newline at end of file +AUTHENTICATION_PUBLIC_KEY= +# --- GCP Confidential Space --- +# Set to "true" to run the attestor inside a GCP Confidential Space VM. +# In this mode: PRIVATE_KEY/TOPRF_* are loaded from Secret Manager, the +# server terminates TLS using a Let's Encrypt cert (HTTP-01 via port 80), +# and every signed claim carries a GCP attestation JWT. +ENCLAVE_MODE= +# Domain the attestor terminates TLS on (must point to this VM). +ENCLAVE_DOMAIN= +# HTTPS port to listen on in enclave mode (default 443). +HTTPS_PORT= +# Port for the ephemeral HTTP-01 challenge server (default 80). Bound +# only during ACME orders, then released. +HTTP_PORT= +# Email passed to the ACME registration. +ACME_EMAIL= +# ACME directory URL. Default: Let's Encrypt production. Use the staging +# URL https://acme-staging-v02.api.letsencrypt.org/directory while testing. +ACME_DIRECTORY_URL= +# GCP project that owns the Secret Manager secrets. +GOOGLE_PROJECT_ID= +# Log name to use under Cloud Logging in enclave mode (default: attestor-core). +LOG_NAME= \ No newline at end of file diff --git a/.gitignore b/.gitignore index a1a9da6e..6e8b762f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,6 @@ src/test.ts .env.* .cache envs -out \ No newline at end of file +out +# deploy +deploy/build.env diff --git a/deploy/build.env.example b/deploy/build.env.example new file mode 100644 index 00000000..159a0069 --- /dev/null +++ b/deploy/build.env.example @@ -0,0 +1,16 @@ +# Copy to deploy/build.env (gitignored) and fill in. +# +# Required: +PROJECT_ID=your-gcp-project +DOMAIN=attestor.example.com +ACME_EMAIL=ops@example.com + +# Optional overrides (defaults shown in comments): +# REGISTRY=gcr.io/${PROJECT_ID} +# ZONE=asia-south2-a +# INSTANCE=attestor-prod +# MACHINE_TYPE=n2d-standard-2 +# SERVICE_ACCOUNT=tee-attestor-sa@${PROJECT_ID}.iam.gserviceaccount.com +# STATIC_IP_NAME=attestor-prod-ip +# FIREWALL_RULE=allow-attestor +# NETWORK_TAG=attestor diff --git a/deploy/build.sh b/deploy/build.sh new file mode 100755 index 00000000..25d1ec93 --- /dev/null +++ b/deploy/build.sh @@ -0,0 +1,153 @@ +#!/bin/bash +set -e + +# ============================================================================= +# attestor-core reproducible build +# ============================================================================= +# Builds and pushes a bit-for-bit reproducible image of attestor-core for +# GCP Confidential Space. Same git commit + same builder image digest = +# same OCI tarball sha256, so customers can rebuild and verify what's +# running. +# +# Flow: deterministic OCI tarball -> crane push (preserves digest) +# +# Requirements: +# - Docker with buildx +# - crane (go install github.com/google/go-containerregistry/cmd/crane@latest) +# - deploy/build.env with REGISTRY (gitignored) +# +# Usage: +# ./deploy/build.sh [tag] [commit] [--verify] +# ./deploy/build.sh # tag=v1, HEAD +# ./deploy/build.sh v2 # explicit tag, HEAD +# ./deploy/build.sh v2 abc123 # explicit tag, specific commit +# ./deploy/build.sh v2 --verify # build + reproducibility check +# ============================================================================= + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(dirname "${SCRIPT_DIR}")" + +# Pinned BuildKit. Update this digest when upgrading by running: +# crane digest moby/buildkit:buildx-stable-1 +BUILDKIT_IMAGE="moby/buildkit:buildx-stable-1@sha256:0039c1d47e8748b5afea56f4e85f14febaf34452bd99d9552d2daa82262b5cc5" + +# Pinned base image. Must match FROM in gcp.dockerfile. +NODE_IMAGE_DIGEST="sha256:1de022d8459f896fff2e7b865823699dc7a8d5567507e8b87b14a7442e07f206" + +BUILD_ENV="${SCRIPT_DIR}/build.env" +if [[ ! -f "${BUILD_ENV}" ]]; then + echo "Missing ${BUILD_ENV}. Create it with:" + echo " REGISTRY=gcr.io/your-gcp-project" + exit 1 +fi +source "${BUILD_ENV}" + +: "${REGISTRY:?REGISTRY not set in deploy/build.env}" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" +} + +error() { + echo "[ERROR] $1" >&2 + exit 1 +} + +IMAGE_TAG="${1:-v1}" +COMMIT="${2:-}" +VERIFY="${3:-}" + +# Allow `./build.sh v1 --verify` (commit defaults to HEAD). +if [[ "${COMMIT}" == "--verify" ]]; then + VERIFY="--verify" + COMMIT="" +fi + +IMAGE="${REGISTRY}/attestor-core:${IMAGE_TAG}" + +if [[ -n "${COMMIT}" ]]; then + export SOURCE_DATE_EPOCH=$(git -C "${REPO_ROOT}" log -1 --pretty=%ct "${COMMIT}") + log "SOURCE_DATE_EPOCH from commit ${COMMIT}: ${SOURCE_DATE_EPOCH}" +else + COMMIT=$(git -C "${REPO_ROOT}" rev-parse HEAD) + export SOURCE_DATE_EPOCH=$(git -C "${REPO_ROOT}" log -1 --pretty=%ct) + log "SOURCE_DATE_EPOCH from HEAD (${COMMIT:0:12}): ${SOURCE_DATE_EPOCH}" +fi + +TMPDIR=$(mktemp -d) +trap "rm -rf ${TMPDIR}" EXIT + +command -v crane >/dev/null 2>&1 \ + || error "crane not found. Install: go install github.com/google/go-containerregistry/cmd/crane@latest" + +# Normalize file mtimes to SOURCE_DATE_EPOCH. git checkout sets mtimes +# to checkout time, which varies between environments. +find "${REPO_ROOT}" \ + -not -path '*/.git/*' \ + -not -path '*/node_modules/*' \ + -exec touch -d "@${SOURCE_DATE_EPOCH}" {} + 2>/dev/null || true + +# Reuse pinned BuildKit builder if it exists; else create. +BUILDER_NAME="attestor-repro" +if ! docker buildx inspect "${BUILDER_NAME}" >/dev/null 2>&1; then + log "Creating pinned builder: ${BUILDER_NAME}" + docker buildx create --name "${BUILDER_NAME}" \ + --driver docker-container \ + --driver-opt image="${BUILDKIT_IMAGE}" \ + --bootstrap +fi +BUILDER_FLAG="--builder=${BUILDER_NAME}" + +build_to_tar() { + local out="$1" + docker buildx build ${BUILDER_FLAG} --no-cache \ + --build-arg "NODE_DIGEST=${NODE_IMAGE_DIGEST}" \ + -f "${REPO_ROOT}/gcp.dockerfile" \ + -o "type=oci,dest=${out},rewrite-timestamp=true" \ + "${REPO_ROOT}" +} + +log "Building reproducible image" +log " Image: ${IMAGE}" +log " Commit: ${COMMIT}" +log " Epoch: ${SOURCE_DATE_EPOCH}" + +build_to_tar "${TMPDIR}/attestor.tar" +log "Build complete" + +# Extract OCI layout and push with crane to preserve the exact digest +# produced by the build. +mkdir -p "${TMPDIR}/oci" +tar -xf "${TMPDIR}/attestor.tar" -C "${TMPDIR}/oci" + +log "Pushing to ${IMAGE}" +crane push "${TMPDIR}/oci" "${IMAGE}" + +DIGEST=$(crane digest "${IMAGE}") +echo +echo "=============================================" +echo "Image: ${IMAGE}" +echo "Digest: ${DIGEST}" +echo "=============================================" + +if [[ "${VERIFY}" == "--verify" ]]; then + log "Rebuilding from scratch to verify reproducibility" + build_to_tar "${TMPDIR}/verify.tar" + + HASH_ORIG=$(sha256sum "${TMPDIR}/attestor.tar" | cut -d' ' -f1) + HASH_VERIFY=$(sha256sum "${TMPDIR}/verify.tar" | cut -d' ' -f1) + + echo + echo "Reproducibility verification:" + echo " Build 1: ${HASH_ORIG}" + echo " Build 2: ${HASH_VERIFY}" + if [[ "${HASH_ORIG}" == "${HASH_VERIFY}" ]]; then + echo " Result: MATCH (bit-for-bit reproducible)" + else + echo " Result: MISMATCH" + echo + echo "Inspect differences with:" + echo " diff <(tar -tvf ${TMPDIR}/attestor.tar | sort) <(tar -tvf ${TMPDIR}/verify.tar | sort)" + exit 1 + fi +fi diff --git a/deploy/deploy.sh b/deploy/deploy.sh new file mode 100755 index 00000000..d56ca3f7 --- /dev/null +++ b/deploy/deploy.sh @@ -0,0 +1,250 @@ +#!/bin/bash +set -e + +# ============================================================================= +# attestor-core Confidential Space deployment helper +# ============================================================================= +# One-shot setup for the attestor-core VM. Idempotent — re-running only +# changes what needs changing. +# +# Steps: +# provision one-time: create service account, grant IAM, reserve IP +# secrets one-time: upload signing key + OPRF material to Secret Manager +# create create the Confidential Space VM +# update update the VM's image reference + tee-env metadata, restart +# status show VM state + recent logs +# +# Usage: +# ./deploy/deploy.sh provision +# ./deploy/deploy.sh secrets +# ./deploy/deploy.sh create v1 +# ./deploy/deploy.sh update v2 +# ./deploy/deploy.sh status +# ============================================================================= + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/build.env" || { echo "Missing deploy/build.env (copy from build.env.example)"; exit 1; } + +# Required (no defaults — must be set in deploy/build.env): +: "${PROJECT_ID:?PROJECT_ID not set in deploy/build.env}" +: "${DOMAIN:?DOMAIN not set in deploy/build.env}" +: "${ACME_EMAIL:?ACME_EMAIL not set in deploy/build.env}" + +# Optional with sensible defaults: +REGISTRY="${REGISTRY:-gcr.io/${PROJECT_ID}}" +ZONE="${ZONE:-asia-south2-a}" +INSTANCE="${INSTANCE:-attestor-prod}" +MACHINE_TYPE="${MACHINE_TYPE:-n2d-standard-2}" +SERVICE_ACCOUNT="${SERVICE_ACCOUNT:-tee-attestor-sa@${PROJECT_ID}.iam.gserviceaccount.com}" +STATIC_IP_NAME="${STATIC_IP_NAME:-attestor-prod-ip}" +REGION="${REGION:-${ZONE%-*}}" +FIREWALL_RULE="${FIREWALL_RULE:-allow-attestor}" +NETWORK_TAG="${NETWORK_TAG:-attestor}" +# Set DEBUG=1 to use the debug Confidential Space image (SSH-able) and +# stream container stdout/stderr to Cloud Logging via the launcher +# (so crashes before our installCloudLogging are visible). +DEBUG_MODE="${DEBUG:-0}" +if [[ "${DEBUG_MODE}" == "1" ]]; then + IMAGE_FAMILY="confidential-space-debug" + LOG_REDIRECT="true" +else + IMAGE_FAMILY="confidential-space" + LOG_REDIRECT="false" +fi + +log() { echo "[$(date '+%H:%M:%S')] $1"; } +err() { echo "[ERROR] $1" >&2; exit 1; } + +cmd_provision() { + log "Creating service account ${SERVICE_ACCOUNT}" + gcloud iam service-accounts describe "${SERVICE_ACCOUNT}" --project="${PROJECT_ID}" >/dev/null 2>&1 \ + || gcloud iam service-accounts create "tee-attestor-sa" \ + --display-name="attestor-core (Confidential Space)" \ + --project="${PROJECT_ID}" + + log "Granting roles/secretmanager.secretAccessor" + gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${SERVICE_ACCOUNT}" \ + --role="roles/secretmanager.secretAccessor" \ + --condition=None >/dev/null + + log "Granting roles/secretmanager.secretVersionAdder (for ACME cert + account state writeback)" + gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${SERVICE_ACCOUNT}" \ + --role="roles/secretmanager.secretVersionAdder" \ + --condition=None >/dev/null + + log "Granting roles/secretmanager.admin (for attestor to create its own cert/account secrets on first boot)" + gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${SERVICE_ACCOUNT}" \ + --role="roles/secretmanager.admin" \ + --condition=None >/dev/null + + log "Granting roles/logging.logWriter" + gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${SERVICE_ACCOUNT}" \ + --role="roles/logging.logWriter" \ + --condition=None >/dev/null + + log "Granting roles/confidentialcomputing.workloadUser" + gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${SERVICE_ACCOUNT}" \ + --role="roles/confidentialcomputing.workloadUser" \ + --condition=None >/dev/null + + # gcr.io in this project is fronted by Artifact Registry, so the + # right grant is artifactregistry.reader on the gcr.io repo (not the + # legacy GCS bucket). The repo lives in location=us by default. + log "Granting roles/artifactregistry.reader on gcr.io (so launcher can pull)" + gcloud artifacts repositories add-iam-policy-binding gcr.io \ + --location=us \ + --member="serviceAccount:${SERVICE_ACCOUNT}" \ + --role=roles/artifactregistry.reader \ + --project="${PROJECT_ID}" >/dev/null + + log "Reserving static external IP ${STATIC_IP_NAME} in ${REGION}" + gcloud compute addresses describe "${STATIC_IP_NAME}" \ + --region="${REGION}" --project="${PROJECT_ID}" >/dev/null 2>&1 \ + || gcloud compute addresses create "${STATIC_IP_NAME}" \ + --region="${REGION}" --project="${PROJECT_ID}" + + IP=$(gcloud compute addresses describe "${STATIC_IP_NAME}" \ + --region="${REGION}" --project="${PROJECT_ID}" --format='value(address)') + log "Static IP: ${IP}" + log "Set DNS A record: ${DOMAIN} -> ${IP}" + + log "Creating firewall rule ${FIREWALL_RULE}" + gcloud compute firewall-rules describe "${FIREWALL_RULE}" --project="${PROJECT_ID}" >/dev/null 2>&1 \ + || gcloud compute firewall-rules create "${FIREWALL_RULE}" \ + --allow=tcp:80,tcp:443 \ + --target-tags="${NETWORK_TAG}" \ + --project="${PROJECT_ID}" +} + +cmd_secrets() { + [[ -z "${SIGNING_KEY:-}" ]] && err "SIGNING_KEY env var required (0x-prefixed hex)" + [[ -z "${TOPRF_PUBLIC_KEY:-}" ]] && err "TOPRF_PUBLIC_KEY env var required" + [[ -z "${TOPRF_SHARE_PRIVATE_KEY:-}" ]] && err "TOPRF_SHARE_PRIVATE_KEY env var required" + [[ -z "${TOPRF_SHARE_PUBLIC_KEY:-}" ]] && err "TOPRF_SHARE_PUBLIC_KEY env var required" + + upload() { + local id="$1" value="$2" + if gcloud secrets describe "${id}" --project="${PROJECT_ID}" >/dev/null 2>&1; then + printf '%s' "${value}" | gcloud secrets versions add "${id}" \ + --project="${PROJECT_ID}" --data-file=- + else + printf '%s' "${value}" | gcloud secrets create "${id}" \ + --replication-policy=automatic \ + --project="${PROJECT_ID}" --data-file=- + fi + log "Uploaded ${id}" + } + upload attestor-signing-key "${SIGNING_KEY}" + upload attestor-toprf-public "${TOPRF_PUBLIC_KEY}" + upload attestor-toprf-share-private "${TOPRF_SHARE_PRIVATE_KEY}" + upload attestor-toprf-share-public "${TOPRF_SHARE_PUBLIC_KEY}" +} + +build_metadata() { + local image_ref="$1" + cat <&1 | tail -2 || true + cmd_create "${tag}" +} + +case "${1:-}" in + provision) cmd_provision ;; + secrets) cmd_secrets ;; + create) cmd_create "${2:-}" ;; + update) cmd_update "${2:-}" ;; + recreate) cmd_recreate "${2:-}" ;; + status) cmd_status ;; + *) echo "Usage: $0 {provision|secrets|create |update |recreate |status}"; exit 1 ;; +esac diff --git a/docs/gcp-confidential-space.md b/docs/gcp-confidential-space.md new file mode 100644 index 00000000..73468636 --- /dev/null +++ b/docs/gcp-confidential-space.md @@ -0,0 +1,182 @@ +# Running attestor-core in GCP Confidential Space + +Operator guide for deploying the attestor inside a GCP Confidential Space +VM. The image runs the same code as the standalone deployment plus a TEE +bootstrap that: + +- terminates TLS inside the enclave with a Let's Encrypt certificate; +- pulls the signing key and OPRF share material from Secret Manager; +- attaches a GCP attestation JWT to every signed claim + (`signatures.claimAttestation` on the wire); +- streams logs to Cloud Logging. + +All TEE behavior is gated on `ENCLAVE_MODE=true`. Without it, the binary +behaves identically to the existing non-TEE deployment. + +## Configuration + +`deploy/deploy.sh` reads `deploy/build.env` (gitignored). Required: + +- `PROJECT_ID` — GCP project that holds the registry, secrets, and VM. +- `DOMAIN` — the public hostname the attestor terminates TLS on. +- `ACME_EMAIL` — passed to Let's Encrypt during account registration. + +Optional overrides (sensible defaults in `deploy.sh`): + +| Setting | Default | +|---|---| +| Registry | `gcr.io/${PROJECT_ID}` | +| Service account | `tee-attestor-sa@${PROJECT_ID}.iam.gserviceaccount.com` | +| Zone | `asia-south2-a` | +| Machine type | `n2d-standard-2` | +| Instance name | `attestor-prod` | +| Static IP name | `attestor-prod-ip` | +| Firewall rule | `allow-attestor` (tcp:80,443 on tag `attestor`) | + +## One-time prerequisites + +```bash +# 1. Create deploy/build.env from the example. +cp deploy/build.env.example deploy/build.env +# (edit if you want non-defaults) + +# 2. Provision SA, IAM grants, static IP, firewall. +./deploy/deploy.sh provision +# Note the printed IP — set the DNS A record for the domain to point +# at it BEFORE creating the VM, otherwise the first ACME order fails +# and the VM logs the failure (recoverable: see "First-boot recovery"). + +# 3. Generate keys locally and upload to Secret Manager. +SIGNING_KEY=$(openssl rand -hex 32) +eval "$(npm run --silent generate:toprf-keys 2>&1 | grep -E '^(TOPRF|TOPRF_SHARE)')" +SIGNING_KEY="0x${SIGNING_KEY}" \ +TOPRF_PUBLIC_KEY="${TOPRF_PUBLIC_KEY}" \ +TOPRF_SHARE_PRIVATE_KEY="${TOPRF_SHARE_PRIVATE_KEY}" \ +TOPRF_SHARE_PUBLIC_KEY="${TOPRF_SHARE_PUBLIC_KEY}" \ + ./deploy/deploy.sh secrets +``` + +## Build & push the image + +The image is reproducible — same git commit + same builder image digest +yields the same OCI tarball sha256, which means the same image digest in +the registry, which is what the GCP attestation pins via +`submods.container.image_digest`. + +```bash +# Build, push, and verify reproducibility: +./deploy/build.sh v1 --verify + +# Output ends with: +# Image: gcr.io/${PROJECT_ID}/attestor-core:v1 +# Digest: sha256:... +# Result: MATCH (bit-for-bit reproducible) +``` + +Customers can rebuild from the same git commit and confirm the digest +matches what you've deployed. + +## Create the VM + +```bash +./deploy/deploy.sh create v1 +``` + +This creates the VM (`${INSTANCE}` in `${ZONE}`) with: +- Confidential SEV, shielded secure boot, terminate-on-host-maintenance. +- The static IP from `provision`. +- Image pinned by digest (so the running image cannot be swapped under + the attestation). +- All `tee-env-*` metadata for the bootstrap. + +## First-boot recovery + +If DNS isn't ready before VM creation, ACME HTTP-01 fails on first boot. +The attestor logs the failure to Cloud Logging and does not start the +HTTPS listener. To recover: + +1. Confirm the DNS A record points at the static IP and has propagated + (`dig +short ${DOMAIN}`). +2. Restart the VM: `gcloud compute instances reset ${INSTANCE} + --zone=${ZONE} --project=${PROJECT_ID}`. The bootstrap retries the + ACME order on every start. + +## Updates + +```bash +# Build a new tag. +./deploy/build.sh v2 + +# Re-pin the VM at the new digest and restart. +./deploy/deploy.sh update v2 +``` + +The cert and ACME account state in Secret Manager survive the restart; +the renewal loop picks up where it left off. + +## Inspecting + +```bash +./deploy/deploy.sh status +``` + +Or query Cloud Logging directly: + +```bash +gcloud logging read \ + "resource.type=\"gce_instance\" AND logName=\"projects/${PROJECT_ID}/logs/attestor-core\"" \ + --project=${PROJECT_ID} \ + --freshness=1h --limit=50 \ + --format='table(timestamp,severity,jsonPayload.message)' +``` + +## Verifying a TEE-signed claim + +A consumer who wants end-to-end trust: + +```ts +import { validateGcpAttestationAndExtractKey } + from '@reclaimprotocol/attestor-core/lib/server/utils/gcp-attestation.js' + +const { signatures } = claimTunnelResponse +if (!signatures?.claimAttestation?.report?.length) { + throw new Error('not a TEE-signed claim') +} + +const r = await validateGcpAttestationAndExtractKey( + signatures.claimAttestation.report +) +if (!r.isValid) throw new Error(r.errors.join(', ')) +if (r.userDataType !== 'attestor') throw new Error('wrong nonce kind') +const expected = signatures.attestorAddress.toLowerCase() +const got = '0x' + Buffer.from(r.ethAddress!).toString('hex').toLowerCase() +if (got !== expected) throw new Error('attestation does not match signer') + +// r.pcr0 holds submods.container.image_digest. Pin this against the +// image_digest you built and pushed (./deploy/build.sh prints it). +``` + +## How it works at boot + +`src/scripts/start-server.ts` calls `bootstrapTee()` when +`ENCLAVE_MODE=true`. `src/server/tee/bootstrap.ts`: + +1. `installCloudLogging` — replaces pino's stdout destination with a + stream that posts entries to Cloud Logging under `LOG_NAME`. From + this point on every log line lands in Cloud Logging. +2. `loadSecretsIntoEnv` — fetches `attestor-signing-key`, + `attestor-toprf-share-private`, `attestor-toprf-share-public`, + `attestor-toprf-public` from Secret Manager and populates + `process.env` so the existing `getEnvVariable` consumers see them. +3. `bootstrapCertificate` — tries `attestor-tls-cert-` first; + if absent or within 14 days of expiry, runs ACME HTTP-01 against + `ACME_DIRECTORY_URL` (port 80 bound only for the order, then + released) and persists the cert + ACME account state. +4. `startRenewalLoop` — daily check, hot-swap via `SNICallback`. +5. `startAttestationRefresh` — calls the launcher socket at + `/run/container_launcher/teeserver.sock` `/v1/token` with two + nonces: + - `attestor_public_key:` + - `attestor_cert_hash:` + JWT cached 5 minutes, refreshed every 4. Every claim response + carries the cached JWT in `signatures.claimAttestation`. diff --git a/eslint.config.js b/eslint.config.js index a6aa34f5..ae64be1c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -46,7 +46,7 @@ export default [ // Import rules 'no-relative-import-paths/no-relative-import-paths': [ 'error', - { allowSameFolder: false, rootDir: '' }, + { allowSameFolder: false, rootDir: 'src', prefix: '#src' }, ], 'simple-import-sort/imports': 'error', diff --git a/gcp.dockerfile b/gcp.dockerfile new file mode 100644 index 00000000..a6aea0e9 --- /dev/null +++ b/gcp.dockerfile @@ -0,0 +1,86 @@ +# check=skip=SecretsUsedInArgOrEnv +# syntax=docker/dockerfile:1.7 +# Reproducible build for the GCP Confidential Space deployment. +# Same source + same builder image digest = same OCI tarball sha256. +# +# Base image: node:24.13.0 (Debian bookworm). Pinned by digest so apt +# repositories cannot drift between builds. Update both the tag and the +# digest in lockstep when bumping Node. +ARG NODE_DIGEST=sha256:1de022d8459f896fff2e7b865823699dc7a8d5567507e8b87b14a7442e07f206 + +FROM node:24.13.0@${NODE_DIGEST} AS builder + +# git is required for the few git-resolved entries in package-lock.json +# (@reclaimprotocol/tls, snarkjs). unzip is required by the +# zk-symmetric-crypto download script. +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ + apt-get install -y --no-install-recommends git unzip ca-certificates && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Two-step copy so the npm ci layer caches independently of source edits. +COPY package.json package-lock.json tsconfig.json tsconfig.build.json ./ + +# package.json declares "prepare": "npm run build", which runs during +# `npm ci` and needs src/ to exist. Stub it so the prepare step is a +# no-op; the real build happens after the source COPY below. The same +# trick is used in attestor.dockerfile. +RUN mkdir -p src/scripts && \ + printf 'console.log("stub")\n' > src/index.ts && \ + printf 'console.log("stub")\n' > src/scripts/build-lib.ts + +# `npm ci` resolves strictly from the lockfile; re2's install script first +# tries to download a prebuilt .node from the maintainer's GitHub release +# and only falls back to compiling. The prebuilt path is bit-identical +# across builds; the fallback is not, so we want the prebuilt to succeed. +RUN --mount=type=cache,target=/root/.npm,sharing=locked \ + npm ci --no-audit --no-fund \ + --fetch-retries=10 \ + --fetch-retry-mintimeout=60000 \ + --fetch-retry-maxtimeout=300000 \ + --fetch-timeout=600000 + +# Replace stubs with the real source. +COPY . . + +RUN npm run build && \ + npm run download:zk-files && \ + npm run build:browser && \ + npm prune --omit=dev + +# --------------------------------------------------------------------------- + +FROM node:24.13.0@${NODE_DIGEST} AS runtime + +# CA certs come from the base image; nothing else needed at runtime. +WORKDIR /app + +# Copy the whole built /app from the builder stage. We rely on the +# builder having pruned dev deps already (`npm prune --omit=dev`). +# `npm run start` invokes `node --experimental-strip-types src/scripts/start-server.ts` +# so we need the source tree, not just lib/. +COPY --from=builder /app /app + +# Declare every env var that the deployer is allowed to override via +# instance metadata. Confidential Space enforces a strict policy: only +# variables present here (and listed in the launch_policy label below) +# can be set with `tee-env-` at VM creation. +ENV ENCLAVE_MODE="" +ENV ENCLAVE_DOMAIN="" +ENV ACME_EMAIL="" +ENV ACME_DIRECTORY_URL="" +ENV GOOGLE_PROJECT_ID="" +ENV LOG_NAME="" +ENV LOG_LEVEL="" +ENV PORT="" +ENV HTTP_PORT="" +ENV HTTPS_PORT="" +ENV DISABLE_BGP_CHECKS="" + +LABEL "tee.launch_policy.allow_env_override"="ENCLAVE_MODE,ENCLAVE_DOMAIN,ACME_EMAIL,ACME_DIRECTORY_URL,GOOGLE_PROJECT_ID,LOG_NAME,LOG_LEVEL,PORT,HTTP_PORT,HTTPS_PORT,DISABLE_BGP_CHECKS" + +EXPOSE 80 443 +CMD ["npm", "run", "start"] diff --git a/package-lock.json b/package-lock.json index 4a7a80fe..e1338d16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,23 @@ { "name": "@reclaimprotocol/attestor-core", - "version": "5.0.2-beta.4", + "version": "5.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@reclaimprotocol/attestor-core", - "version": "5.0.2-beta.4", + "version": "5.0.2", "license": "AGPL-3.0-only", "dependencies": { "@bufbuild/protobuf": "^2.11.0", + "@google-cloud/logging": "^11.2.1", + "@google-cloud/secret-manager": "^6.1.2", "@peculiar/asn1-x509": "^2.6.1", "@peculiar/webcrypto": "^1.5.0", "@peculiar/x509": "^1.14.3", "@reclaimprotocol/tls": "github:reclaimprotocol/tls", "@reclaimprotocol/zk-symmetric-crypto": "^5.1.3", + "acme-client": "^5.4.0", "ajv": "^8.18.0", "bs58": "^6.0.0", "canonicalize": "^2.1.0", @@ -1768,6 +1771,556 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/@google-cloud/common": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.2.tgz", + "integrity": "sha512-V7bmBKYQyu0eVG2BFejuUjlBt+zrya6vtsKdY+JxMM/dNntPF41vZ9+LhOshEUH01zOHEqBSvI7Dad7ZS6aUeA==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "extend": "^3.0.2", + "google-auth-library": "^9.0.0", + "html-entities": "^2.5.2", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/common/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@google-cloud/common/node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@google-cloud/common/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/common/node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/common/node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/common/node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/common/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@google-cloud/common/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@google-cloud/common/node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/common/node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/common/node_modules/teeny-request/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@google-cloud/common/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/@google-cloud/common/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/@google-cloud/common/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/@google-cloud/logging": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-11.2.1.tgz", + "integrity": "sha512-2h9HBJG3OAsvzXmb81qXmaTPfXYU7KJTQUxunoOKFGnY293YQ/eCkW1Y5mHLocwpEqeqQYT/Qvl6Tk+Q7PfStw==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/common": "^5.0.0", + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "4.0.0", + "@opentelemetry/api": "^1.7.0", + "arrify": "^2.0.1", + "dot-prop": "^6.0.0", + "eventid": "^2.0.0", + "extend": "^3.0.2", + "gcp-metadata": "^6.0.0", + "google-auth-library": "^9.0.0", + "google-gax": "^4.0.3", + "on-finished": "^2.3.0", + "pumpify": "^2.0.1", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/logging/node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@google-cloud/logging/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@google-cloud/logging/node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@google-cloud/logging/node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@google-cloud/logging/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/logging/node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/logging/node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/logging/node_modules/google-gax": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz", + "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/logging/node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/logging/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@google-cloud/logging/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@google-cloud/logging/node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "license": "Apache-2.0", + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/logging/node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/logging/node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/logging/node_modules/teeny-request/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@google-cloud/logging/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/@google-cloud/logging/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/@google-cloud/logging/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/paginator/node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/secret-manager": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@google-cloud/secret-manager/-/secret-manager-6.1.2.tgz", + "integrity": "sha512-X4GiHC1OsZU8LOcnM/hk7Q+W/orZLSJz6IAvBJrjaQCj1pSRHkTHJnU87A6E5G8/ubIJjL6vs1GJ8f17TkDzPw==", + "license": "Apache-2.0", + "dependencies": { + "google-gax": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1836,6 +2389,102 @@ "ffjavascript": "^0.2.48" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -2478,6 +3127,16 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -2851,6 +3510,80 @@ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", "license": "MIT" }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", + "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "license": "BSD-3-Clause" + }, "node_modules/@reclaimprotocol/tls": { "version": "0.1.2", "resolved": "git+ssh://git@github.com/reclaimprotocol/tls.git#c41fb4a02c8ae2da47112c5f25036ea9a2f2b983", @@ -2942,6 +3675,15 @@ "eslint": "^9.0.0 || ^10.0.0" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", + "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", @@ -3035,6 +3777,12 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT" + }, "node_modules/@types/chance": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@types/chance/-/chance-1.1.7.tgz", @@ -3146,6 +3894,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -3164,24 +3918,72 @@ "version": "22.19.15", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/prettier": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "dev": true, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/request": { + "version": "2.48.13", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz", + "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==", + "license": "MIT", + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.5" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@types/request/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT" }, "node_modules/@types/send": { @@ -3219,7 +4021,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "dev": true, "license": "MIT" }, "node_modules/@types/ws": { @@ -3527,6 +4328,22 @@ "node": ">=6" } }, + "node_modules/acme-client": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/acme-client/-/acme-client-5.4.0.tgz", + "integrity": "sha512-mORqg60S8iML6XSmVjqjGHJkINrCGLMj2QvDmFzI9vIlv1RGlyjmw3nrzaINJjkNsYXC41XhhD5pfy7CtuGcbA==", + "license": "MIT", + "dependencies": { + "@peculiar/x509": "^1.11.0", + "asn1js": "^3.0.5", + "axios": "^1.7.2", + "debug": "^4.3.5", + "node-forge": "^1.3.1" + }, + "engines": { + "node": ">= 16" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -3661,7 +4478,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3671,7 +4487,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3951,6 +4766,12 @@ "async-value": "^1.2.2" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -3976,6 +4797,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, "node_modules/b4a": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", @@ -4368,6 +5200,12 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -4712,7 +5550,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -4747,7 +5584,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4760,9 +5596,20 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/command-line-args": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", @@ -5093,7 +5940,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5114,6 +5960,15 @@ "node": ">=8" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -5300,6 +6155,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -5463,6 +6327,33 @@ "node": ">= 0.4" } }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5659,7 +6550,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -5861,7 +6751,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -5950,7 +6839,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6559,6 +7447,28 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, + "node_modules/eventid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/eventid/-/eventid-2.0.1.tgz", + "integrity": "sha512-sPNTqiMokAvV048P2c9+foqVJzk49o6d4e0D/sq5jog3pw+4kBgyR0gaM1FM7Mx6Kzd9dztesh9oYz1LWWOpzw==", + "license": "Apache-2.0", + "dependencies": { + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eventid/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -6626,6 +7536,12 @@ "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "license": "Apache-2.0" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/eyes": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", @@ -6728,6 +7644,29 @@ } } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/ffjavascript": { "version": "0.2.63", "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.2.63.tgz", @@ -6866,6 +7805,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -6882,6 +7841,62 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded-parse": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", @@ -6987,6 +8002,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -7012,7 +8055,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7191,6 +8233,54 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-gax": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-5.0.6.tgz", + "integrity": "sha512-1kGbqVQBZPAAu4+/R1XxPQKP0ydbNYoLAr4l0ZO2bMV0kLyLW4I1gAk++qBLWt7DPORTzmWRMsCZe86gDjShJA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.12.6", + "@grpc/proto-loader": "^0.8.0", + "duplexify": "^4.1.3", + "google-auth-library": "^10.1.0", + "google-logging-utils": "^1.1.1", + "node-fetch": "^3.3.2", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^3.0.0", + "protobufjs": "^7.5.3", + "retry-request": "^8.0.0", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -7209,6 +8299,77 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/gtoken/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gtoken/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/gtoken/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/gtoken/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/gtoken/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/handlebars": { "version": "4.7.9", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", @@ -7318,7 +8479,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -7405,6 +8565,22 @@ "dev": true, "license": "ISC" }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -7912,7 +9088,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8029,7 +9204,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8097,7 +9271,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8227,7 +9400,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -8335,6 +9507,21 @@ "node": ">= 0.4" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.9.4", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", @@ -9365,6 +10552,27 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -9465,7 +10673,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isfunction": { @@ -9550,6 +10757,12 @@ "integrity": "sha512-jLlHnlsPSJjpwUfcNyUxXCl33AYg2cHhIf9QhGL2T4iPT0XPB+xP1LRKFPgIg1M/sg9kAJvy94w9CzBNrfnstA==", "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -9784,6 +10997,27 @@ "node": ">=4" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -10050,6 +11284,26 @@ "integrity": "sha512-+I10J3wKNoKddNxn0CNpoZ3eTZuqxjNM3b1GImVx22+ePI+Y15P8g/j3WsbP0fhzzrFzrtjOAoq5NCCucswXOQ==", "license": "MIT" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-exports-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", @@ -10079,6 +11333,33 @@ "semver": "bin/semver.js" } }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-gyp": { "version": "12.2.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", @@ -10257,6 +11538,15 @@ "integrity": "sha512-CsubGNxhIEChNY4cXYuA6KXafztzHqzLLZ/y3Kasf3A+sa3lL9thq3z+7o0pZqzEinjXT6lXDPAfVWI59dUyzQ==", "license": "MIT" }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-identity-map": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/object-identity-map/-/object-identity-map-1.0.2.tgz", @@ -10541,6 +11831,12 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -10631,7 +11927,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10973,6 +12268,72 @@ "react-is": "^16.13.1" } }, + "node_modules/proto3-json-serializer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-3.0.4.tgz", + "integrity": "sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==", + "license": "Apache-2.0", + "dependencies": { + "protobufjs": "^7.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/protobufjs": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.6.tgz", + "integrity": "sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.1", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "license": "MIT", + "dependencies": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -11522,7 +12883,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11622,6 +12982,101 @@ "node": ">=10" } }, + "node_modules/retry-request": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-8.0.2.tgz", + "integrity": "sha512-JzFPAfklk1kjR1w76f0QOIhoDkNkSqW8wYKT08n9yysTmZfB+RQ2QoXoTAeOi1HD9ZipTyTAZg3c4pM/jeqgSw==", + "license": "MIT", + "dependencies": { + "extend": "^3.0.2", + "teeny-request": "^10.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -11849,7 +13304,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -11862,7 +13316,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12250,6 +13703,21 @@ "readable-stream": "^3.0.6" } }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -12305,7 +13773,21 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -12418,7 +13900,19 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -12474,6 +13968,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12552,6 +14052,21 @@ "node": ">=18" } }, + "node_modules/teeny-request": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.2.tgz", + "integrity": "sha512-Xj0ZAQ0CeuQn6UxCDPLbFRlgcSTUEyO3+wiepr2grjIjyL/lMMs1Z4OwXn8kLvn/V1OuaEP0UY7Na6UDNNsYrQ==", + "license": "Apache-2.0", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "stream-events": "^1.0.5" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -13304,7 +14819,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unicode-byte-truncate": { @@ -13393,6 +14907,20 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -13479,6 +15007,15 @@ "wasmbuilder": "0.0.16" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/web-worker": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", @@ -13526,7 +15063,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -13672,7 +15208,24 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -13741,7 +15294,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -13776,7 +15328,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -13805,7 +15356,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" diff --git a/package.json b/package.json index 37416f4d..93f7f0ff 100644 --- a/package.json +++ b/package.json @@ -82,11 +82,14 @@ "license": "AGPL-3.0-only", "dependencies": { "@bufbuild/protobuf": "^2.11.0", + "@google-cloud/logging": "^11.2.1", + "@google-cloud/secret-manager": "^6.1.2", "@peculiar/asn1-x509": "^2.6.1", "@peculiar/webcrypto": "^1.5.0", "@peculiar/x509": "^1.14.3", "@reclaimprotocol/tls": "github:reclaimprotocol/tls", "@reclaimprotocol/zk-symmetric-crypto": "^5.1.3", + "acme-client": "^5.4.0", "ajv": "^8.18.0", "bs58": "^6.0.0", "canonicalize": "^2.1.0", diff --git a/proto/api.proto b/proto/api.proto index 1f74a4d8..2ffb2ce9 100644 --- a/proto/api.proto +++ b/proto/api.proto @@ -1,6 +1,8 @@ syntax = "proto3"; package reclaim_attestor; +import "tee-bundle.proto"; + enum TranscriptMessageSenderType { TRANSCRIPT_MESSAGE_SENDER_TYPE_UNKNOWN = 0; TRANSCRIPT_MESSAGE_SENDER_TYPE_CLIENT = 1; @@ -338,6 +340,12 @@ message ClaimTeeBundleResponse { * structure with empty "signatures" field */ bytes resultSignature = 3; + /** + * Attestation from the TEE the attestor is running in, + * binding the attestor's signing key to the enclave. + * Empty when the attestor is not running in a TEE. + */ + teeproto.AttestationReport claimAttestation = 4; } } @@ -415,6 +423,12 @@ message ClaimTunnelResponse { * structure with empty "signatures" field */ bytes resultSignature = 3; + /** + * Attestation from the TEE the attestor is running in, + * binding the attestor's signing key to the enclave. + * Empty when the attestor is not running in a TEE. + */ + teeproto.AttestationReport claimAttestation = 4; } } diff --git a/src/proto/api.ts b/src/proto/api.ts index bb8cf552..6077fcd1 100644 --- a/src/proto/api.ts +++ b/src/proto/api.ts @@ -1,11 +1,12 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: -// protoc-gen-ts_proto v2.5.0 +// protoc-gen-ts_proto v2.11.6 // protoc v6.31.1 // source: api.proto /* eslint-disable */ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; +import { AttestationReport } from "./tee-bundle.ts"; export const protobufPackage = "reclaim_attestor"; @@ -628,6 +629,12 @@ export interface ClaimTeeBundleResponse_Signatures { * structure with empty "signatures" field */ resultSignature: Uint8Array; + /** + * Attestation from the TEE the attestor is running in, + * binding the attestor's signing key to the enclave. + * Empty when the attestor is not running in a TEE. + */ + claimAttestation: AttestationReport | undefined; } export interface ClaimTunnelRequest { @@ -700,6 +707,12 @@ export interface ClaimTunnelResponse_Signatures { * structure with empty "signatures" field */ resultSignature: Uint8Array; + /** + * Attestation from the TEE the attestor is running in, + * binding the attestor's signing key to the enclave. + * Empty when the attestor is not running in a TEE. + */ + claimAttestation: AttestationReport | undefined; } export interface RequestClaimOnAvsRequest { @@ -914,7 +927,7 @@ function createBaseClaimContext(): ClaimContext { export const ClaimContext: MessageFns = { encode(message: ClaimContext, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { - Object.entries(message.extractedParameters).forEach(([key, value]) => { + globalThis.Object.entries(message.extractedParameters).forEach(([key, value]: [string, string]) => { ClaimContext_ExtractedParametersEntry.encode({ key: key as any, value }, writer.uint32(10).fork()).join(); }); if (message.providerHash !== "") { @@ -925,7 +938,7 @@ export const ClaimContext: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ClaimContext { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseClaimContext(); while (reader.pos < end) { const tag = reader.uint32(); @@ -961,10 +974,13 @@ export const ClaimContext: MessageFns = { fromJSON(object: any): ClaimContext { return { extractedParameters: isObject(object.extractedParameters) - ? Object.entries(object.extractedParameters).reduce<{ [key: string]: string }>((acc, [key, value]) => { - acc[key] = String(value); - return acc; - }, {}) + ? (globalThis.Object.entries(object.extractedParameters) as [string, any][]).reduce( + (acc: { [key: string]: string }, [key, value]: [string, any]) => { + acc[key] = globalThis.String(value); + return acc; + }, + {}, + ) : {}, providerHash: isSet(object.providerHash) ? globalThis.String(object.providerHash) : "", }; @@ -973,7 +989,7 @@ export const ClaimContext: MessageFns = { toJSON(message: ClaimContext): unknown { const obj: any = {}; if (message.extractedParameters) { - const entries = Object.entries(message.extractedParameters); + const entries = globalThis.Object.entries(message.extractedParameters) as [string, string][]; if (entries.length > 0) { obj.extractedParameters = {}; entries.forEach(([k, v]) => { @@ -992,15 +1008,13 @@ export const ClaimContext: MessageFns = { }, fromPartial(object: DeepPartial): ClaimContext { const message = createBaseClaimContext(); - message.extractedParameters = Object.entries(object.extractedParameters ?? {}).reduce<{ [key: string]: string }>( - (acc, [key, value]) => { + message.extractedParameters = (globalThis.Object.entries(object.extractedParameters ?? {}) as [string, string][]) + .reduce((acc: { [key: string]: string }, [key, value]: [string, string]) => { if (value !== undefined) { acc[key] = globalThis.String(value); } return acc; - }, - {}, - ); + }, {}); message.providerHash = object.providerHash ?? ""; return message; }, @@ -1023,7 +1037,7 @@ export const ClaimContext_ExtractedParametersEntry: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ProviderClaimData { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseProviderClaimData(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1258,7 +1272,7 @@ export const ProviderClaimInfo: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ProviderClaimInfo { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseProviderClaimInfo(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1350,7 +1364,7 @@ export const ErrorData: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ErrorData { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseErrorData(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1448,7 +1462,7 @@ export const CreateTunnelRequest: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): CreateTunnelRequest { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseCreateTunnelRequest(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1560,7 +1574,7 @@ export const FetchCertificateBytesRequest: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): DisconnectTunnelRequest { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseDisconnectTunnelRequest(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1731,7 +1745,7 @@ export const Empty: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): Empty { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseEmpty(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1780,7 +1794,7 @@ export const TunnelMessage: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): TunnelMessage { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseTunnelMessage(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1856,7 +1870,7 @@ export const TunnelDisconnectEvent: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): TunnelDisconnectEvent { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseTunnelDisconnectEvent(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1934,7 +1948,7 @@ export const MessageReveal: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): MessageReveal { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseMessageReveal(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2019,7 +2033,7 @@ export const MessageReveal_MessageRevealDirect: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): MessageReveal_ZKProof { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseMessageReveal_ZKProof(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2402,7 +2416,7 @@ export const MessageReveal_TOPRFProof: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): MessageReveal_TOPRFProof { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseMessageReveal_TOPRFProof(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2496,7 +2510,7 @@ export const TOPRFPayload: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): TOPRFPayload { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseTOPRFPayload(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2589,7 +2603,7 @@ export const DataSlice: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): DataSlice { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseDataSlice(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2674,7 +2688,7 @@ export const ClaimRequestData: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ClaimRequestData { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseClaimRequestData(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2792,7 +2806,7 @@ export const ClaimTeeBundleRequest: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ClaimTeeBundleRequest { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseClaimTeeBundleRequest(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2884,7 +2898,7 @@ export const ClaimTeeBundleRequest_Signatures: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ClaimTeeBundleResponse { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseClaimTeeBundleResponse(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3047,7 +3061,12 @@ export const ClaimTeeBundleResponse: MessageFns = { }; function createBaseClaimTeeBundleResponse_Signatures(): ClaimTeeBundleResponse_Signatures { - return { attestorAddress: "", claimSignature: new Uint8Array(0), resultSignature: new Uint8Array(0) }; + return { + attestorAddress: "", + claimSignature: new Uint8Array(0), + resultSignature: new Uint8Array(0), + claimAttestation: undefined, + }; } export const ClaimTeeBundleResponse_Signatures: MessageFns = { @@ -3061,12 +3080,15 @@ export const ClaimTeeBundleResponse_Signatures: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ClaimTunnelRequest { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseClaimTunnelRequest(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3324,7 +3363,7 @@ export const ClaimTunnelRequest_Signatures: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ClaimTunnelResponse { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseClaimTunnelResponse(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3581,7 +3620,12 @@ export const ClaimTunnelResponse: MessageFns = { }; function createBaseClaimTunnelResponse_Signatures(): ClaimTunnelResponse_Signatures { - return { attestorAddress: "", claimSignature: new Uint8Array(0), resultSignature: new Uint8Array(0) }; + return { + attestorAddress: "", + claimSignature: new Uint8Array(0), + resultSignature: new Uint8Array(0), + claimAttestation: undefined, + }; } export const ClaimTunnelResponse_Signatures: MessageFns = { @@ -3595,12 +3639,15 @@ export const ClaimTunnelResponse_Signatures: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): RequestClaimOnAvsRequest { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseRequestClaimOnAvsRequest(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3786,7 +3850,7 @@ export const RequestClaimOnAvsResponse: MessageFns = decode(input: BinaryReader | Uint8Array, length?: number): RequestClaimOnAvsResponse { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseRequestClaimOnAvsResponse(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3878,7 +3942,7 @@ export const CompleteClaimOnAvsRequest: MessageFns = decode(input: BinaryReader | Uint8Array, length?: number): CompleteClaimOnAvsRequest { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseCompleteClaimOnAvsRequest(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3967,7 +4031,7 @@ export const CompleteClaimOnAvsResponse: MessageFns decode(input: BinaryReader | Uint8Array, length?: number): CompleteClaimOnAvsResponse { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseCompleteClaimOnAvsResponse(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4042,7 +4106,7 @@ export const CreateTaskOnMechainRequest: MessageFns decode(input: BinaryReader | Uint8Array, length?: number): CreateTaskOnMechainRequest { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseCreateTaskOnMechainRequest(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4106,7 +4170,7 @@ export const CreateTaskOnMechainResponse: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): AuthenticatedUserData { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseAuthenticatedUserData(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4305,7 +4369,7 @@ export const AuthenticationRequest: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): AuthenticationRequest { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseAuthenticationRequest(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4386,7 +4450,7 @@ export const InitRequest: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): InitRequest { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseInitRequest(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4477,7 +4541,7 @@ export const InitResponse: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): InitResponse { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseInitResponse(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4553,7 +4617,7 @@ export const TOPRFRequest: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): TOPRFRequest { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseTOPRFRequest(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4640,7 +4704,7 @@ export const TOPRFResponse: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): TOPRFResponse { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseTOPRFResponse(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4837,7 +4901,7 @@ export const RPCMessage: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): RPCMessage { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseRPCMessage(); while (reader.pos < end) { const tag = reader.uint32(); @@ -5307,7 +5371,7 @@ export const RPCMessages: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): RPCMessages { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseRPCMessages(); while (reader.pos < end) { const tag = reader.uint32(); @@ -5357,7 +5421,7 @@ export const RPCMessages: MessageFns = { function bytesFromBase64(b64: string): Uint8Array { if ((globalThis as any).Buffer) { - return Uint8Array.from(globalThis.Buffer.from(b64, "base64")); + return Uint8Array.from((globalThis as any).Buffer.from(b64, "base64")); } else { const bin = globalThis.atob(b64); const arr = new Uint8Array(bin.length); @@ -5370,7 +5434,7 @@ function bytesFromBase64(b64: string): Uint8Array { function base64FromBytes(arr: Uint8Array): string { if ((globalThis as any).Buffer) { - return globalThis.Buffer.from(arr).toString("base64"); + return (globalThis as any).Buffer.from(arr).toString("base64"); } else { const bin: string[] = []; arr.forEach((byte) => { diff --git a/src/proto/tee-bundle.ts b/src/proto/tee-bundle.ts index 06c8941b..a371942e 100644 --- a/src/proto/tee-bundle.ts +++ b/src/proto/tee-bundle.ts @@ -1,6 +1,6 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: -// protoc-gen-ts_proto v2.5.0 +// protoc-gen-ts_proto v2.11.6 // protoc v6.31.1 // source: tee-bundle.proto @@ -207,7 +207,7 @@ export const RequestRedactionRange: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): RequestRedactionRange { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseRequestRedactionRange(); while (reader.pos < end) { const tag = reader.uint32(); @@ -296,7 +296,7 @@ export const ResponseRedactionRange: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ResponseRedactionRange { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseResponseRedactionRange(); while (reader.pos < end) { const tag = reader.uint32(); @@ -372,7 +372,7 @@ export const SignedRedactedDecryptionStream: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): CertificateInfo { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseCertificateInfo(); while (reader.pos < end) { const tag = reader.uint32(); @@ -513,11 +521,31 @@ export const CertificateInfo: MessageFns = { fromJSON(object: any): CertificateInfo { return { - commonName: isSet(object.commonName) ? globalThis.String(object.commonName) : "", - issuerCommonName: isSet(object.issuerCommonName) ? globalThis.String(object.issuerCommonName) : "", - notBeforeUnix: isSet(object.notBeforeUnix) ? globalThis.Number(object.notBeforeUnix) : 0, - notAfterUnix: isSet(object.notAfterUnix) ? globalThis.Number(object.notAfterUnix) : 0, - dnsNames: globalThis.Array.isArray(object?.dnsNames) ? object.dnsNames.map((e: any) => globalThis.String(e)) : [], + commonName: isSet(object.commonName) + ? globalThis.String(object.commonName) + : isSet(object.common_name) + ? globalThis.String(object.common_name) + : "", + issuerCommonName: isSet(object.issuerCommonName) + ? globalThis.String(object.issuerCommonName) + : isSet(object.issuer_common_name) + ? globalThis.String(object.issuer_common_name) + : "", + notBeforeUnix: isSet(object.notBeforeUnix) + ? globalThis.Number(object.notBeforeUnix) + : isSet(object.not_before_unix) + ? globalThis.Number(object.not_before_unix) + : 0, + notAfterUnix: isSet(object.notAfterUnix) + ? globalThis.Number(object.notAfterUnix) + : isSet(object.not_after_unix) + ? globalThis.Number(object.not_after_unix) + : 0, + dnsNames: globalThis.Array.isArray(object?.dnsNames) + ? object.dnsNames.map((e: any) => globalThis.String(e)) + : globalThis.Array.isArray(object?.dns_names) + ? object.dns_names.map((e: any) => globalThis.String(e)) + : [], }; }, @@ -578,7 +606,7 @@ export const HandshakeSecrets: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): HandshakeSecrets { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseHandshakeSecrets(); while (reader.pos < end) { const tag = reader.uint32(); @@ -626,9 +654,21 @@ export const HandshakeSecrets: MessageFns = { fromJSON(object: any): HandshakeSecrets { return { - handshakeKey: isSet(object.handshakeKey) ? bytesFromBase64(object.handshakeKey) : new Uint8Array(0), - handshakeIv: isSet(object.handshakeIv) ? bytesFromBase64(object.handshakeIv) : new Uint8Array(0), - cipherSuite: isSet(object.cipherSuite) ? globalThis.Number(object.cipherSuite) : 0, + handshakeKey: isSet(object.handshakeKey) + ? bytesFromBase64(object.handshakeKey) + : isSet(object.handshake_key) + ? bytesFromBase64(object.handshake_key) + : new Uint8Array(0), + handshakeIv: isSet(object.handshakeIv) + ? bytesFromBase64(object.handshakeIv) + : isSet(object.handshake_iv) + ? bytesFromBase64(object.handshake_iv) + : new Uint8Array(0), + cipherSuite: isSet(object.cipherSuite) + ? globalThis.Number(object.cipherSuite) + : isSet(object.cipher_suite) + ? globalThis.Number(object.cipher_suite) + : 0, algorithm: isSet(object.algorithm) ? globalThis.String(object.algorithm) : "", }; }, @@ -677,7 +717,7 @@ export const Opening: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): Opening { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseOpening(); while (reader.pos < end) { const tag = reader.uint32(); @@ -700,7 +740,13 @@ export const Opening: MessageFns = { }, fromJSON(object: any): Opening { - return { proofStream: isSet(object.proofStream) ? bytesFromBase64(object.proofStream) : new Uint8Array(0) }; + return { + proofStream: isSet(object.proofStream) + ? bytesFromBase64(object.proofStream) + : isSet(object.proof_stream) + ? bytesFromBase64(object.proof_stream) + : new Uint8Array(0), + }; }, toJSON(message: Opening): unknown { @@ -741,7 +787,7 @@ export const OPRFOutput: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): OPRFOutput { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseOPRFOutput(); while (reader.pos < end) { const tag = reader.uint32(); @@ -781,9 +827,21 @@ export const OPRFOutput: MessageFns = { fromJSON(object: any): OPRFOutput { return { - tlsStart: isSet(object.tlsStart) ? globalThis.Number(object.tlsStart) : 0, - tlsLength: isSet(object.tlsLength) ? globalThis.Number(object.tlsLength) : 0, - hashOutput: isSet(object.hashOutput) ? bytesFromBase64(object.hashOutput) : new Uint8Array(0), + tlsStart: isSet(object.tlsStart) + ? globalThis.Number(object.tlsStart) + : isSet(object.tls_start) + ? globalThis.Number(object.tls_start) + : 0, + tlsLength: isSet(object.tlsLength) + ? globalThis.Number(object.tlsLength) + : isSet(object.tls_length) + ? globalThis.Number(object.tls_length) + : 0, + hashOutput: isSet(object.hashOutput) + ? bytesFromBase64(object.hashOutput) + : isSet(object.hash_output) + ? bytesFromBase64(object.hash_output) + : new Uint8Array(0), }; }, @@ -857,7 +915,7 @@ export const KOutputPayload: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): KOutputPayload { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseKOutputPayload(); while (reader.pos < end) { const tag = reader.uint32(); @@ -937,21 +995,45 @@ export const KOutputPayload: MessageFns = { fromJSON(object: any): KOutputPayload { return { - redactedRequest: isSet(object.redactedRequest) ? bytesFromBase64(object.redactedRequest) : new Uint8Array(0), + redactedRequest: isSet(object.redactedRequest) + ? bytesFromBase64(object.redactedRequest) + : isSet(object.redacted_request) + ? bytesFromBase64(object.redacted_request) + : new Uint8Array(0), requestRedactionRanges: globalThis.Array.isArray(object?.requestRedactionRanges) ? object.requestRedactionRanges.map((e: any) => RequestRedactionRange.fromJSON(e)) + : globalThis.Array.isArray(object?.request_redaction_ranges) + ? object.request_redaction_ranges.map((e: any) => RequestRedactionRange.fromJSON(e)) : [], consolidatedResponseKeystream: isSet(object.consolidatedResponseKeystream) ? bytesFromBase64(object.consolidatedResponseKeystream) + : isSet(object.consolidated_response_keystream) + ? bytesFromBase64(object.consolidated_response_keystream) : new Uint8Array(0), - certificateInfo: isSet(object.certificateInfo) ? CertificateInfo.fromJSON(object.certificateInfo) : undefined, + certificateInfo: isSet(object.certificateInfo) + ? CertificateInfo.fromJSON(object.certificateInfo) + : isSet(object.certificate_info) + ? CertificateInfo.fromJSON(object.certificate_info) + : undefined, responseRedactionRanges: globalThis.Array.isArray(object?.responseRedactionRanges) ? object.responseRedactionRanges.map((e: any) => ResponseRedactionRange.fromJSON(e)) + : globalThis.Array.isArray(object?.response_redaction_ranges) + ? object.response_redaction_ranges.map((e: any) => ResponseRedactionRange.fromJSON(e)) : [], - timestampMs: isSet(object.timestampMs) ? globalThis.Number(object.timestampMs) : 0, - sessionId: isSet(object.sessionId) ? globalThis.String(object.sessionId) : "", + timestampMs: isSet(object.timestampMs) + ? globalThis.Number(object.timestampMs) + : isSet(object.timestamp_ms) + ? globalThis.Number(object.timestamp_ms) + : 0, + sessionId: isSet(object.sessionId) + ? globalThis.String(object.sessionId) + : isSet(object.session_id) + ? globalThis.String(object.session_id) + : "", oprfOutputs: globalThis.Array.isArray(object?.oprfOutputs) ? object.oprfOutputs.map((e: any) => OPRFOutput.fromJSON(e)) + : globalThis.Array.isArray(object?.oprf_outputs) + ? object.oprf_outputs.map((e: any) => OPRFOutput.fromJSON(e)) : [], }; }, @@ -1038,7 +1120,7 @@ export const TOutputPayload: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): TOutputPayload { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseTOutputPayload(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1096,14 +1178,28 @@ export const TOutputPayload: MessageFns = { return { consolidatedResponseCiphertext: isSet(object.consolidatedResponseCiphertext) ? bytesFromBase64(object.consolidatedResponseCiphertext) + : isSet(object.consolidated_response_ciphertext) + ? bytesFromBase64(object.consolidated_response_ciphertext) : new Uint8Array(0), requestProofStreams: globalThis.Array.isArray(object?.requestProofStreams) ? object.requestProofStreams.map((e: any) => bytesFromBase64(e)) + : globalThis.Array.isArray(object?.request_proof_streams) + ? object.request_proof_streams.map((e: any) => bytesFromBase64(e)) : [], - timestampMs: isSet(object.timestampMs) ? globalThis.Number(object.timestampMs) : 0, - sessionId: isSet(object.sessionId) ? globalThis.String(object.sessionId) : "", + timestampMs: isSet(object.timestampMs) + ? globalThis.Number(object.timestampMs) + : isSet(object.timestamp_ms) + ? globalThis.Number(object.timestamp_ms) + : 0, + sessionId: isSet(object.sessionId) + ? globalThis.String(object.sessionId) + : isSet(object.session_id) + ? globalThis.String(object.session_id) + : "", oprfOutputs: globalThis.Array.isArray(object?.oprfOutputs) ? object.oprfOutputs.map((e: any) => OPRFOutput.fromJSON(e)) + : globalThis.Array.isArray(object?.oprf_outputs) + ? object.oprf_outputs.map((e: any) => OPRFOutput.fromJSON(e)) : [], }; }, @@ -1159,7 +1255,7 @@ export const AttestationReport: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): AttestationReport { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseAttestationReport(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1250,7 +1346,7 @@ export const SignedMessage: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): SignedMessage { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseSignedMessage(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1306,12 +1402,22 @@ export const SignedMessage: MessageFns = { fromJSON(object: any): SignedMessage { return { - bodyType: isSet(object.bodyType) ? bodyTypeFromJSON(object.bodyType) : 0, + bodyType: isSet(object.bodyType) + ? bodyTypeFromJSON(object.bodyType) + : isSet(object.body_type) + ? bodyTypeFromJSON(object.body_type) + : 0, body: isSet(object.body) ? bytesFromBase64(object.body) : new Uint8Array(0), - ethAddress: isSet(object.ethAddress) ? bytesFromBase64(object.ethAddress) : new Uint8Array(0), + ethAddress: isSet(object.ethAddress) + ? bytesFromBase64(object.ethAddress) + : isSet(object.eth_address) + ? bytesFromBase64(object.eth_address) + : new Uint8Array(0), signature: isSet(object.signature) ? bytesFromBase64(object.signature) : new Uint8Array(0), attestationReport: isSet(object.attestationReport) ? AttestationReport.fromJSON(object.attestationReport) + : isSet(object.attestation_report) + ? AttestationReport.fromJSON(object.attestation_report) : undefined, }; }, @@ -1372,7 +1478,7 @@ export const OPRFVerificationData: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): OPRFVerificationData { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseOPRFVerificationData(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1412,10 +1518,20 @@ export const OPRFVerificationData: MessageFns = { fromJSON(object: any): OPRFVerificationData { return { - streamPos: isSet(object.streamPos) ? globalThis.Number(object.streamPos) : 0, - streamLength: isSet(object.streamLength) ? globalThis.Number(object.streamLength) : 0, + streamPos: isSet(object.streamPos) + ? globalThis.Number(object.streamPos) + : isSet(object.stream_pos) + ? globalThis.Number(object.stream_pos) + : 0, + streamLength: isSet(object.streamLength) + ? globalThis.Number(object.streamLength) + : isSet(object.stream_length) + ? globalThis.Number(object.stream_length) + : 0, publicSignalsJson: isSet(object.publicSignalsJson) ? bytesFromBase64(object.publicSignalsJson) + : isSet(object.public_signals_json) + ? bytesFromBase64(object.public_signals_json) : new Uint8Array(0), }; }, @@ -1466,7 +1582,7 @@ export const VerificationBundle: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): VerificationBundle { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseVerificationBundle(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1506,10 +1622,20 @@ export const VerificationBundle: MessageFns = { fromJSON(object: any): VerificationBundle { return { - teekSigned: isSet(object.teekSigned) ? SignedMessage.fromJSON(object.teekSigned) : undefined, - teetSigned: isSet(object.teetSigned) ? SignedMessage.fromJSON(object.teetSigned) : undefined, + teekSigned: isSet(object.teekSigned) + ? SignedMessage.fromJSON(object.teekSigned) + : isSet(object.teek_signed) + ? SignedMessage.fromJSON(object.teek_signed) + : undefined, + teetSigned: isSet(object.teetSigned) + ? SignedMessage.fromJSON(object.teetSigned) + : isSet(object.teet_signed) + ? SignedMessage.fromJSON(object.teet_signed) + : undefined, oprfVerifications: globalThis.Array.isArray(object?.oprfVerifications) ? object.oprfVerifications.map((e: any) => OPRFVerificationData.fromJSON(e)) + : globalThis.Array.isArray(object?.oprf_verifications) + ? object.oprf_verifications.map((e: any) => OPRFVerificationData.fromJSON(e)) : [], }; }, @@ -1546,7 +1672,7 @@ export const VerificationBundle: MessageFns = { function bytesFromBase64(b64: string): Uint8Array { if ((globalThis as any).Buffer) { - return Uint8Array.from(globalThis.Buffer.from(b64, "base64")); + return Uint8Array.from((globalThis as any).Buffer.from(b64, "base64")); } else { const bin = globalThis.atob(b64); const arr = new Uint8Array(bin.length); @@ -1559,7 +1685,7 @@ function bytesFromBase64(b64: string): Uint8Array { function base64FromBytes(arr: Uint8Array): string { if ((globalThis as any).Buffer) { - return globalThis.Buffer.from(arr).toString("base64"); + return (globalThis as any).Buffer.from(arr).toString("base64"); } else { const bin: string[] = []; arr.forEach((byte) => { diff --git a/src/scripts/generate-proto.sh b/src/scripts/generate-proto.sh index e908d363..0bd07935 100755 --- a/src/scripts/generate-proto.sh +++ b/src/scripts/generate-proto.sh @@ -1,5 +1,5 @@ mkdir -p src/proto protoc --plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \ --ts_proto_out=./src/proto \ - --ts_proto_opt=enumsAsLiterals=true,useExactTypes=false \ + --ts_proto_opt=enumsAsLiterals=true,useExactTypes=false,importSuffix=.ts \ --proto_path=./proto ./proto/*.proto \ No newline at end of file diff --git a/src/scripts/start-server.ts b/src/scripts/start-server.ts index f5cf0051..22d9feac 100644 --- a/src/scripts/start-server.ts +++ b/src/scripts/start-server.ts @@ -9,6 +9,14 @@ getApm() setCryptoImplementation(webcryptoCrypto) async function main() { + if(process.env.ENCLAVE_MODE === 'true') { + // Must run before #src/server/index.ts is imported: the server modules + // read PRIVATE_KEY at module load, so secrets need to be on + // process.env before that import resolves. + const { bootstrapTee } = await import('#src/server/tee/bootstrap.ts') + await bootstrapTee() + } + // importing dynamically to allow APM to inject // into modules before they are used const { createServer } = await import('#src/server/index.ts') diff --git a/src/server/create-server.ts b/src/server/create-server.ts index e0f31887..8e789dc6 100644 --- a/src/server/create-server.ts +++ b/src/server/create-server.ts @@ -1,5 +1,6 @@ import type { IncomingMessage } from 'http' import { createServer as createHttpServer } from 'http' +import { createServer as createHttpsServer } from 'https' import serveStatic from 'serve-static' import type { Duplex } from 'stream' import type { WebSocket } from 'ws' @@ -7,6 +8,7 @@ import { WebSocketServer } from 'ws' import { API_SERVER_PORT, ATTESTOR_ADDRESS_PATHNAME, BROWSER_RPC_PATHNAME, WS_PATHNAME } from '#src/config/index.ts' import { AttestorServerSocket } from '#src/server/socket.ts' +import { getActiveCertificate } from '#src/server/tee/cert-manager.ts' import { getAttestorAddress } from '#src/server/utils/generics.ts' import { addKeepAlive } from '#src/server/utils/keep-alive.ts' import type { BGPListener } from '#src/types/index.ts' @@ -17,6 +19,8 @@ import { SelectedServiceSignatureType } from '#src/utils/signatures/index.ts' import { promisifySend } from '#src/utils/ws.ts' const PORT = +(getEnvVariable('PORT') || API_SERVER_PORT) +const HTTPS_PORT = +(getEnvVariable('HTTPS_PORT') || 443) +const ENCLAVE_MODE = getEnvVariable('ENCLAVE_MODE') === 'true' const DISABLE_BGP_CHECKS = getEnvVariable('DISABLE_BGP_CHECKS') === '1' const ATTESTOR_ADDRESS_JSON_RES = JSON.stringify({ @@ -29,8 +33,20 @@ const ATTESTOR_ADDRESS_JSON_RES = JSON.stringify({ * creates a fileserver to serve the browser RPC client, * and listens on the given port. */ -export async function createServer(port = PORT) { - const http = createHttpServer() +export async function createServer(port = ENCLAVE_MODE ? HTTPS_PORT : PORT) { + const http = ENCLAVE_MODE + ? createHttpsServer({ + SNICallback: (_servername, cb) => { + const cert = getActiveCertificate() + if(!cert) { + cb(new Error('tls certificate not bootstrapped')) + return + } + + cb(null, cert.secureContext) + } + }) + : createHttpServer() const serveBrowserRpc = serveStatic( 'browser', { diff --git a/src/server/handlers/claimTeeBundle.ts b/src/server/handlers/claimTeeBundle.ts index 3f3e257f..0bf7028a 100644 --- a/src/server/handlers/claimTeeBundle.ts +++ b/src/server/handlers/claimTeeBundle.ts @@ -8,6 +8,7 @@ import { ClaimTeeBundleResponse } from '#src/proto/api.ts' import type { CertificateInfo } from '#src/proto/tee-bundle.ts' import { VerificationBundle } from '#src/proto/tee-bundle.ts' import { substituteParamValues } from '#src/providers/http/index.ts' +import { makeClaimAttestation } from '#src/server/tee/attestation-generate.ts' import { assertValidProviderTranscript } from '#src/server/utils/assert-valid-claim-request.ts' import { getAttestorAddress, niceParseJsonObject, signAsAttestor } from '#src/server/utils/generics.ts' import { verifyOprfMpcOutputs } from '#src/server/utils/tee-oprf-mpc-verification.ts' @@ -121,7 +122,8 @@ export const claimTeeBundle: RPCHandler<'claimTeeBundle'> = async( resultSignature: await signAsAttestor( ClaimTeeBundleResponse.encode(res).finish(), client.metadata.signatureType - ) + ), + claimAttestation: makeClaimAttestation() } logger.info('TEE bundle claim processing completed') diff --git a/src/server/handlers/claimTunnel.ts b/src/server/handlers/claimTunnel.ts index 0d7a3c3b..05671563 100644 --- a/src/server/handlers/claimTunnel.ts +++ b/src/server/handlers/claimTunnel.ts @@ -1,5 +1,6 @@ import { MAX_CLAIM_TIMESTAMP_DIFF_S } from '#src/config/index.ts' import { ClaimTunnelResponse } from '#src/proto/api.ts' +import { makeClaimAttestation } from '#src/server/tee/attestation-generate.ts' import { getApm } from '#src/server/utils/apm.ts' import { assertTranscriptsMatch, assertValidClaimRequest } from '#src/server/utils/assert-valid-claim-request.ts' import { getAttestorAddress, signAsAttestor } from '#src/server/utils/generics.ts' @@ -107,7 +108,8 @@ export const claimTunnel: RPCHandler<'claimTunnel'> = async( resultSignature: await signAsAttestor( ClaimTunnelResponse.encode(res).finish(), client.metadata.signatureType - ) + ), + claimAttestation: makeClaimAttestation() } // remove tunnel from client -- to free up our mem diff --git a/src/server/tee/acme-http-server.ts b/src/server/tee/acme-http-server.ts new file mode 100644 index 00000000..ee4a0f61 --- /dev/null +++ b/src/server/tee/acme-http-server.ts @@ -0,0 +1,71 @@ +import { createServer, type Server } from 'http' + +import { logger as LOGGER } from '#src/utils/logger.ts' + +const CHALLENGE_PATH_PREFIX = '/.well-known/acme-challenge/' + +/** + * Ephemeral HTTP server that answers ACME HTTP-01 challenges. Started right + * before an order is placed and stopped as soon as the order is finalized, + * so the attestor does not keep port 80 bound during normal operation. + */ +export class AcmeChallengeServer { + private readonly tokens = new Map() + private server?: Server + + add(token: string, keyAuthorization: string): void { + this.tokens.set(token, keyAuthorization) + } + + remove(token: string): void { + this.tokens.delete(token) + } + + async start(port: number): Promise { + if(this.server) { + return + } + + const server = createServer((req, res) => { + const url = req.url ?? '/' + if(!url.startsWith(CHALLENGE_PATH_PREFIX)) { + res.statusCode = 404 + res.end() + return + } + + const token = url.slice(CHALLENGE_PATH_PREFIX.length) + const keyAuth = this.tokens.get(token) + if(!keyAuth) { + res.statusCode = 404 + res.end() + return + } + + res.writeHead(200, { 'Content-Type': 'text/plain' }) + res.end(keyAuth) + }) + + await new Promise((resolve, reject) => { + server.once('listening', () => resolve()) + server.once('error', reject) + server.listen(port) + }) + + LOGGER.info({ port }, 'tee: acme challenge server listening') + this.server = server + } + + async stop(): Promise { + const server = this.server + if(!server) { + return + } + + this.server = undefined + await new Promise((resolve, reject) => { + server.close((err) => err ? reject(err) : resolve()) + }) + LOGGER.info('tee: acme challenge server stopped') + } +} diff --git a/src/server/tee/attestation-generate.ts b/src/server/tee/attestation-generate.ts new file mode 100644 index 00000000..8bab730e --- /dev/null +++ b/src/server/tee/attestation-generate.ts @@ -0,0 +1,172 @@ +import { Buffer } from 'buffer' +import http from 'http' + +import type { AttestationReport } from '#src/proto/tee-bundle.ts' +import { logger as LOGGER } from '#src/utils/logger.ts' + +const LAUNCHER_SOCKET_PATH = '/run/container_launcher/teeserver.sock' +const LAUNCHER_TOKEN_PATH = '/v1/token' +const LAUNCHER_AUDIENCE = 'https://reclaimprotocol.org' + +const CACHE_TTL_MS = 5 * 60 * 1000 +const REFRESH_INTERVAL_MS = 4 * 60 * 1000 + +let cachedJwt: Uint8Array | undefined +let cachedAt = 0 +let refreshTimer: NodeJS.Timeout | undefined + +/** + * GCP requires each nonce between 8 and 88 bytes inclusive. + */ +function clampNonce(n: string): string { + if(n.length < 8) { + return n.padEnd(8, ' ') + } + + if(n.length > 88) { + return n.slice(0, 88) + } + + return n +} + +/** + * Requests a custom attestation token from the Confidential Space launcher + * over the unix domain socket. Mirrors reclaim-tee's shared/gcp_attestation.go. + */ +export async function generateAttestationJwt( + nonces: string[] +): Promise { + const body = JSON.stringify({ + audience: LAUNCHER_AUDIENCE, + // eslint-disable-next-line camelcase + token_type: 'PKI', + nonces: nonces.map(clampNonce) + }) + + return new Promise((resolve, reject) => { + const req = http.request( + { + socketPath: LAUNCHER_SOCKET_PATH, + path: LAUNCHER_TOKEN_PATH, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(body).toString() + }, + timeout: 10_000 + }, + (res) => { + const chunks: Buffer[] = [] + res.on('data', (c) => chunks.push(c)) + res.on('end', () => { + const data = Buffer.concat(chunks) + if(res.statusCode !== 200) { + reject(new Error( + `launcher returned ${res.statusCode}: ${data.toString('utf8')}` + )) + return + } + + resolve(new Uint8Array(data)) + }) + } + ) + req.on('error', reject) + req.on('timeout', () => { + req.destroy(new Error('launcher socket timeout')) + }) + req.write(body) + req.end() + }) +} + +export interface AttestationContext { + attestorAddress: string + tlsCertSha256Hex: () => string | undefined +} + +let context: AttestationContext | undefined + +async function refresh(): Promise { + if(!context) { + return + } + + const nonces = [`attestor_public_key:${context.attestorAddress}`] + const certHash = context.tlsCertSha256Hex() + if(certHash) { + nonces.push(`attestor_cert_hash:${certHash}`) + } + + cachedJwt = await generateAttestationJwt(nonces) + cachedAt = Date.now() + LOGGER.info( + { bytes: cachedJwt.length, nonces: nonces.length }, + 'tee: refreshed gcp attestation' + ) +} + +/** + * Starts the background attestation refresh loop. The first attestation is + * generated synchronously so getCachedAttestationJwt() is ready by the time + * the server begins handling claims. + */ +export async function startAttestationRefresh( + ctx: AttestationContext +): Promise { + context = ctx + await refresh() + if(refreshTimer) { + clearInterval(refreshTimer) + } + + refreshTimer = setInterval(() => { + refresh().catch((err) => { + LOGGER.error({ err }, 'tee: attestation refresh failed') + }) + }, REFRESH_INTERVAL_MS) + refreshTimer.unref?.() +} + +export function stopAttestationRefresh(): void { + if(refreshTimer) { + clearInterval(refreshTimer) + refreshTimer = undefined + } + + context = undefined + cachedJwt = undefined + cachedAt = 0 +} + +/** + * Returns the currently-cached attestation JWT bytes, or undefined if no + * attestation has been generated yet or the cache has expired without a + * successful refresh. + */ +export function getCachedAttestationJwt(): Uint8Array | undefined { + if(!cachedJwt) { + return undefined + } + + if(Date.now() - cachedAt > CACHE_TTL_MS) { + return undefined + } + + return cachedJwt +} + +/** + * Returns an AttestationReport ready to embed in a claim response, or + * undefined when no attestation is available (i.e. attestor is not running + * inside a TEE, or the refresh loop has not produced one yet). + */ +export function makeClaimAttestation(): AttestationReport | undefined { + const report = getCachedAttestationJwt() + if(!report) { + return undefined + } + + return { type: 'gcp', report } +} diff --git a/src/server/tee/bootstrap.ts b/src/server/tee/bootstrap.ts new file mode 100644 index 00000000..7d0597d4 --- /dev/null +++ b/src/server/tee/bootstrap.ts @@ -0,0 +1,64 @@ +import { Wallet } from 'ethers' + +import { + startAttestationRefresh +} from '#src/server/tee/attestation-generate.ts' +import { + bootstrapCertificate, + getActiveCertificate, + startRenewalLoop +} from '#src/server/tee/cert-manager.ts' +import { installCloudLogging } from '#src/server/tee/cloud-logging.ts' +import { loadSecretsIntoEnv } from '#src/server/tee/secret-loader.ts' +import { logger as LOGGER } from '#src/utils/logger.ts' + +function requireEnv(name: string): string { + const v = process.env[name] + if(!v) { + throw new Error(`tee bootstrap: ${name} is required`) + } + + return v +} + +/** + * Brings the attestor up in TEE mode: + * 1. Pull signing/OPRF secrets from GCP Secret Manager into process.env. + * 2. Load (or obtain via ACME) the TLS cert and start the renewal loop. + * 3. Start the attestation refresh loop, with the public key + cert hash + * as nonces. + * + * Must run before #src/server/index.ts is imported, since modules in that + * tree read PRIVATE_KEY at module load. + */ +export async function bootstrapTee(): Promise { + const projectId = requireEnv('GOOGLE_PROJECT_ID') + const domain = requireEnv('ENCLAVE_DOMAIN') + const email = requireEnv('ACME_EMAIL') + const directoryUrl = process.env.ACME_DIRECTORY_URL + || 'https://acme-v02.api.letsencrypt.org/directory' + const httpChallengePort = +(process.env.HTTP_PORT || 80) + const logName = process.env.LOG_NAME || 'attestor-core' + + installCloudLogging({ projectId, logName }) + LOGGER.info({ projectId, domain }, 'tee: bootstrap start') + + await loadSecretsIntoEnv(projectId) + + const cfg = { projectId, domain, email, directoryUrl, httpChallengePort } + await bootstrapCertificate(cfg) + startRenewalLoop(cfg) + + const signingKey = process.env.PRIVATE_KEY + if(!signingKey) { + throw new Error('tee bootstrap: PRIVATE_KEY missing after secret load') + } + + const attestorAddress = new Wallet(signingKey).address + await startAttestationRefresh({ + attestorAddress, + tlsCertSha256Hex: () => getActiveCertificate()?.sha256Hex + }) + + LOGGER.info({ attestorAddress, domain }, 'tee: bootstrap complete') +} diff --git a/src/server/tee/cert-manager.ts b/src/server/tee/cert-manager.ts new file mode 100644 index 00000000..6796c369 --- /dev/null +++ b/src/server/tee/cert-manager.ts @@ -0,0 +1,252 @@ +import * as acme from 'acme-client' +import { Buffer } from 'buffer' +import crypto, { X509Certificate } from 'crypto' +import tls from 'tls' + +import { AcmeChallengeServer } from '#src/server/tee/acme-http-server.ts' +import { accessLatestSecret, addSecretVersion, createSecretIfNotExists } from '#src/server/tee/secret-manager.ts' +import { logger as LOGGER } from '#src/utils/logger.ts' + +const RENEW_IF_EXPIRES_WITHIN_MS = 14 * 24 * 60 * 60 * 1000 +const RENEW_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000 + +interface PersistedCert { + certPem: string + keyPem: string +} + +interface PersistedAccount { + accountKeyPem: string + accountUrl?: string +} + +export interface CertManagerConfig { + projectId: string + domain: string + email: string + directoryUrl: string + httpChallengePort: number +} + +export interface ActiveCertificate { + certPem: string + keyPem: string + notAfter: Date + sha256Hex: string + secureContext: tls.SecureContext +} + +let active: ActiveCertificate | undefined +let renewTimer: NodeJS.Timeout | undefined + +function certSecretId(domain: string): string { + return `attestor-tls-cert-${domain.replaceAll('.', '-')}` +} + +function accountSecretId(domain: string): string { + return `attestor-acme-account-${domain.replaceAll('.', '-')}` +} + +async function loadPersistedCert( + projectId: string, + domain: string +): Promise { + try { + const bytes = await accessLatestSecret(projectId, certSecretId(domain)) + const json = JSON.parse(Buffer.from(bytes).toString('utf8')) as PersistedCert + if(!json.certPem || !json.keyPem) { + return undefined + } + + return json + } catch(err) { + LOGGER.info({ err: (err as Error).message }, 'tee: no persisted cert') + return undefined + } +} + +async function persistCert( + projectId: string, + domain: string, + cert: PersistedCert +): Promise { + const id = certSecretId(domain) + await createSecretIfNotExists(projectId, id) + await addSecretVersion( + projectId, + id, + Buffer.from(JSON.stringify(cert), 'utf8') + ) +} + +async function loadPersistedAccount( + projectId: string, + domain: string +): Promise { + try { + const bytes = await accessLatestSecret(projectId, accountSecretId(domain)) + return JSON.parse(Buffer.from(bytes).toString('utf8')) as PersistedAccount + } catch{ + return undefined + } +} + +async function persistAccount( + projectId: string, + domain: string, + account: PersistedAccount +): Promise { + const id = accountSecretId(domain) + await createSecretIfNotExists(projectId, id) + await addSecretVersion( + projectId, + id, + Buffer.from(JSON.stringify(account), 'utf8') + ) +} + +function buildActive(cert: PersistedCert): ActiveCertificate { + const x509 = new X509Certificate(cert.certPem) + const der = Buffer.from(x509.raw) + const sha256 = crypto.createHash('sha256').update(der).digest('hex') + return { + certPem: cert.certPem, + keyPem: cert.keyPem, + notAfter: new Date(x509.validTo), + sha256Hex: sha256, + secureContext: tls.createSecureContext({ + cert: cert.certPem, + key: cert.keyPem + }) + } +} + +function isFresh(cert: PersistedCert): boolean { + try { + const x509 = new X509Certificate(cert.certPem) + const remaining = new Date(x509.validTo).getTime() - Date.now() + return remaining > RENEW_IF_EXPIRES_WITHIN_MS + } catch(err) { + LOGGER.warn({ err: (err as Error).message }, 'tee: cert parse failed') + return false + } +} + +async function obtainViaAcme( + cfg: CertManagerConfig +): Promise { + const persistedAccount = await loadPersistedAccount(cfg.projectId, cfg.domain) + const accountKey = persistedAccount + ? Buffer.from(persistedAccount.accountKeyPem, 'utf8') + : await acme.crypto.createPrivateKey() + + const client = new acme.Client({ + directoryUrl: cfg.directoryUrl, + accountKey, + accountUrl: persistedAccount?.accountUrl + }) + + const [certKey, csr] = await acme.crypto.createCsr({ + commonName: cfg.domain, + altNames: [cfg.domain] + }) + + const challengeServer = new AcmeChallengeServer() + await challengeServer.start(cfg.httpChallengePort) + try { + const certPem = await client.auto({ + csr, + email: cfg.email, + termsOfServiceAgreed: true, + challengePriority: ['http-01'], + challengeCreateFn: async(_authz, challenge, keyAuthorization) => { + if(challenge.type !== 'http-01') { + throw new Error(`unsupported challenge: ${challenge.type}`) + } + + challengeServer.add(challenge.token, keyAuthorization) + }, + challengeRemoveFn: async(_authz, challenge) => { + challengeServer.remove(challenge.token) + } + }) + + await persistAccount(cfg.projectId, cfg.domain, { + accountKeyPem: accountKey.toString('utf8'), + accountUrl: client.getAccountUrl() + }) + + const result: PersistedCert = { + certPem, + keyPem: certKey.toString('utf8') + } + await persistCert(cfg.projectId, cfg.domain, result) + return result + } finally{ + await challengeServer.stop().catch((err) => { + LOGGER.warn({ err }, 'tee: acme challenge server stop failed') + }) + } +} + +/** + * Bootstraps the TLS certificate. Tries Secret Manager first; if absent or + * expiring within the renewal window, runs ACME against the configured + * directory URL and persists the result. + */ +export async function bootstrapCertificate( + cfg: CertManagerConfig +): Promise { + const persisted = await loadPersistedCert(cfg.projectId, cfg.domain) + if(persisted && isFresh(persisted)) { + LOGGER.info({ domain: cfg.domain }, 'tee: using persisted certificate') + active = buildActive(persisted) + return active + } + + LOGGER.info( + { domain: cfg.domain, directoryUrl: cfg.directoryUrl }, + persisted ? 'tee: cert near expiry, renewing' : 'tee: no cert, requesting new' + ) + const fresh = await obtainViaAcme(cfg) + active = buildActive(fresh) + return active +} + +export function startRenewalLoop(cfg: CertManagerConfig): void { + if(renewTimer) { + clearInterval(renewTimer) + } + + renewTimer = setInterval(async() => { + try { + const remaining = active + ? active.notAfter.getTime() - Date.now() + : 0 + if(remaining > RENEW_IF_EXPIRES_WITHIN_MS) { + return + } + + LOGGER.info( + { remainingDays: Math.round(remaining / (24 * 60 * 60 * 1000)) }, + 'tee: cert near expiry, renewing' + ) + const fresh = await obtainViaAcme(cfg) + active = buildActive(fresh) + } catch(err) { + LOGGER.error({ err }, 'tee: cert renewal failed') + } + }, RENEW_CHECK_INTERVAL_MS) + renewTimer.unref?.() +} + +export function stopRenewalLoop(): void { + if(renewTimer) { + clearInterval(renewTimer) + renewTimer = undefined + } +} + +export function getActiveCertificate(): ActiveCertificate | undefined { + return active +} diff --git a/src/server/tee/cloud-logging.ts b/src/server/tee/cloud-logging.ts new file mode 100644 index 00000000..3d9acbd0 --- /dev/null +++ b/src/server/tee/cloud-logging.ts @@ -0,0 +1,153 @@ +import { Logging } from '@google-cloud/logging' +import { Writable } from 'stream' + +import type { LogLevel } from '#src/types/index.ts' +import { setLoggerDestination } from '#src/utils/logger.ts' + +// Map pino numeric levels to Cloud Logging severities. +function pinoLevelToSeverity(level: number): string { + if(level >= 60) { + return 'CRITICAL' + } + + if(level >= 50) { + return 'ERROR' + } + + if(level >= 40) { + return 'WARNING' + } + + if(level >= 30) { + return 'INFO' + } + + if(level >= 20) { + return 'DEBUG' + } + + return 'DEFAULT' +} + +interface CloudLoggingOptions { + projectId: string + logName: string + level?: LogLevel +} + +class CloudLoggingStream extends Writable { + private readonly cloudLogger: ReturnType + + constructor(projectId: string, logName: string) { + super({ decodeStrings: false }) + const logging = new Logging({ projectId }) + this.cloudLogger = logging.log(logName) + } + + async probe(): Promise { + await this.cloudLogger.write( + this.cloudLogger.entry( + { severity: 'DEFAULT' }, + { message: 'cloud logging probe' } + ) + ) + } + + override _write( + chunk: Buffer | string, + _encoding: string, + cb: (err?: Error) => void + ): void { + try { + let parsed: Record + try { + parsed = JSON.parse(chunk.toString()) + } catch { + parsed = { message: chunk.toString().trim() } + } + + const level = (parsed.level as number) ?? 30 + const severity = pinoLevelToSeverity(level) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { msg, message, time, level: _level, ...rest } = parsed + const entry = this.cloudLogger.entry( + { + severity, + timestamp: typeof time === 'string' + ? new Date(time) + : undefined + }, + { + message: (msg ?? message ?? '') as string, + ...rest + } + ) + // Cloud Logging buffers internally; don't block the producer. + this.cloudLogger.write(entry).catch(() => undefined) + } catch { + // Never let logging crash the process. + } + + cb() + } +} + +let installed = false + +/** + * Replaces the default pino logger with one that forwards every log line + * to GCP Cloud Logging under the given log name. Idempotent. + * + * Probes the Cloud Logging client first by writing a no-op entry; if + * authentication or transport fails, leaves the default stdout logger in + * place rather than crashing the process. On Confidential Space VMs the + * launcher's `tee-container-log-redirect` ships stdout to Cloud Logging + * anyway, so the worst case is logs appear under + * `confidential-space-launcher` rather than the configured `logName`. + * + * We also install a process-wide `unhandledRejection` filter that + * swallows errors originating in `@google-cloud/logging`, since the SDK + * has internal lazy gRPC init that escapes our local `.catch()`. + */ +export function installCloudLogging(opts: CloudLoggingOptions): void { + if(installed) { + return + } + + // Install rejection + exception filters BEFORE constructing the + // Logging client. The SDK kicks off auth+gRPC stub creation as a + // background promise whose rejection is rethrown synchronously + // inside a microtask, escaping any local `.catch()`. Without these + // handlers a misconfigured environment (no ADC, network blip, IAM + // issue) crashes the process before any other log can be written. + const isLoggingErr = (e: unknown) => { + const stack = (e as Error)?.stack ?? '' + return stack.includes('@google-cloud/logging') + || stack.includes('google-gax') + || stack.includes('google-auth-library') + } + process.on('unhandledRejection', (reason) => { + if(isLoggingErr(reason)) { + + console.error('tee: cloud logging async rejection swallowed:', + (reason as Error)?.message) + return + } + + throw reason + }) + process.on('uncaughtException', (err) => { + if(isLoggingErr(err)) { + + console.error('tee: cloud logging uncaught error swallowed:', + err?.message) + return + } + + throw err + }) + + const stream = new CloudLoggingStream(opts.projectId, opts.logName) + setLoggerDestination(stream, opts.level) + installed = true +} diff --git a/src/server/tee/secret-loader.ts b/src/server/tee/secret-loader.ts new file mode 100644 index 00000000..5493d058 --- /dev/null +++ b/src/server/tee/secret-loader.ts @@ -0,0 +1,31 @@ +import { accessLatestSecret } from '#src/server/tee/secret-manager.ts' +import { logger } from '#src/utils/logger.ts' + +const SECRET_TO_ENV: Record = { + 'attestor-signing-key': 'PRIVATE_KEY', + 'attestor-toprf-share-private': 'TOPRF_SHARE_PRIVATE_KEY', + 'attestor-toprf-share-public': 'TOPRF_SHARE_PUBLIC_KEY', + 'attestor-toprf-public': 'TOPRF_PUBLIC_KEY' +} + +/** + * Fetches the attestor's signing key and OPRF key material from GCP + * Secret Manager and writes them into process.env, so that the rest of + * the server (which reads these via getEnvVariable at module load) sees + * them as if they had been set in the environment. + * + * Must be called before any module that reads PRIVATE_KEY / TOPRF_* is + * imported, otherwise the reads happen before the values are populated. + */ +export async function loadSecretsIntoEnv(projectId: string): Promise { + for(const [secretId, envName] of Object.entries(SECRET_TO_ENV)) { + if(process.env[envName]) { + logger.info({ envName }, 'tee: env already set, skipping secret fetch') + continue + } + + const bytes = await accessLatestSecret(projectId, secretId) + process.env[envName] = Buffer.from(bytes).toString('utf8').trim() + logger.info({ envName, secretId }, 'tee: loaded secret into env') + } +} diff --git a/src/server/tee/secret-manager.ts b/src/server/tee/secret-manager.ts new file mode 100644 index 00000000..0907bf66 --- /dev/null +++ b/src/server/tee/secret-manager.ts @@ -0,0 +1,57 @@ +import { SecretManagerServiceClient } from '@google-cloud/secret-manager' + +let cachedClient: SecretManagerServiceClient | undefined + +function getClient() { + if(!cachedClient) { + cachedClient = new SecretManagerServiceClient() + } + + return cachedClient +} + +export async function accessLatestSecret( + projectId: string, + secretId: string +): Promise { + const [resp] = await getClient().accessSecretVersion({ + name: `projects/${projectId}/secrets/${secretId}/versions/latest` + }) + const data = resp.payload?.data + if(!data) { + throw new Error(`secret ${secretId} has no payload`) + } + + return typeof data === 'string' ? Buffer.from(data, 'utf8') : data +} + +export async function createSecretIfNotExists( + projectId: string, + secretId: string +): Promise { + try { + await getClient().createSecret({ + parent: `projects/${projectId}`, + secretId, + secret: { + replication: { automatic: {} } + } + }) + } catch(err) { + // ALREADY_EXISTS (gRPC status 6) is fine; rethrow anything else. + if((err as { code?: number }).code !== 6) { + throw err + } + } +} + +export async function addSecretVersion( + projectId: string, + secretId: string, + payload: Uint8Array +): Promise { + await getClient().addSecretVersion({ + parent: `projects/${projectId}/secrets/${secretId}`, + payload: { data: payload } + }) +} diff --git a/src/server/utils/gcp-attestation.ts b/src/server/utils/gcp-attestation.ts index c86fa602..92cd507e 100644 --- a/src/server/utils/gcp-attestation.ts +++ b/src/server/utils/gcp-attestation.ts @@ -360,7 +360,7 @@ export async function validateGcpAttestationAndExtractKey( return { isValid: false, errors } } - const publicKeyPattern = /^(tee_[kt])_public_key:0x([0-9a-fA-F]{40})$/ + const publicKeyPattern = /^(tee_[kt]|attestor)_public_key:0x([0-9a-fA-F]{40})$/ const nonces = Array.isArray(payload.eat_nonce) ? payload.eat_nonce : [payload.eat_nonce] diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 79ccba00..09080a1a 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,4 +1,5 @@ import { type LoggerOptions, pino, stdTimeFunctions } from 'pino' +import type { Writable } from 'stream' import type { LogLevel } from '#src/types/index.ts' import { getEnvVariable } from '#src/utils/env.ts' @@ -11,6 +12,24 @@ export let logger = pino() makeLogger(false, envLevel) +/** + * Replace the active logger with one that writes to the given destination + * stream. Used by the TEE bootstrap to pipe logs into GCP Cloud Logging. + * Subsequent `import { logger }` users see the new instance via the live + * binding. + */ +export function setLoggerDestination( + dest: Writable, + level?: LogLevel +): void { + const pLogger = pino( + { timestamp: stdTimeFunctions.isoTime }, + dest + ) + pLogger.level = level || envLevel || 'info' + logger = pLogger +} + /** * Creates a logger instance with optional redaction of PII. * Replaces default logger