chore(release): pending release v0.44.2#650
Merged
Merged
Conversation
* 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.
* 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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