Threat model
GuildGate is designed for controlled writes from an authenticated Discord dashboard user to a resource they are authorized to manage. The browser, client-side JavaScript, and every incoming request field are treated as untrusted.
Addressed threats
| Threat | Control |
|---|---|
| Forged origin or CSRF | Exact-origin allowlist and session-bound CSRF verification |
| Stolen or stale session | Hashed opaque token, idle/absolute expiry, rotation, revocation, and cookie clearing |
| OAuth state replay | Atomic single-use consume() |
| Credential disclosure | Application key-ring encryption and audit redaction |
| Writes after permission loss | Authorization repeated at action time |
| Repeated request | Idempotency reservation, request hash, and replay |
| Concurrent update | Optimistic revision, lease, and fencing token |
| Old worker corrupting a new result | Reservation-owner compare-and-set |
| Rate-limit race | Atomic store operation; advisory locks in PostgreSQL |
| False rollback after commit | Commit finality and a separate afterCommit failure path |
| Realtime channel disclosure | Session/channel authorization, periodic revalidation, and revocation |
| Slow client or unbounded queue | Payload, queue, buffered-byte, subscription, and connection-lifetime limits |
| Duplicate outbox delivery | At-least-once contract and consumer deduplication by event ID |
| Local address in production | HTTPS requirement and loopback/unspecified address rejection |
Trust boundaries
GuildGate assumes these components are configured correctly:
- TLS termination and proxy-header policy
- secret management and key rotation
- PostgreSQL and Redis access control
- Discord OAuth application settings
- correct route-to-action mapping
- fencing and unique constraints for domain writes
- logs, traces, and error monitoring
Deliberately out of scope
The package does not automatically solve:
- XSS in the dashboard HTML or React application
- operating-system protection for the Discord bot token
- database backup and disaster recovery
- third-party API availability
- application-specific role hierarchy and business rules
- malicious dependencies or compromised CI accounts
Fail-open and fail-closed choices
Not every dependency uses the same failure policy. Examples:
- A write is denied when authorization cannot be resolved.
- For an action in the fail-closed audit list, the transaction cannot commit if audit persistence fails.
- If cache or realtime publishing fails after commit, the domain write remains successful; the issue goes to retry handling and telemetry.
- Retry, timeout, and circuit-breaker bounds for Discord reads are selected per action.
Review checklist
When adding an action, answer these questions:
- How is the resource identity parsed from untrusted input?
- When and with which Discord data is authorization checked?
- Can a repeated request safely return the same result?
- Which worker wins when two workers write the same resource?
- How is a failed post-commit side effect retried?
- Does the audit event contain secrets or unnecessary personal data?
- Does a realtime subscription close after permission loss?
Keep those answers visible in the action definition, store contract, and tests.