serverless-ircd 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +4 -0
- package/.github/workflows/deploy-aws.yml +156 -32
- package/.github/workflows/deploy-cf-tcp.yml +11 -9
- package/.github/workflows/deploy-cf.yml +14 -14
- package/CHANGELOG.md +550 -0
- package/README.md +275 -222
- package/apps/aws-stack/README.md +3 -5
- package/apps/aws-stack/bin/aws.ts +82 -9
- package/apps/aws-stack/cdk.json +0 -3
- package/apps/aws-stack/package.json +3 -4
- package/apps/aws-stack/src/aws-stack.ts +177 -52
- package/apps/aws-stack/src/static-site.ts +323 -0
- package/apps/aws-stack/tests/smoke-helpers.test.ts +1 -1
- package/apps/aws-stack/tests/stack.test.ts +267 -92
- package/apps/aws-stack/tests/static-site.test.ts +491 -0
- package/apps/aws-stack/tests/synth-no-bundle.test.ts +0 -1
- package/apps/cf-tcp-container/package.json +2 -3
- package/apps/cf-tcp-container/src/container-server.ts +33 -10
- package/apps/cf-tcp-container/tests/config-loader.test.ts +43 -0
- package/apps/cf-tcp-container/tests/container-server.test.ts +249 -1
- package/apps/cf-tcp-container/tests/persistence.test.ts +9 -0
- package/apps/cf-tcp-container/tests/tls-e2e.test.ts +24 -5
- package/apps/cf-tcp-container/wrangler.toml +1 -10
- package/apps/cf-worker/package.json +3 -4
- package/apps/cf-worker/wrangler.toml +12 -71
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/server.ts +115 -48
- package/apps/local-cli/tests/config-resolution.test.ts +65 -0
- package/apps/local-cli/tests/motd-file-non-error.test.ts +29 -0
- package/apps/local-cli/tests/rehash.test.ts +147 -0
- package/apps/local-cli/tests/server-helpers.test.ts +63 -0
- package/apps/local-cli/tests/tcp.test.ts +89 -0
- package/apps/local-cli/tests/ws-subprotocol.test.ts +92 -0
- package/apps/web/landing/favicon.ico +0 -0
- package/apps/web/landing/index.html +227 -3
- package/apps/web/package.json +3 -2
- package/apps/web/scripts/build.mjs +91 -6
- package/apps/web/src/build-env.ts +125 -4
- package/apps/web/src/config-schema.ts +20 -6
- package/apps/web/src/render-docs.ts +292 -0
- package/apps/web/static/{config.staging.json → config.prod-aws.json} +3 -2
- package/apps/web/tests/build-env.test.ts +210 -9
- package/apps/web/tests/build-smoke.test.ts +33 -4
- package/apps/web/tests/config-schema.test.ts +149 -25
- package/apps/web/tests/landing-content.test.ts +103 -0
- package/apps/web/tests/render-docs.test.ts +198 -0
- package/docs/AWS-Adapter-Architecture.md +3 -2
- package/docs/AWS-Deployment.md +670 -96
- package/docs/AWS-TCP-Deployment.md +20 -45
- package/docs/Cloudflare-Deployment-Guide.md +87 -113
- package/docs/Cloudflare-TCP-Deployment.md +25 -49
- package/docs/Release-Process.md +27 -23
- package/docs/Services.md +102 -23
- package/docs/WebClientGuide.md +35 -26
- package/package.json +7 -10
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/aws-runtime.ts +15 -1
- package/packages/aws-adapter/src/cdk-table-defs.ts +6 -11
- package/packages/aws-adapter/src/config-loader.ts +19 -2
- package/packages/aws-adapter/src/dynamo-services-store.ts +7 -0
- package/packages/aws-adapter/src/handlers/connect.ts +26 -0
- package/packages/aws-adapter/src/handlers/default.ts +190 -123
- package/packages/aws-adapter/src/handlers/index.ts +67 -23
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +13 -8
- package/packages/aws-adapter/src/index.ts +5 -7
- package/packages/aws-adapter/src/origin-allowlist.ts +94 -0
- package/packages/aws-adapter/src/serialize.ts +15 -0
- package/packages/aws-adapter/src/tables.ts +2 -12
- package/packages/aws-adapter/tests/aws-harness.ts +0 -1
- package/packages/aws-adapter/tests/aws-runtime.test.ts +23 -1
- package/packages/aws-adapter/tests/config-loader.test.ts +66 -0
- package/packages/aws-adapter/tests/connect.test.ts +124 -1
- package/packages/aws-adapter/tests/connection-counter.test.ts +17 -0
- package/packages/aws-adapter/tests/default-occ.test.ts +219 -0
- package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +11 -0
- package/packages/aws-adapter/tests/global-setup.ts +28 -1
- package/packages/aws-adapter/tests/gone-exception.test.ts +21 -2
- package/packages/aws-adapter/tests/handlers.test.ts +117 -11
- package/packages/aws-adapter/tests/migrate-accounts-to-services.test.ts +164 -0
- package/packages/aws-adapter/tests/nlb-stream.test.ts +29 -1
- package/packages/aws-adapter/tests/origin-allowlist.test.ts +110 -0
- package/packages/aws-adapter/tests/ping-checker.test.ts +0 -1
- package/packages/aws-adapter/tests/stats.test.ts +0 -3
- package/packages/aws-adapter/tests/sweeper.test.ts +20 -1
- package/packages/aws-adapter/tests/tables.test.ts +1 -8
- package/packages/aws-adapter/tests/transactions.test.ts +0 -1
- package/packages/cf-adapter/package.json +1 -5
- package/packages/cf-adapter/src/cf-runtime.ts +59 -8
- package/packages/cf-adapter/src/channel-do.ts +13 -3
- package/packages/cf-adapter/src/connection-do.ts +284 -115
- package/packages/cf-adapter/src/d1-services-store.ts +63 -26
- package/packages/cf-adapter/src/env.ts +11 -10
- package/packages/cf-adapter/src/index.ts +0 -6
- package/packages/cf-adapter/tests/cf-runtime.test.ts +101 -1
- package/packages/cf-adapter/tests/channel-do.test.ts +118 -1
- package/packages/cf-adapter/tests/connection-do-coverage.test.ts +460 -0
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +222 -51
- package/packages/cf-adapter/tests/connection-do-sasl-d1.test.ts +62 -38
- package/packages/cf-adapter/tests/d1-services-store.test.ts +53 -2
- package/packages/cf-adapter/tests/serialize.test.ts +25 -0
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/src/account-migration.ts +140 -0
- package/packages/irc-core/src/commands/account-auth.ts +60 -35
- package/packages/irc-core/src/commands/chanserv.ts +288 -4
- package/packages/irc-core/src/commands/hostserv.ts +38 -3
- package/packages/irc-core/src/commands/index.ts +1 -0
- package/packages/irc-core/src/commands/join.ts +41 -35
- package/packages/irc-core/src/commands/memoserv.ts +1 -1
- package/packages/irc-core/src/commands/nickserv.ts +138 -15
- package/packages/irc-core/src/commands/registration.ts +28 -17
- package/packages/irc-core/src/commands/sasl.ts +22 -31
- package/packages/irc-core/src/commands/service-aliases.ts +52 -0
- package/packages/irc-core/src/commands/topic.ts +23 -10
- package/packages/irc-core/src/config.ts +35 -9
- package/packages/irc-core/src/credential-hashing.ts +11 -54
- package/packages/irc-core/src/index.ts +1 -0
- package/packages/irc-core/src/ports.ts +159 -179
- package/packages/irc-core/src/state/channel.ts +17 -0
- package/packages/irc-core/src/types.ts +38 -10
- package/packages/irc-core/tests/account-migration.test.ts +133 -0
- package/packages/irc-core/tests/commands/chanserv.test.ts +668 -1
- package/packages/irc-core/tests/commands/hostserv.test.ts +71 -0
- package/packages/irc-core/tests/commands/join.test.ts +179 -0
- package/packages/irc-core/tests/commands/markread.test.ts +54 -0
- package/packages/irc-core/tests/commands/memoserv.test.ts +19 -0
- package/packages/irc-core/tests/commands/nickserv.test.ts +422 -3
- package/packages/irc-core/tests/commands/oper.test.ts +15 -0
- package/packages/irc-core/tests/commands/registration.test.ts +336 -108
- package/packages/irc-core/tests/commands/sasl.test.ts +194 -169
- package/packages/irc-core/tests/commands/service-aliases.test.ts +52 -0
- package/packages/irc-core/tests/commands/unified-account.test.ts +102 -84
- package/packages/irc-core/tests/credential-hashing.test.ts +0 -78
- package/packages/irc-core/tests/message-store.test.ts +5 -0
- package/packages/irc-core/tests/persistent-services-store.test.ts +71 -12
- package/packages/irc-core/tests/ports.test.ts +71 -0
- package/packages/irc-core/tests/services-store.test.ts +204 -0
- package/packages/irc-core/vitest.config.ts +6 -1
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +80 -44
- package/packages/irc-server/tests/actor.test.ts +384 -50
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/in-memory-harness.ts +8 -5
- package/packages/irc-test-support/src/scenarios.ts +21 -6
- package/packages/irc-test-support/tests/in-memory-harness.test.ts +19 -0
- package/packages/irc-test-support/vitest.config.ts +6 -1
- package/pnpm-workspace.yaml +1 -0
- package/scripts/__tests__/deploy-web-aws.test.ts +491 -0
- package/scripts/deploy-web-aws.mjs +290 -0
- package/scripts/package.json +23 -0
- package/scripts/tsconfig.test.json +12 -0
- package/scripts/vitest.config.ts +19 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/ci-hardening/src/index.ts +2 -0
- package/tools/ci-hardening/src/validate.ts +57 -0
- package/tools/ci-hardening/tests/deploy-aws-oidc.test.ts +96 -0
- package/tools/ci-hardening/tests/validate.test.ts +42 -0
- package/tools/load-test/package.json +1 -1
- package/tools/load-test/src/client.ts +13 -13
- package/tools/load-test/tests/client.test.ts +258 -2
- package/tools/load-test/tests/config.test.ts +39 -0
- package/tools/load-test/tests/harness.test.ts +21 -0
- package/tools/load-test/tests/metrics.test.ts +7 -0
- package/tools/migrate-accounts-to-services.ts +270 -0
- package/tools/package.json +2 -1
- package/tools/seed-aws-accounts.ts +35 -10
- package/tools/seed-cf-accounts.ts +42 -9
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/tools/tcp-ws-forwarder/tests/close-error.test.ts +40 -0
- package/tools/tcp-ws-forwarder/tests/defensive-branches.test.ts +78 -0
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +51 -0
- package/tools/tcp-ws-forwarder/tests/logger.test.ts +31 -1
- package/packages/aws-adapter/src/account-store.ts +0 -121
- package/packages/aws-adapter/src/dynamo-account-store.ts +0 -95
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +0 -223
- package/packages/aws-adapter/tests/account-store.test.ts +0 -276
- package/packages/cf-adapter/src/d1-account-store.ts +0 -198
- package/packages/cf-adapter/tests/d1-account-store.test.ts +0 -274
- package/packages/irc-core/tests/account-store.test.ts +0 -131
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,556 @@ For the release process itself — versioning policy, pre-release checklist,
|
|
|
10
10
|
cutting a tag, rolling back — see [`docs/Release-Process.md`](docs/Release-Process.md).
|
|
11
11
|
Cross-reference `progress.md` / `tickets.md` for per-ticket detail.
|
|
12
12
|
|
|
13
|
+
## [0.10.0] - 2026-08-13
|
|
14
|
+
|
|
15
|
+
The main change in this release is **AWS web client parity** — the
|
|
16
|
+
vendored Kiwi IRC SPA is no longer Cloudflare-only. AWS now hosts it
|
|
17
|
+
via an **S3 + CloudFront + OAC** `StaticSite` construct (opt-in via
|
|
18
|
+
CDK context) with a **stack-output-driven two-phase deploy**, and the
|
|
19
|
+
API Gateway `$connect` route gains an opt-in `WEB_ORIGINS` CSWSH
|
|
20
|
+
defence that mirrors the CF Worker's. Alongside it: the SASL
|
|
21
|
+
`AccountStore` is **collapsed into `ServicesStore`** as the single
|
|
22
|
+
credential home (a one-shot backfill migration ships for existing
|
|
23
|
+
deployments), **NickServ `SET PASSWORD`** lands as self-service
|
|
24
|
+
password change, and the staging/prod deploy split is **collapsed**
|
|
25
|
+
into a single target per platform. A round of **AWS deploy security
|
|
26
|
+
hardening** ships: the CI workflow is **OIDC-only** (no long-lived
|
|
27
|
+
access keys), and API Gateway full-frame body logging
|
|
28
|
+
(`DataTraceEnabled`) is **off by default and hard-locked** so IRC
|
|
29
|
+
frames are never written to CloudWatch. Finally, `UuidIdFactory` now
|
|
30
|
+
sources its random bytes from `globalThis.crypto` (CSPRNG) instead of
|
|
31
|
+
`Math.random`, so generated `msgid` / session ids are cryptographic-
|
|
32
|
+
grade.
|
|
33
|
+
|
|
34
|
+
### Added — AWS web client (`@serverless-ircd/aws-stack`, `@serverless-ircd/aws-adapter`, `@serverless-ircd/web`)
|
|
35
|
+
|
|
36
|
+
The web client is no longer Cloudflare-only. AWS provisions a
|
|
37
|
+
static-site origin for the SPA while the wss IRC endpoint stays on
|
|
38
|
+
API Gateway — two origins, same `apps/web/dist/` artifact.
|
|
39
|
+
|
|
40
|
+
- **`StaticSite` construct** (`apps/aws-stack/src/static-site.ts`): a
|
|
41
|
+
private S3 origin bucket fronted by a CloudFront distribution with
|
|
42
|
+
**Origin Access Control (OAC)** — direct S3 access is denied by the
|
|
43
|
+
bucket policy; only CloudFront URLs resolve. `defaultRootObject` is
|
|
44
|
+
`index.html` (the project landing page at `/`), and a custom error
|
|
45
|
+
response maps S3 `403`/`404` → `200 /webclient/index.html` so
|
|
46
|
+
client-side SPA routes resolve. Custom domain (ACM certificate +
|
|
47
|
+
Route53 `ARecord` alias) is opt-in via CDK context
|
|
48
|
+
(`webSiteCustomDomain` / `webSiteCertificateArn` /
|
|
49
|
+
`webSiteHostedZoneName` / `webSiteHostedZoneId`); omitted by default
|
|
50
|
+
to use `*.cloudfront.net`. Three stable CfnOutputs (`WebsiteURL`,
|
|
51
|
+
`WebsiteBucketName`, `WebsiteDistributionId`) drive the deploy
|
|
52
|
+
pipeline below.
|
|
53
|
+
- **CloudFront directory-index viewer-request function**: S3 REST
|
|
54
|
+
(used with OAC) does not serve index documents, so `/docs/` or
|
|
55
|
+
`/docs` would 404 and fall through to the SPA error response
|
|
56
|
+
(serving the Kiwi shell instead of the rendered docs index). A
|
|
57
|
+
viewer-request CloudFront function now resolves directory-style
|
|
58
|
+
URIs: trailing-slash paths (`/docs/`, `/`) get `index.html`
|
|
59
|
+
appended as an internal rewrite, and extensionless paths without a
|
|
60
|
+
trailing slash (`/docs`) get a `301` redirect to the trailing-slash
|
|
61
|
+
form so the browser resolves relative asset links against the
|
|
62
|
+
directory. Real assets (`.html`, `.css`, `.js`, …) are served
|
|
63
|
+
unchanged. Mirrors the CF Worker `[assets]` binding, which resolves
|
|
64
|
+
directory indexes automatically.
|
|
65
|
+
- **`$connect` Origin allowlist (CSWSH defence)**: API Gateway
|
|
66
|
+
WebSocket has no built-in `Origin` validation, so the `$connect`
|
|
67
|
+
Lambda now enforces it. `WEB_ORIGINS` (comma-separated) is parsed
|
|
68
|
+
into a normalised lowercase set at cold start; a browser-sent
|
|
69
|
+
`Origin` not in the set is denied with `403` (API Gateway closes
|
|
70
|
+
the upgrade). The defence is **opt-in** — unset `WEB_ORIGINS`
|
|
71
|
+
disables the check entirely so existing bare-IRC deployments without
|
|
72
|
+
a web frontend are unchanged on upgrade. Non-browser clients (curl,
|
|
73
|
+
WeeChat, the `tcp-ws-forwarder`, the NLB TCP+TLS path) never send
|
|
74
|
+
`Origin` and always proceed. Deliberate divergence from the CF
|
|
75
|
+
policy: **explicit-allowlist only** (no same-origin auto-derive),
|
|
76
|
+
because the SPA is on a CloudFront origin and the wss endpoint is
|
|
77
|
+
on an API Gateway origin, so the request's own host is never the
|
|
78
|
+
SPA's origin. Mirrors the CF Worker's parsing semantics (trimmed,
|
|
79
|
+
lowercased, scheme+host match).
|
|
80
|
+
- **`prod-aws` build env**: a new `apps/web/static/config.prod-aws.json`
|
|
81
|
+
+ `--env prod-aws` (and a `build:prod-aws` pnpm script) select the
|
|
82
|
+
AWS API-Gateway-shaped config. The `server` field carries
|
|
83
|
+
`{{API_ID}}` / `{{REGION}}` / `{{STAGE}}` placeholders the deploy
|
|
84
|
+
pipeline substitutes at bake time, so no concrete wss URL is
|
|
85
|
+
hardcoded in the repo. Because irc-framework prepends `wss://`
|
|
86
|
+
itself from `tls: true`, the baked URL is **split** into a
|
|
87
|
+
hostname-only `server` and a stage-name `direct_path` (e.g.
|
|
88
|
+
`/<stage>`) — baking the full `wss://` URL into `server` produced
|
|
89
|
+
a doubled-scheme `wss://wss//...` URL that Firefox rejects.
|
|
90
|
+
- **Stack-output-driven deploy pipeline** (`scripts/deploy-web-aws.mjs`,
|
|
91
|
+
a new `@serverless-ircd/deploy-scripts` workspace package): unlike
|
|
92
|
+
the CF Worker (where `wrangler deploy` ships the SPA in the same
|
|
93
|
+
command as the runtime), the AWS SPA deploy is **two-phase** because
|
|
94
|
+
the baked `config.json` carries the API Gateway WebSocket URL as a
|
|
95
|
+
literal that does not exist at synth time. The script runs
|
|
96
|
+
`describe-stacks` → `pnpm --filter web build:prod-aws -- --api-url
|
|
97
|
+
<ConnectUrl>` → `aws s3 sync --delete` →
|
|
98
|
+
`cloudfront create-invalidation /*`. Fails loudly on missing stack
|
|
99
|
+
outputs or unsubstituted `{{...}}` placeholders. The
|
|
100
|
+
`deploy-web` CI job (`.github/workflows/deploy-aws.yml`) runs it
|
|
101
|
+
with `needs: deploy`.
|
|
102
|
+
|
|
103
|
+
### Added — NickServ `SET PASSWORD` (`@serverless-ircd/irc-core`)
|
|
104
|
+
|
|
105
|
+
- **Self-service password change.** `PRIVMSG NickServ :SET PASSWORD
|
|
106
|
+
<old> <new>` re-verifies the current password (defence against a
|
|
107
|
+
hijacked `+r` session), enforces a configurable minimum (default 8)
|
|
108
|
+
and a fixed maximum (256, the scrypt input budget), then re-hashes
|
|
109
|
+
and persists via `ServicesStore.setNickPassword`. The caller's
|
|
110
|
+
session stays `+r`; other sessions on the same account are not
|
|
111
|
+
kicked. Wrong old password is indistinguishable from "no such
|
|
112
|
+
account" (no enumeration vector). Wired through
|
|
113
|
+
`InMemoryServicesStore` and `PersistentServicesStore` with round-
|
|
114
|
+
trip tests on the D1 and DynamoDB backends.
|
|
115
|
+
|
|
116
|
+
### Added — CSPRNG-backed ids (`@serverless-ircd/irc-core`)
|
|
117
|
+
|
|
118
|
+
- **`UuidIdFactory` now uses `globalThis.crypto.getRandomValues`.** The
|
|
119
|
+
previous `Math.random`-based UUIDv4 generation was unsuitable for
|
|
120
|
+
unforgeable `msgid` / session ids — a predictable id would let a
|
|
121
|
+
client forge message attribution. The global `crypto.getRandomValues`
|
|
122
|
+
is available on Node ≥ 19 and Cloudflare Workers without an import.
|
|
123
|
+
Generated ids are now cryptographic-grade. The `IdFactory` port and
|
|
124
|
+
the deterministic `testIdFactory` test seam are unchanged, so
|
|
125
|
+
reducer determinism (reducers never touch real randomness) is
|
|
126
|
+
preserved; only the production factory changed.
|
|
127
|
+
|
|
128
|
+
### Changed — Single credential home (`@serverless-ircd/irc-core`, `@serverless-ircd/cf-adapter`, `@serverless-ircd/aws-adapter`)
|
|
129
|
+
|
|
130
|
+
The SASL `AccountStore` port is **removed**. `ServicesStore` is now
|
|
131
|
+
the single credential home: SASL PLAIN, SASL EXTERNAL (CertFP),
|
|
132
|
+
NickServ `IDENTIFY`, and `PASS <nick>:<password>` all verify through
|
|
133
|
+
the same scrypt-hashed `verifyNick` / `verifyCertFP` surface. This
|
|
134
|
+
closes the cross-store credential drift flagged as a known limitation
|
|
135
|
+
in v0.9.0 — a nick registered via NickServ `REGISTER` after the worker
|
|
136
|
+
booted now authenticates via SASL/PASS immediately, because there is
|
|
137
|
+
no second store to drift out of sync with.
|
|
138
|
+
|
|
139
|
+
- **`ServicesStore` grows CertFP**: `verifyCertFP` / `addCertFP` /
|
|
140
|
+
`removeCertFP` are added to the port, and `RegisteredNick` gains a
|
|
141
|
+
`certSubjects: string[]` field so SASL EXTERNAL resolves through the
|
|
142
|
+
services store (previously SASL EXTERNAL only worked against the
|
|
143
|
+
static `InMemoryAccountStore`, never the hashed D1/DynamoDB table).
|
|
144
|
+
- **D1 `cert_subjects` column auto-migrates**: the CF D1 path runs
|
|
145
|
+
`ALTER TABLE nickserv_accounts ADD COLUMN cert_subjects` inside
|
|
146
|
+
`migrateServicesSchema` on cold start (`CREATE TABLE IF NOT EXISTS`
|
|
147
|
+
+ the `ALTER`), so no manual DDL is needed. DynamoDB is schemaless.
|
|
148
|
+
- **Removed**: `AccountStore` port, `InMemoryAccountStore`,
|
|
149
|
+
`HashedAccountStore`, the D1/DynamoDB `account-store`
|
|
150
|
+
implementations, `SaslPayload` / `SaslResult` / `SaslAccountCredential`
|
|
151
|
+
/ `constantTimeEquals` / `ingestAccountCredential`, and the
|
|
152
|
+
`accounts` / `Accounts` table reads. The new code only reads
|
|
153
|
+
`nickserv_accounts` / `Services`.
|
|
154
|
+
- **`tools/migrate-accounts-to-services.ts`**: a one-shot, idempotent,
|
|
155
|
+
never-overwrite backfill of legacy `accounts` / `Accounts` rows into
|
|
156
|
+
`nickserv_accounts` / `Services`. Reads the still-deployed
|
|
157
|
+
`SASL_ACCOUNTS` env var at run time so env-seeded accounts are
|
|
158
|
+
carried into the services table. `cert_subjects` defaults to `'[]'`
|
|
159
|
+
for every migrated row (the legacy table had no cert column).
|
|
160
|
+
**Run this before the v0.10.0 build deploys** — see the migration
|
|
161
|
+
section below.
|
|
162
|
+
|
|
163
|
+
### Changed — Single deploy target per platform
|
|
164
|
+
|
|
165
|
+
The staging/prod deploy split is **collapsed** into one target per
|
|
166
|
+
platform. Staging vs production isolation is now driven by which
|
|
167
|
+
account/region (AWS) or which Worker credentials (CF) the deploy
|
|
168
|
+
targets, not by stack/table/Worker name templating.
|
|
169
|
+
|
|
170
|
+
- **AWS CDK**: drops the `environmentName` prop, `prefixedTableName`,
|
|
171
|
+
and the `IrcAwsStack-<env>` stack id. Tables use bare logical ids
|
|
172
|
+
(`Connections`, `Nicks`, …); the stack is always `IrcAwsStack`.
|
|
173
|
+
- **Cloudflare**: drops the `env.staging` blocks in the cf-worker and
|
|
174
|
+
cf-tcp-container wrangler configs.
|
|
175
|
+
- **Web build**: drops `build:staging` / `config.staging.json`; `--env`
|
|
176
|
+
now accepts only `prod` (or the default dev config) and `prod-aws`.
|
|
177
|
+
- **CI**: collapses `deploy-*: staging` jobs into single `deploy` /
|
|
178
|
+
`deploy-web` jobs; script names align (`pnpm deploy:cf`,
|
|
179
|
+
`pnpm deploy:aws`, `pnpm deploy:cf:tcp`, `pnpm smoke:aws`,
|
|
180
|
+
`pnpm smoke:cf`). Deploy workflows remain `workflow_dispatch`-only
|
|
181
|
+
(no push trigger).
|
|
182
|
+
|
|
183
|
+
### Added — AWS deploy security
|
|
184
|
+
|
|
185
|
+
- **OIDC-only CI deploys.** The GitHub Actions deploy workflow
|
|
186
|
+
(`deploy-aws.yml`) accepts **only** GitHub OIDC web-identity
|
|
187
|
+
credentials. `aws-access-key-id` / `aws-secret-access-key` inputs
|
|
188
|
+
are dropped from every `configure-aws-credentials` step in both the
|
|
189
|
+
`deploy` and `deploy-web` jobs; the workflow requires
|
|
190
|
+
`AWS_DEPLOY_ROLE_ARN` and fails fast with `::error::` if it is
|
|
191
|
+
unset, then asserts `aws sts get-caller-identity` matches the
|
|
192
|
+
configured role before any `cdk deploy` (defence-in-depth against a
|
|
193
|
+
silent fallback to ambient static credentials). Long-lived access
|
|
194
|
+
keys powerful enough to deploy CloudFormation + IAM + Lambda +
|
|
195
|
+
DynamoDB are effectively account-admin; a single exfil vector
|
|
196
|
+
yields full account takeover. The full IAM trust policy snippet,
|
|
197
|
+
condition keys, and verification steps are in
|
|
198
|
+
`docs/AWS-Deployment.md` §17. A `ci-hardening` lint + test suite
|
|
199
|
+
(`findAwsCredentialSteps`, `readWorkflowPermissions`) locks the
|
|
200
|
+
OIDC-only contract on every PR.
|
|
201
|
+
- **API Gateway `DataTraceEnabled` off by default and hard-locked.**
|
|
202
|
+
Full-frame body tracing wrote every IRC frame (`PASS <password>`,
|
|
203
|
+
`AUTHENTICATE <base64-SASL-PLAIN>`, `JOIN #chan <key>`,
|
|
204
|
+
`PRIVMSG`/`NOTICE`) to the APIGW execution-log group, leaking
|
|
205
|
+
credentials to anyone with `logs:GetLogEvents`. Now safe-by-default
|
|
206
|
+
(`false`); because the staging/prod split collapsed, there is no
|
|
207
|
+
quiet "non-prod" path to re-enable it. The only way back on is an
|
|
208
|
+
explicit **two-step opt-in** (`-c allowDataTrace=true` **and** `-c
|
|
209
|
+
iUnderstandThisLeaksCredentials=true`) that refuses synthesis unless
|
|
210
|
+
both flags are set — see `docs/AWS-Deployment.md` §7.6. Tear the
|
|
211
|
+
sandbox stack down immediately after debugging; never deploy that
|
|
212
|
+
combination to a shared account.
|
|
213
|
+
|
|
214
|
+
### Changed — Documentation
|
|
215
|
+
|
|
216
|
+
- **`README.md` updated** to cover AWS web client hosting (the web
|
|
217
|
+
client section was previously Cloudflare-only), the AWS `$connect`
|
|
218
|
+
CSWSH defence, the OIDC-only deploy model, the `DataTraceEnabled`
|
|
219
|
+
hard-lock, and the `scripts/deploy-web-aws.mjs` two-phase deploy.
|
|
220
|
+
The CF-vs-AWS origin difference is laid out in a table, and the
|
|
221
|
+
CSWSH section now documents both adapters' modes (same-origin
|
|
222
|
+
auto-derive on CF; explicit-allowlist-only on AWS).
|
|
223
|
+
|
|
224
|
+
### ⚠️ Migration required
|
|
225
|
+
|
|
226
|
+
- **Run `tools/migrate-accounts-to-services.ts` BEFORE deploying
|
|
227
|
+
v0.10.0** if the deployment has any rows in the legacy `accounts`
|
|
228
|
+
(CF D1) / `Accounts` (AWS DynamoDB) table. The v0.10.0 build only
|
|
229
|
+
reads `nickserv_accounts` / `Services`, so legacy rows that are not
|
|
230
|
+
backfilled will stop authenticating. The script is one-shot,
|
|
231
|
+
idempotent (`INSERT OR IGNORE` on D1 / conditional `PutItem` on
|
|
232
|
+
DynamoDB), and never-overwrites a nick already registered in the
|
|
233
|
+
services table. It reads the still-deployed `SASL_ACCOUNTS` env var
|
|
234
|
+
at run time so env-seeded accounts are carried through. See the
|
|
235
|
+
script header for the per-platform invocation:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Cloudflare D1
|
|
239
|
+
node --import tsx tools/migrate-accounts-to-services.ts \
|
|
240
|
+
--platform cf --database <d1-name> --remote
|
|
241
|
+
|
|
242
|
+
# AWS DynamoDB
|
|
243
|
+
node --import tsx tools/migrate-accounts-to-services.ts \
|
|
244
|
+
--platform aws --accounts-table Accounts --services-table Services \
|
|
245
|
+
--region us-east-1
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
- **Single deploy target: rename your deploy commands.** The
|
|
249
|
+
staging-specific commands are gone. Update any scripts, runbooks, or
|
|
250
|
+
CI that called `pnpm deploy:cf:staging` / `pnpm deploy:aws:staging`
|
|
251
|
+
/ `--env staging` to the single-target form (`pnpm deploy:cf`,
|
|
252
|
+
`pnpm deploy:aws`). Staging vs production isolation is now by
|
|
253
|
+
account/region (AWS) or by Worker credentials (CF), not by command
|
|
254
|
+
suffix.
|
|
255
|
+
- **AWS CI: switch to OIDC.** If the repo still has
|
|
256
|
+
`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` secrets, delete them
|
|
257
|
+
and invalidate the underlying IAM access key. Configure
|
|
258
|
+
`AWS_DEPLOY_ROLE_ARN` (a GitHub-OIDC-trusted role) per
|
|
259
|
+
`docs/AWS-Deployment.md` §17; there is no static-key fallback.
|
|
260
|
+
- **No persisted-DO-state schema change.** `PERSISTED_STATE_VERSION`
|
|
261
|
+
stays `1`. The `cert_subjects` D1 column is added by an idempotent
|
|
262
|
+
`ALTER TABLE` on cold start; the D1/DynamoDB services-row shape
|
|
263
|
+
change is backward-compatible for reads. **Code rollback across
|
|
264
|
+
this release is safe** provided the account-migration backfill was
|
|
265
|
+
run (rolling back the code re-introduces the legacy `accounts`/
|
|
266
|
+
`Accounts` reads, which still work as long as the table still
|
|
267
|
+
exists — it is not auto-dropped).
|
|
268
|
+
|
|
269
|
+
### Known limitations
|
|
270
|
+
|
|
271
|
+
- **The web client remains partial.** The Playwright headless-browser
|
|
272
|
+
e2e against a deployed frontend is still pending; the SPA is
|
|
273
|
+
exercised via the WS smoke (`scripts/smoke.mjs`) and the `apps/web`
|
|
274
|
+
unit / build-smoke suite.
|
|
275
|
+
- **The AWS web client `StaticSite` requires a custom domain to
|
|
276
|
+
provision via `bin/aws.ts`.** Provisioning the construct without a
|
|
277
|
+
custom domain (default `*.cloudfront.net` only) currently requires
|
|
278
|
+
editing `bin/aws.ts` to pass `webSite: {}` directly. Tracked as a
|
|
279
|
+
follow-up.
|
|
280
|
+
- Same transport limits as v0.9.0: the **CF TCP path requires
|
|
281
|
+
Cloudflare Spectrum (Enterprise tier)** plus a stateful Container
|
|
282
|
+
origin; the **AWS TCP path** uses NLB + Lambda streaming and is
|
|
283
|
+
subject to Lambda idle-timeout / stream-duration limits. The
|
|
284
|
+
WebSocket path remains the zero-extra-deps default.
|
|
285
|
+
- **mTLS (for SASL `EXTERNAL`) requires a custom domain + uploaded
|
|
286
|
+
CA/trust-store** on both platforms and is not enabled by default.
|
|
287
|
+
The `cert_subjects` plumbing lands the server-side storage; the
|
|
288
|
+
adapter-side client-cert capture at the TLS edge remains a
|
|
289
|
+
follow-up.
|
|
290
|
+
- **Client compatibility sweep** (WeeChat / HexChat / IRCCloud /
|
|
291
|
+
TheLounge / matrix-IRC bridge) remains pending.
|
|
292
|
+
- **Coverage hardening is partial.** `irc-core`, `irc-server`,
|
|
293
|
+
`in-memory-runtime`, and **`cf-adapter`** (new this release) sit at
|
|
294
|
+
100%; `aws-adapter` and `aws-stack` clear the 90% gate. The
|
|
295
|
+
remaining packages (`local-cli`, `load-test`, `cf-tcp-container`,
|
|
296
|
+
`tcp-ws-forwarder`, `irc-test-support`, `web`) are **above the gate
|
|
297
|
+
but below 100%** — follow-ups drive each to full coverage.
|
|
298
|
+
|
|
299
|
+
### Security
|
|
300
|
+
|
|
301
|
+
- **OIDC-only AWS deploys** (above): removes long-lived access keys
|
|
302
|
+
from the deploy path — the highest-impact exfil vector against an
|
|
303
|
+
AWS account with deploy permissions.
|
|
304
|
+
- **APIGW `DataTraceEnabled` hard-locked off** (above): IRC frames
|
|
305
|
+
(including `PASS`, `AUTHENTICATE <SASL-PLAIN>`, channel keys) are
|
|
306
|
+
no longer written to CloudWatch even if an operator toggles the
|
|
307
|
+
console setting by hand; the two-flag escape hatch cannot be set by
|
|
308
|
+
accident.
|
|
309
|
+
- **CSPRNG-backed `msgid` / session ids** (above): generated ids are
|
|
310
|
+
now cryptographic-grade, closing a forgery vector in the previous
|
|
311
|
+
`Math.random`-based `UuidIdFactory`.
|
|
312
|
+
- No other auth path changed. The scrypt-hashed credential store,
|
|
313
|
+
server-password gate, and CF-side CSWSH defence are unchanged from
|
|
314
|
+
v0.9.0.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## [0.9.0] - 2026-08-10
|
|
319
|
+
|
|
320
|
+
The main change in this release is **rounding out the integrated IRC
|
|
321
|
+
services surface** that landed in v0.8.0: ChanServ gains the prefix and
|
|
322
|
+
roster mutation verbs operators expect (`OP` / `DEOP` / `VOICE` /
|
|
323
|
+
`DEVOICE` / `KICK` / `BAN` / `UNBAN`), HostServ **auto-applies an
|
|
324
|
+
assigned vhost on identify** (no per-session `HostServ ON`), HostServ
|
|
325
|
+
HELP hides oper-only commands from non-opers, NickServ grows an `ID`
|
|
326
|
+
alias for `IDENTIFY`, and **NickServ `INFO` no longer leaks the
|
|
327
|
+
registrant's email** to non-owners. Alongside it: service **shortcut
|
|
328
|
+
verbs** (`/NICKSERV`, `/NS`, `/CS`, `/HS`, `/MS`, `/OS`, …) that the
|
|
329
|
+
daemon rewrites to the equivalent `PRIVMSG <Service>`, `echo-message`
|
|
330
|
+
reflection of `PRIVMSG` to service nicks, and the long-missing **JOIN
|
|
331
|
+
`332`/`333` topic numerics** on reconnect/rejoin (regardless of how the
|
|
332
|
+
topic came to be set). A real PASS-login bug is fixed: a client that
|
|
333
|
+
completes registration before sending `PASS <nick>:<password>` is now
|
|
334
|
+
logged in instead of receiving a silent no-op. The AWS adapter gets
|
|
335
|
+
another round of consistency hardening — every `Connections` `GetCommand`
|
|
336
|
+
now uses `ConsistentRead: true`. The web client renders the `docs/` wiki
|
|
337
|
+
to standalone HTML at `/docs/` and gains a client-specific "how to
|
|
338
|
+
connect" section on the landing page.
|
|
339
|
+
|
|
340
|
+
### Added — Services surface expansion (`@serverless-ircd/irc-core`)
|
|
341
|
+
|
|
342
|
+
- **ChanServ `OP` / `DEOP` / `VOICE` / `DEVOICE` / `KICK` / `BAN` /
|
|
343
|
+
`UNBAN`**: founder-or-`AUTOOP`-level callers can grant/remove prefix
|
|
344
|
+
modes on demand, kick through ChanServ, and manage channel bans. Each
|
|
345
|
+
verb emits the corresponding `:ChanServ!ChanServ@services MODE` /
|
|
346
|
+
`KICK` broadcast and an `ApplyChannelDelta` against the channel
|
|
347
|
+
authority (the same mechanism MLOCK reasserts use), so the roster
|
|
348
|
+
and ban list update atomically with the broadcast. A target not on
|
|
349
|
+
the channel produces a NOTICE and no state change. Privilege gating
|
|
350
|
+
reuses the existing access-list check — no new store methods.
|
|
351
|
+
- **HostServ auto-applies an assigned vhost on identify.** SASL
|
|
352
|
+
PLAIN/EXTERNAL, NickServ `IDENTIFY`, and PASS-auth now consult
|
|
353
|
+
`services.getVhost(account)` on a successful login: when an assigned
|
|
354
|
+
vhost exists, `applyVhost` runs immediately — `state.host` is set,
|
|
355
|
+
`state.vhostActive` is stamped, and any currently-joined
|
|
356
|
+
`chghost`-capable peers see the `CHGHOST` broadcast. At SASL/PASS-auth
|
|
357
|
+
time (pre-JOIN) there are no peers so no broadcast, but the vhost is
|
|
358
|
+
visible in the subsequent `001` welcome. Users no longer need to run
|
|
359
|
+
`HostServ ON` every session.
|
|
360
|
+
- **NickServ `ID` alias for `IDENTIFY`.** `PRIVMSG NickServ :ID <pw>`
|
|
361
|
+
and `ID <nick> <pw>` behave identically to `IDENTIFY` — the alias
|
|
362
|
+
is not advertised in HELP (mirrors Atheme's hidden shorthand).
|
|
363
|
+
- **Service shortcut verbs** (`@serverless-ircd/irc-server`):
|
|
364
|
+
`/NICKSERV`, `/NS`, `/CS`, `/HS`, `/MS`, `/OS` (and their long forms)
|
|
365
|
+
are rewritten at the actor to the equivalent
|
|
366
|
+
`PRIVMSG <ServiceNick> :<args>`. Clients with a dedicated
|
|
367
|
+
slash-command UX (WeeChat, HexChat, IRCCloud) now work without a
|
|
368
|
+
custom alias file. The aliases are case-insensitive and accept both
|
|
369
|
+
colon-trailing and space-joined arg shapes.
|
|
370
|
+
- **`echo-message` reflection for service PRIVMSG**: a client that
|
|
371
|
+
has negotiated `echo-message` now sees its own
|
|
372
|
+
`PRIVMSG NickServ :IDENTIFY hunter2` reflected back before the
|
|
373
|
+
NickServ NOTICE reply, matching the echo behaviour for every other
|
|
374
|
+
PRIVMSG target. The echo is emitted before the reply so labeled-
|
|
375
|
+
response batches wrap both correctly.
|
|
376
|
+
|
|
377
|
+
### Added — Web client (`apps/web`)
|
|
378
|
+
|
|
379
|
+
- **`docs/` wiki rendered to `/docs/`.** The Gitea-wiki markdown under
|
|
380
|
+
the `docs/` submodule is now rendered to standalone HTML at
|
|
381
|
+
`apps/web/dist/docs/<slug>.html` and served by the existing
|
|
382
|
+
`[assets]` binding at `/docs/<slug>.html` (with `Home.md` →
|
|
383
|
+
`/docs/index.html`). Intra-docs Markdown links are rewritten to
|
|
384
|
+
`.html`, a shared stylesheet is emitted, and an `ADR-Index` page
|
|
385
|
+
links every ADR. Requires `git submodule update --init docs` before
|
|
386
|
+
build; the build fails loudly with the recovery command if the
|
|
387
|
+
submodule is missing or empty.
|
|
388
|
+
- **Landing-page "how to connect" section.** The static landing page
|
|
389
|
+
gains a CSS-only tabbed picker with copy-paste connect snippets for
|
|
390
|
+
the common stock IRC clients (WeeChat, HexChat, irssi, IRCCloud),
|
|
391
|
+
replacing the previous per-client static guides. The picker covers
|
|
392
|
+
both the WebSocket path and the `irc+tls :6697` path.
|
|
393
|
+
|
|
394
|
+
### Changed — PASS-based account login (`@serverless-ircd/irc-core`)
|
|
395
|
+
|
|
396
|
+
- **A late `PASS <nick>:<password>` now logs the connection in.**
|
|
397
|
+
Previously, a client that completed registration (`NICK` + `USER`
|
|
398
|
+
resolving before `PASS` arrived, or any `PASS` sent after `CAP END`
|
|
399
|
+
resolved) was silently rejected — the `passReducer` short-circuited
|
|
400
|
+
at `state.registration === 'registered'` and emitted `462
|
|
401
|
+
ERR_ALREADYREGISTRED` for any post-registration `PASS`. The shared
|
|
402
|
+
verify + `applyAccountSuccess` pipeline now runs on a
|
|
403
|
+
post-registration `PASS <nick>:<password>`: on success it emits
|
|
404
|
+
`900 RPL_LOGGEDIN` + `903 RPL_SASLSUCCESS`, stamps `+r`, and runs
|
|
405
|
+
the read-marker / away / memo replay in-band, exactly like the
|
|
406
|
+
at-registration path. A bare `PASS <value>` (no colon) is a silent
|
|
407
|
+
no-op once registration has completed — a server password is
|
|
408
|
+
meaningless post-registration, and emitting `462` for it was
|
|
409
|
+
counterproductive. The pre-registration duplicate-PASS `462` (two
|
|
410
|
+
`PASS`es before `NICK`+`USER`) is preserved unchanged.
|
|
411
|
+
|
|
412
|
+
### Fixed — Channel correctness (`@serverless-ircd/irc-core`)
|
|
413
|
+
|
|
414
|
+
- **JOIN now emits `332 RPL_TOPIC` / `333 RPL_TOPICWHOISTIME` from any
|
|
415
|
+
source of the topic.** The previous JOIN reducer keyed topic
|
|
416
|
+
emission solely on the ChanServ KEEPTOPIC restore path (an empty
|
|
417
|
+
channel this join), so reconnecting clients after a deploy saw the
|
|
418
|
+
NAMES list but no topic — even when the topic survived the deploy
|
|
419
|
+
in storage. The emission now keys off `state.topic`, so a topic
|
|
420
|
+
restored from a KEEPTOPIC snapshot, a deploy snapshot with
|
|
421
|
+
KEEPTOPIC off, or an earlier joiner all produce the correct
|
|
422
|
+
numerics. The KEEPTOPIC restore path still maintains `state.topic`
|
|
423
|
+
for an empty channel; only the emission trigger changed.
|
|
424
|
+
- **No auto-op for the first joiner of a registered channel off the
|
|
425
|
+
access list.** A registered channel that had been emptied (so its
|
|
426
|
+
roster snapshot was fresh) used to auto-op the first joiner
|
|
427
|
+
unconditionally as a "founder fallback", which let any client claim
|
|
428
|
+
`+o` by joining an empty registered channel. The fallback now only
|
|
429
|
+
fires when the joiner is identified as the founder (or is on the
|
|
430
|
+
access list); an unidentified first joiner joins without prefix
|
|
431
|
+
modes.
|
|
432
|
+
|
|
433
|
+
### Fixed — AWS adapter (`@serverless-ircd/aws-adapter`)
|
|
434
|
+
|
|
435
|
+
- **Strongly-consistent `Connections` reads across every handler.**
|
|
436
|
+
`$disconnect`'s `cleanupConnection` and the NLB streaming handler's
|
|
437
|
+
per-chunk row read were eventually consistent, so under DynamoDB
|
|
438
|
+
replication lag they could miss the row that was just written (or
|
|
439
|
+
updated) — dropping the first frame of a raw `:6697` connection or
|
|
440
|
+
skipping nick release + membership fanout on `$disconnect`. Every
|
|
441
|
+
`GetCommand` against the `Connections` table in `aws-adapter/src/**`
|
|
442
|
+
now sets `ConsistentRead: true`, matching the `$default` fix from
|
|
443
|
+
v0.8.0. A unit test asserts each call site carries the flag.
|
|
444
|
+
- **Bounded `docker info` probe in the test global-setup.** On hosts
|
|
445
|
+
with Docker Desktop installed but not running, the macOS `docker`
|
|
446
|
+
CLI hangs indefinitely waiting for the daemon to spin up — and
|
|
447
|
+
`testcontainers` inherits that hang, stalling the whole `pnpm test`
|
|
448
|
+
run. The aws-adapter `globalSetup` now pre-probes `docker info`
|
|
449
|
+
with a 5-second exec timeout; if Docker is not reachable, it logs
|
|
450
|
+
and falls through to the Java ZIP fallback (or skip) instead of
|
|
451
|
+
hanging. No behaviour change when Docker is up.
|
|
452
|
+
|
|
453
|
+
### Fixed — HostServ help leak (`@serverless-ircd/irc-core`)
|
|
454
|
+
|
|
455
|
+
- **HostServ HELP hides oper-only commands from non-opers.** The
|
|
456
|
+
help/unknown NOTICE previously listed all seven HostServ commands
|
|
457
|
+
(`ON, OFF, REQUEST, SET, APPROVE, REJECT, LIST`) to every caller,
|
|
458
|
+
leaking the existence and names of oper commands. The notice
|
|
459
|
+
helpers now branch on `state.userModes.oper`: a non-oper sees only
|
|
460
|
+
`ON, OFF, REQUEST`; an oper sees the full list. The per-handler
|
|
461
|
+
oper gates are unchanged, so a non-oper who guesses an oper
|
|
462
|
+
command name still gets the existing permission-denied NOTICE.
|
|
463
|
+
|
|
464
|
+
### Fixed — NickServ INFO privacy (`@serverless-ircd/irc-core`)
|
|
465
|
+
|
|
466
|
+
- **NickServ `INFO` no longer leaks the registrant's email to
|
|
467
|
+
non-owners.** `handleInfo` previously returned the `Email:` line
|
|
468
|
+
unconditionally to any caller, identified or not — a remote,
|
|
469
|
+
unidentified user could enumerate registered nicks and harvest
|
|
470
|
+
email addresses. The `Email:` line is now gated behind
|
|
471
|
+
`state.account === rec.account` (the owner) or
|
|
472
|
+
`state.userModes.oper === true` (an oper). The owner path still
|
|
473
|
+
shows the full record; `INFO` with no target defaults to the
|
|
474
|
+
caller's own nick (owner path). The `Nick:` / `Account:` lines
|
|
475
|
+
remain visible to all callers (consistent with how a WHOIS against
|
|
476
|
+
a registered nick surfaces the account name).
|
|
477
|
+
|
|
478
|
+
### Changed — Local CLI (`apps/local-cli`)
|
|
479
|
+
|
|
480
|
+
- **`operCreds` plumbed through the config schema.** The local CLI's
|
|
481
|
+
`ServerConfig` now carries `operCreds` end-to-end (was wired ad-hoc
|
|
482
|
+
through constructor options); OPER auth against configured creds
|
|
483
|
+
works the same way it does in the cloud adapters. The transport
|
|
484
|
+
seams (WS / TCP) are also extracted into named helpers for
|
|
485
|
+
readability.
|
|
486
|
+
|
|
487
|
+
### Changed — CI / tooling
|
|
488
|
+
|
|
489
|
+
- CI workflows now explicitly checkout every submodule (`docs/`,
|
|
490
|
+
`apps/web/upstream/`) so the render-docs and Kiwi build steps run
|
|
491
|
+
deterministically on every runner, not just maintainer laptops.
|
|
492
|
+
|
|
493
|
+
### ⚠️ Migration required
|
|
494
|
+
|
|
495
|
+
- **No persisted-DO-state schema change.** `PERSISTED_STATE_VERSION`
|
|
496
|
+
stays `1`. The ChanServ roster commands, HostServ auto-vhost, and
|
|
497
|
+
the JOIN topic-numeric fix are runtime behaviour, not persisted DO
|
|
498
|
+
fields. **Code rollback across this release is safe.**
|
|
499
|
+
- **No D1 / DynamoDB schema change.** The existing services tables
|
|
500
|
+
(`nickserv_accounts`, `chanserv_channels`, …) carry the new
|
|
501
|
+
behaviour unchanged. The `cert_subjects` column flagged for the
|
|
502
|
+
in-progress services credential consolidation is **not yet
|
|
503
|
+
present** — operators do not need to run any migration command for
|
|
504
|
+
this release.
|
|
505
|
+
- **Behaviour change: late `PASS` no longer emits `462`.** A
|
|
506
|
+
post-registration `PASS <value>` used to reply
|
|
507
|
+
`462 ERR_ALREADYREGISTRED`; it is now silent. Clients that keyed
|
|
508
|
+
off the `462` to detect "you already sent PASS" will no longer see
|
|
509
|
+
it. No client is known to do this (the numeric is informational),
|
|
510
|
+
but scripted regression tests against this behaviour must be
|
|
511
|
+
updated (the in-tree `irc-test-support` scenario was updated).
|
|
512
|
+
- **Behaviour change: late `PASS <nick>:<password>` now logs in.**
|
|
513
|
+
Clients that intentionally sent the colon form post-registration
|
|
514
|
+
expecting it to be ignored will now be logged in. This is the
|
|
515
|
+
intended fix; flag it in operator runbooks if the deployment was
|
|
516
|
+
relying on the silent-reject for some access-control flow.
|
|
517
|
+
|
|
518
|
+
### Known limitations
|
|
519
|
+
|
|
520
|
+
- **The `AccountStore` / `ServicesStore` credential drift is still
|
|
521
|
+
present.** A nick registered via NickServ `REGISTER` after the
|
|
522
|
+
worker booted still cannot authenticate via SASL PLAIN/EXTERNAL or
|
|
523
|
+
`PASS <nick>:<password>` against the *fresh* password — those paths
|
|
524
|
+
consult the SASL `AccountStore` (`accounts` table), which
|
|
525
|
+
`registerNick` does not write back to. The PASS-login fix above
|
|
526
|
+
makes the at-registration and post-registration paths symmetric,
|
|
527
|
+
but the underlying cross-store drift remains tracked for a
|
|
528
|
+
follow-up release that consolidates the credential into a single
|
|
529
|
+
store. Operators who need a NickServ-registered nick to work with
|
|
530
|
+
SASL/PASS immediately should re-seed via
|
|
531
|
+
`tools/seed-cf-accounts.ts` (D1) / `tools/seed-aws-accounts.ts`
|
|
532
|
+
(DynamoDB) with the same password.
|
|
533
|
+
- Same transport limits as v0.8.0: the **CF TCP path requires
|
|
534
|
+
Cloudflare Spectrum (Enterprise tier)** plus a stateful Container
|
|
535
|
+
origin; the **AWS TCP path** uses NLB + Lambda streaming and is
|
|
536
|
+
subject to Lambda idle-timeout / stream-duration limits. The
|
|
537
|
+
WebSocket path remains the zero-extra-deps default.
|
|
538
|
+
- **mTLS (for SASL `EXTERNAL`) requires a custom domain + uploaded
|
|
539
|
+
CA/trust-store** on both platforms and is not enabled by default.
|
|
540
|
+
- **Client compatibility sweep** (WeeChat / HexChat / IRCCloud /
|
|
541
|
+
TheLounge / matrix-IRC bridge) remains pending.
|
|
542
|
+
- **Coverage hardening is partial.** `irc-core`, `irc-server`, and
|
|
543
|
+
`in-memory-runtime` sit at 100%; `aws-adapter` and `aws-stack`
|
|
544
|
+
clear the 90% gate. The remaining packages (`cf-adapter`,
|
|
545
|
+
`local-cli`, `load-test`, `cf-tcp-container`, `tcp-ws-forwarder`,
|
|
546
|
+
`irc-test-support`, `web`) are **above the gate but below 100%** —
|
|
547
|
+
follow-ups drive each to full coverage.
|
|
548
|
+
|
|
549
|
+
### Security
|
|
550
|
+
|
|
551
|
+
- **NickServ `INFO` email disclosure** (Fixed above): a remote,
|
|
552
|
+
unidentified client could harvest email addresses from every
|
|
553
|
+
registered nick. The disclosure is now gated behind owner-or-oper.
|
|
554
|
+
- **First-joiner op-takeover on empty registered channels** (Fixed
|
|
555
|
+
above): an unidentified client could claim `+o` by being the first
|
|
556
|
+
to join an empty registered channel. The auto-op fallback now
|
|
557
|
+
requires founder/access-list identification.
|
|
558
|
+
- No other auth path changed. The scrypt-hashed credential store,
|
|
559
|
+
server-password gate, and CSWSH defense are unchanged from v0.8.0.
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
13
563
|
## [0.8.0] - 2026-08-08
|
|
14
564
|
|
|
15
565
|
The main change in this release is **integrated IRC services**: NickServ,
|