Skip to content

chore(release): pending release v0.44.2#650

Merged
marcusrbrown merged 3 commits into
releasefrom
next
May 20, 2026
Merged

chore(release): pending release v0.44.2#650
marcusrbrown merged 3 commits into
releasefrom
next

Conversation

@fro-bot
Copy link
Copy Markdown
Contributor

@fro-bot fro-bot Bot commented May 20, 2026

Pending Release: v0.44.2

This PR tracks changes pending release. Released on the next auto-release cycle (Sunday/Wednesday) or via manual dispatch.

Merge this PR to trigger a release. Releases also run automatically on Sunday/Wednesday at 20:00 UTC, or via manual workflow dispatch.

Commits Since Last Release


Auto-generated by the release pipeline. Updated: 2026-05-20 06:51 UTC

* fix(gateway): plumb AWS credentials through optional secret files

Reads AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and the optional
AWS_SESSION_TOKEN from secret files in loadGatewayConfig, validates
the access/secret pair, and injects them into the S3Client constructor.
When neither is set, the SDK default credential chain takes over so
IRSA/EC2-instance-role deployments keep working.

- AwsCredentials interface added next to ObjectStoreConfig
- Pair validation: throws when exactly one of the access/secret pair is set
- AWS_SESSION_TOKEN is optional and only attached when the pair is present
- Orphan session token (set without the pair) logs an info-level warning
  and falls through to the SDK default chain
- S3Client receives credentials only when explicitly configured

Gateway tests: 87 -> 95 (+8). Runtime tests: 350 -> 352 (+2).

* fix(gateway): mount AWS credential secrets, bound log output, guard directory secrets

deploy/compose.yaml now mounts the three AWS credential bind-mounts
(access key id, secret access key, session token) alongside the
existing Discord and S3 secret files, so operators wiring infra-as-code
can supply them through the same pattern. Each service also gains a
bounded json-file logging block (10m x 3) to keep small-VM disk usage
in check.

readOptionalSecret now asserts the secret path resolves to a file
via statSync, not just that the path exists. When a bind-mount source
doesn't exist on the host, Docker materializes the path as a directory;
the new guard surfaces a clear startup error pointing operators at the
likely cause instead of failing later with a raw EISDIR.

Gateway tests: 95 -> 96 (+1). Docker compose config validates clean
once the three AWS secret files exist (empty files for the
default-chain path).

* fix(gateway): real readiness healthcheck backed by Discord clientReady

The Dockerfile healthcheck no longer succeeds before the gateway is
genuinely connected to Discord. A new readiness helper clears any
stale `/tmp/gateway-ready` flag at process startup and registers a
one-time `clientReady` listener that writes the flag once Discord
confirms the bot is fully connected. The healthcheck polls for the
flag plus PID 1 liveness, so `docker compose up --wait` only
returns once the daemon is actually ready.

- packages/gateway/src/readiness.ts: setupReadinessFlag(client, logger)
- main.ts wires it between client creation and login()
- Dockerfile HEALTHCHECK switched from no-op to file-and-pid probe with
  --interval=10s --timeout=3s --retries=12 --start-period=45s
- compose.yaml drops the gateway healthcheck override (CA-cert wait is
  already gated by depends_on: mitmproxy: service_healthy)
- deploy/README.md documents the new readiness semantics and the AWS
  credential `touch` block for operators wiring infra-as-code

Gateway tests: 96 -> 101 (+5).

* chore(gateway): tighten deploy contract review pass

- `stat.isFile()` check uses explicit boolean comparison
- Orphan AWS_SESSION_TOKEN warning emits via `console.warn` rather than
  `console.log` (matches makeLogger's warn-level pattern; lifts the
  eslint-disable directive)
- Drop the redundant `NodeJS.ErrnoException` cast in readiness stale-flag
  cleanup -- in-operator narrowing is sufficient
- Strengthen the stale-readiness test to assert flag unlink ordering
  at listener-registration time, not just final state
- Replace `as string` / `as unknown` assertions in config.test.ts with
  proper narrowing
- Add custom-endpoint+credentials coverage to s3-adapter
- Document the AWS credential pair contract for operators in
  deploy/README.md

Runtime tests: 352 -> 353 (+1).

* docs(plans): add deploy contract hardening plan

Captures the v0.44.x deploy contract hardening pass: AWS credential
plumbing, compose mounts and bounded logging, secret-path directory
guard, and a real readiness healthcheck backed by Discord clientReady.

* fix(gateway): move readiness flag out of /tmp

Static analyzers flag predictable paths in /tmp as a temp-file weakness
(CWE-377/378). The flag is intentionally deterministic — the Dockerfile
healthcheck has to know where to find it — so fs.mkdtempSync() doesn't
apply. Move the default to /var/run/fro-bot/gateway-ready, which the
Dockerfile creates with 0700 permissions before the HEALTHCHECK runs.

The FRO_BOT_READY_FLAG_PATH env override is preserved; tests already use
isolated mkdtempSync paths and didn't need changes. Rebuilt dist/ for
the readiness path baked into main.mjs.

* fix(gateway): address static-analysis findings on secret read + readiness write

- Collapse `existsSync` + `statSync` in `readOptionalSecret` into a single
  `try/catch` on `statSync`. ENOENT falls through to the env-var fallback;
  any other error rethrows. Behavior is identical (missing file → null,
  directory → clear error, file → read) without the check-then-act window.
- Pass `{mode: 0o600}` to `writeFileSync` for the gateway-ready flag and
  its corresponding test fixture. Defense-in-depth on the readiness flag
  permissions and a signal to static analyzers that the write is
  intentional and owner-scoped.

* fix(gateway): close stat-then-read race; document credential rotation; harden compose

- `readOptionalSecret` collapses to a single `readFileSync` that catches
  ENOENT (fall through to env-var) and EISDIR (clear directory error).
  Removes the stat-then-read pattern that static analyzers flag as a
  file-system race.
- Drop redundant `|| exit 1` from the gateway HEALTHCHECK -- the shell
  already exits non-zero when test/kill fail.
- Convert secret bind mounts in compose.yaml to long syntax with
  `bind.create_host_path: false`. Missing secret source files now fail
  at `docker compose up` time instead of silently materializing as
  directories. The readOptionalSecret directory guard remains as a
  backstop.
- Document static-credential rotation in deploy/README.md: rotate by
  writing the new value into the secret file and restarting the
  gateway. STS or instance-role credentials should leave the pair
  empty and rely on the SDK default credential chain.
@fro-bot fro-bot Bot requested review from fro-bot and marcusrbrown as code owners May 20, 2026 04:44
marcusrbrown and others added 2 commits May 19, 2026 23:48
* fix(gateway): drop privileged intents from the default set

The Discord client now requests Guilds and GuildMessages as its baseline.
Callers that need MessageContent or GuildMembers pass them explicitly
via options.intents — the existing Set-based merge composes them on top
of the smaller default. No behavioral change for callers that supply
the privileged intents; existing deployments that relied on the implicit
defaults need to opt in via the next unit's config knob.

* fix(gateway): add DISCORD_PRIVILEGED_INTENTS opt-in for privileged intents

Operators that need MessageContent or GuildMembers now set
DISCORD_PRIVILEGED_INTENTS in the gateway env (or the matching
_FILE secret). The parser accepts a comma-separated list with
whitespace tolerance, allowlists exactly those two values, and
throws a clear startup error on any other token. main.ts threads
the parsed list into createDiscordClient as the intents override
so the existing Set-based merge composes them on top of the
non-privileged baseline.

Existing deployments that depended on the implicit privileged
defaults must set this env var on next deploy.

* fix(gateway): document the intent posture and isolate test runs

- New tests cover the non-privileged baseline, single-intent opt-ins
  for MessageContent and GuildMembers, and both-opted-in composition.
- The obsolete "default intents include MessageContent" assertion is
  gone, replaced by an exact-baseline check.
- A small sibling module exports validateTokenIsFake; a beforeAll in
  client.test.ts calls it against process.env.DISCORD_TOKEN so the
  suite refuses to run with what looks like a real bot token.
- packages/gateway/AGENTS.md documents the new
  DISCORD_PRIVILEGED_INTENTS knob in a Configuration knobs section.

* chore(gateway): close review pass on the intent posture flip

- Reject prototype-property tokens (`constructor`, `__proto__`, etc.)
  in the privileged-intent allowlist by switching to a hasOwnProperty
  check, with regression tests for the three common bypass vectors.
- Loosen `DiscordClientOptions.intents` and `DEFAULT_INTENTS` to
  `readonly GatewayIntentBits[]` so callers pass `config.privilegedIntents`
  directly without a defensive spread.
- Extract `expectClientIntents()` in the gateway client tests so the
  bitfield-cast idiom lives in one place.
- Use an explicit `=== true` boolean check in `validateTokenIsFake` to
  match the project's strict-boolean convention.
- Configure vitest/expect-expect with `assertFunctionNames` so helpers
  like `expectClientIntents` count as assertions; drop the now-unused
  `eslint-disable` comments across the gateway test suite.

* docs(plans): record the gateway intent posture flip plan

Captures the v0.45-track gateway change: drop privileged intents from
the default set, expose them as opt-in via DISCORD_PRIVILEGED_INTENTS,
add the test-isolation guard, document the knob in
packages/gateway/AGENTS.md. Origin issue: #646.

* fix(gateway): tighten test-token guard and verify main wiring

- Extract `makeDiscordClientFromConfig` from the main program so the
  config -> client wiring has a small, directly-testable surface.
  Add `main.test.ts` with three scenarios covering empty intents,
  `MessageContent`-only, and both privileged intents.
- Tighten `validateTokenIsFake`: bare `fake` and `MOCK` no longer pass.
  The regex now requires `fake-token`, `mock-token`, `test-token-fake`,
  or `test` followed by a non-alphanumeric character. Update the
  guard's tests to match — adds coverage for `testing123` and
  similar previously-accepted-but-shouldn't-have-been strings.
- Document the `expectClientIntents` helper's BitField cast in
  `client.test.ts` so future readers understand the discord.js
  internal-API dependency.
@marcusrbrown marcusrbrown merged commit fa60201 into release May 20, 2026
1 check passed
@marcusrbrown marcusrbrown deleted the next branch May 20, 2026 06:52
@fro-bot fro-bot mentioned this pull request May 20, 2026
46 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant