aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2025-04-27 12:35:45 -0400
committerXe Iaso <me@xeiaso.net>2025-04-27 12:42:58 -0400
commitef94cbcc7f9f90ef5c238413ee3305c305743a42 (patch)
treec41253eefe23a91b83cec041c9ee0e86bac53da5
parent972cc990716c8593fc1f1d7061e6b707c6bccc51 (diff)
downloadx-ef94cbcc7f9f90ef5c238413ee3305c305743a42.tar.xz
x-ef94cbcc7f9f90ef5c238413ee3305c305743a42.zip
feat(relayd): store and query TLS fingerprints
Release-Status: cut Signed-off-by: Xe Iaso <me@xeiaso.net>
-rw-r--r--cmd/relayd/main.go93
-rw-r--r--cmd/relayd/relayd.service6
-rw-r--r--go.mod13
-rw-r--r--go.sum22
4 files changed, 127 insertions, 7 deletions
diff --git a/cmd/relayd/main.go b/cmd/relayd/main.go
index 3185b37..a658673 100644
--- a/cmd/relayd/main.go
+++ b/cmd/relayd/main.go
@@ -3,6 +3,8 @@ package main
import (
"context"
"crypto/tls"
+ "database/sql"
+ "errors"
"flag"
"fmt"
"log"
@@ -19,16 +21,20 @@ import (
"syscall"
"time"
+ "github.com/avct/uasurfer"
"github.com/google/uuid"
+ "github.com/jackc/puddle/v2"
+ _ "modernc.org/sqlite"
"within.website/x/internal"
)
var (
- bind = flag.String("bind", ":3004", "port to listen on")
- certDir = flag.String("cert-dir", "/xe/pki", "where to read mounted certificates from")
- certFname = flag.String("cert-fname", "tls.crt", "certificate filename")
- keyFname = flag.String("key-fname", "tls.key", "key filename")
- proxyTo = flag.String("proxy-to", "http://localhost:5000", "where to reverse proxy to")
+ bind = flag.String("bind", ":3004", "port to listen on")
+ certDir = flag.String("cert-dir", "/xe/pki", "where to read mounted certificates from")
+ certFname = flag.String("cert-fname", "tls.crt", "certificate filename")
+ fpDatabase = flag.String("fp-database", "", "location of fingerprint database")
+ keyFname = flag.String("key-fname", "tls.key", "key filename")
+ proxyTo = flag.String("proxy-to", "http://localhost:5000", "where to reverse proxy to")
)
func main() {
@@ -38,6 +44,7 @@ func main() {
"bind", *bind,
"cert-dir", *certDir,
"cert-fname", *certFname,
+ "fp-database", *fpDatabase,
"key-fname", *keyFname,
"proxy-to", *proxyTo,
)
@@ -82,6 +89,24 @@ func main() {
log.Fatal(err)
}
+ var db *sql.DB
+
+ if fpDatabase != nil {
+ var err error
+ db, err = sql.Open("sqlite", *fpDatabase)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := db.Ping(); err != nil {
+ log.Fatal(err)
+ }
+ }
+
+ uaParser, err := uaParserPool()
+ if err != nil {
+ log.Fatal(err)
+ }
+
h := httputil.NewSingleHostReverseProxy(u)
oldDirector := h.Director
@@ -117,6 +142,47 @@ func main() {
// req.Header.Set("X-TCP-Fingerprint-JA4T", tcpFP.String())
// }
+ if uap, err := uaParser.Acquire(req.Context()); err == nil {
+ defer uap.Release()
+
+ uasurfer.ParseUserAgent(req.UserAgent(), uap.Value())
+
+ vers := uap.Value().Browser.Version
+
+ req.Header.Set("Xe-X-Relayd-UserAgent-Browser", uap.Value().Browser.Name.StringTrimPrefix())
+ req.Header.Set("Xe-X-Relayd-UserAgent-Browser-Version", fmt.Sprintf("%d.%d.%d", vers.Major, vers.Minor, vers.Patch))
+ req.Header.Set("Xe-X-Relayd-UserAgent-OS", uap.Value().OS.Name.StringTrimPrefix())
+ req.Header.Set("Xe-X-Relayd-UserAgent-Platform", uap.Value().OS.Platform.StringTrimPrefix())
+ }
+
+ if ja4 := req.Header.Get("X-TLS-Fingerprint-JA4"); db != nil && ja4 != "" {
+ var application, userAgent, notes sql.NullString
+ if err := db.QueryRowContext(req.Context(), "SELECT application, user_agent_string, notes FROM fingerprints WHERE ja4_fingerprint = ?", ja4).Scan(&application, &userAgent, &notes); err == nil {
+ slog.Debug("found a hit", "application", application, "userAgent", userAgent, "notes", notes)
+ if application.Valid {
+ req.Header.Set("Xe-X-Relayd-Ja4-Application", application.String)
+ }
+
+ if userAgent.Valid {
+ req.Header.Set("Xe-X-Relayd-Ja4-UserAgent", userAgent.String)
+ }
+
+ if notes.Valid {
+ req.Header.Set("Xe-X-Relayd-Ja4-Notes", notes.String)
+ }
+ } else if errors.Is(err, sql.ErrNoRows) {
+ userAgent := req.UserAgent()
+ notes := fmt.Sprintf("Observed via relayd on host %s at %s", req.Host, time.Now().Format(time.RFC3339))
+ if _, err := db.ExecContext(req.Context(), "INSERT INTO fingerprints(user_agent_string, notes, ja4_fingerprint) VALUES (?, ?, ?)", userAgent, notes, ja4); err != nil {
+ slog.Error("can't insert fingerprint into database", "err", err)
+ }
+
+ req.Header.Set("Xe-X-Relayd-New-Client", "true")
+ } else {
+ slog.Debug("can't read from database", "err", err)
+ }
+ }
+
req.Header.Set("X-Forwarded-Host", req.URL.Host)
req.Header.Set("X-Forwarded-Proto", "https")
req.Header.Set("X-Forwarded-Scheme", "https")
@@ -215,3 +281,20 @@ func (kpr *keypairReloader) GetCertificate(*tls.ClientHelloInfo) (*tls.Certifica
return kpr.cert, nil
}
+
+func uaParserPool() (*puddle.Pool[*uasurfer.UserAgent], error) {
+ cons := func(context.Context) (*uasurfer.UserAgent, error) {
+ return &uasurfer.UserAgent{}, nil
+ }
+ des := func(ua *uasurfer.UserAgent) {
+ ua.Reset()
+ }
+
+ pool, err := puddle.NewPool(&puddle.Config[*uasurfer.UserAgent]{
+ Constructor: cons,
+ Destructor: des,
+ MaxSize: 512,
+ })
+
+ return pool, err
+}
diff --git a/cmd/relayd/relayd.service b/cmd/relayd/relayd.service
index cb6fc07..519d36a 100644
--- a/cmd/relayd/relayd.service
+++ b/cmd/relayd/relayd.service
@@ -7,6 +7,12 @@ Restart=always
RestartSec=30s
EnvironmentFile=/etc/within.website/x/relayd.env
LimitNOFILE=infinity
+DynamicUser=true
+CacheDirectory=relayd
+CacheDirectoryMode=0755
+StateDirectory=relayd
+StateDirectoryMode=0755
+ReadWritePaths=/run
[Install]
WantedBy=multi-user.target \ No newline at end of file
diff --git a/go.mod b/go.mod
index 509cd4f..3db189d 100644
--- a/go.mod
+++ b/go.mod
@@ -90,6 +90,7 @@ require (
github.com/TecharoHQ/yeet v0.2.1 // indirect
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
+ github.com/avct/uasurfer v0.0.0-20250320214457-f1f6afeb74db // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.29 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
@@ -109,6 +110,7 @@ require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/evanw/esbuild v0.19.11 // indirect
@@ -128,7 +130,7 @@ require (
github.com/goccy/go-yaml v1.12.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
- github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect
+ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect
github.com/goreleaser/chglog v0.7.0 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect
@@ -152,6 +154,7 @@ require (
github.com/ipfs/go-metrics-interface v0.0.1 // indirect
github.com/ipld/go-car/v2 v2.13.1 // indirect
github.com/ipld/go-ipld-prime v0.21.0 // indirect
+ github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
@@ -183,12 +186,14 @@ require (
github.com/natefinch/atomic v1.0.1 // indirect
github.com/nats-io/nkeys v0.4.9 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
+ github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/term v1.2.0-beta.2 // indirect
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
@@ -222,6 +227,10 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
+ modernc.org/libc v1.62.1 // indirect
+ modernc.org/mathutil v1.7.1 // indirect
+ modernc.org/memory v1.9.1 // indirect
+ modernc.org/sqlite v1.37.0 // indirect
)
require (
@@ -263,7 +272,7 @@ require (
github.com/sacOO7/go-logger v0.0.0-20180719173527-9ac9add5a50d // indirect
github.com/sacOO7/gowebsocket v0.0.0-20221109081133-70ac927be105
github.com/sendgrid/rest v2.6.9+incompatible // indirect
- golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
+ golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
golang.org/x/image v0.24.0
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.39.0
diff --git a/go.sum b/go.sum
index a49f22b..e4aeffa 100644
--- a/go.sum
+++ b/go.sum
@@ -119,6 +119,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/avct/uasurfer v0.0.0-20250320214457-f1f6afeb74db h1:Bk7CPE8ZVhOKA11SZlplKBRWfo17OyyPj0t1dgYN2qI=
+github.com/avct/uasurfer v0.0.0-20250320214457-f1f6afeb74db/go.mod h1:s+GCtuP4kZNxh1WGoqdWI1+PbluBcycrMMWuKQ9e5Nk=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 h1:zAxi9p3wsZMIaVCdoiQp2uZ9k1LsZvmAnoTBeZPXom0=
@@ -245,6 +247,8 @@ github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yA
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c h1:mxWGS0YyquJ/ikZOjSrRjjFIbUqIP9ojyYQ+QZTU3Rg=
github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eaburns/peggy v1.0.2 h1:RJwNVF4cvzLGiKInHGBT8sVwP5HDuVP/PaBU9tHO9Rk=
github.com/eaburns/peggy v1.0.2/go.mod h1:X2pbl0EV5erfnK8kSGwo0lBCgMGokvR1E6KerAoDKXg=
github.com/eaburns/pretty v1.0.0 h1:00W1wrrtMXUSqLPN0txS8j7g9qFXy6nA5vZVqVQOo6w=
@@ -442,6 +446,8 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
+github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
+github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
@@ -548,6 +554,8 @@ github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH
github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ=
github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd h1:gMlw/MhNr2Wtp5RwGdsW23cs+yCuj9k2ON7i9MiJlRo=
github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20230102063945-1a409dc236dd/go.mod h1:wZ8hH8UxeryOs4kJEJaiui/s00hDSbE37OKsL47g+Sw=
+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
+github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
@@ -692,6 +700,8 @@ github.com/ncruces/go-sqlite3 v0.16.0 h1:O7eULuEjvSBnS1QCN+dDL/ixLQZoUGWr466A02G
github.com/ncruces/go-sqlite3 v0.16.0/go.mod h1:2TmAeD93ImsKXJRsUIKohfMvt17dZSbS6pzJ3k6YYFg=
github.com/ncruces/go-sqlite3/gormlite v0.16.0 h1:pBN2h323adfTF3NThIuHa3e7GCesgqrDZFEdAmdFNQE=
github.com/ncruces/go-sqlite3/gormlite v0.16.0/go.mod h1:TyfqjFmR31x1iOth4t6hPgtUf2UvM8zupKSj6TZkP4c=
+github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
+github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/nicklaw5/helix/v2 v2.31.0 h1:/8E5H20D/f3PGmSWT5NWtjwt+M8/GeCjnK/AkoLIFQA=
@@ -769,6 +779,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/redis/go-redis/v9 v9.5.5 h1:51VEyMF8eOO+NUHFm8fpg+IOc1xFuFOhxs3R+kPu1FM=
github.com/redis/go-redis/v9 v9.5.5/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -969,6 +981,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
+golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
+golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -1498,6 +1512,14 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
+modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
+modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
+modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
+modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
+modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
+modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
+modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
+modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0=
nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=