Security and Secrets¶
This page covers safe handling for server keys, global authorization keys, webhook signatures, and logs.
Key Types¶
| Key | Sent as | Use |
|---|---|---|
| Server key | Server-Key |
Required for server API requests. |
| Global key | Authorization |
Optional PRC large-app authorization flow. |
Create clients with explicit values or environment variables:
import os
from erlc_api import AsyncERLC
api = AsyncERLC(
os.environ["ERLC_SERVER_KEY"],
global_key=os.environ.get("ERLC_GLOBAL_KEY"),
)
Secret Storage¶
Prefer environment variables, deployment secrets, or your platform secret store. Do not commit server keys to source control.
Suggested environment names:
The wrapper does not require these exact names; they are conventions for your application.
Logging Redaction¶
Do not log:
Server-Key;Authorization;- raw request headers;
- full request objects from frameworks;
- webhook raw bodies when they may include private moderation text.
Log safe context instead:
Wrapper exceptions expose short body excerpts and request metadata without requiring secret headers.
When you need to correlate logs with a configured key, log a fingerprint instead of the key:
from erlc_api.security import key_fingerprint
logger.info("Configured ERLC server key %s", key_fingerprint(server_key))
Fingerprints are one-way hash prefixes. They are useful for diagnostics, but they are not proof that a key is valid.
Setup Validation¶
Validate keys during setup or bot startup before enabling command routes:
from erlc_api import AsyncERLC, ValidationStatus
api = AsyncERLC(server_key)
async with api:
result = await api.validate_key()
if result.status is not ValidationStatus.OK:
raise RuntimeError(f"ERLC key validation failed: {result.status}")
If a key repeatedly returns auth failures, stop using that configured server, disable command routes for it, and ask an operator to rotate or re-enter the key. If your bot leaves or is removed from a Discord guild, remove that guild's stored server key from your own storage.
The HTTP layer tracks repeated auth failures in memory by key fingerprint only:
from erlc_api.security import auth_failures
for record in auth_failures.snapshot():
if record.repeated:
logger.warning("Repeated ERLC auth failures for %s", record.fingerprint)
This is process-local guidance. It does not block keys, store raw secrets, or replace your own account/guild configuration lifecycle.
Webhook Verification¶
Verify the raw request body before trusting JSON:
from erlc_api.webhooks import WebhookError, assert_valid_event_webhook_signature
async def handler(request):
raw_body = await request.body()
try:
assert_valid_event_webhook_signature(raw_body=raw_body, headers=request.headers)
except WebhookError:
return {"ok": False}, 401
payload = await request.json()
return {"ok": True, "payload": payload}
Important rules:
- Use the exact raw bytes from the request.
- Do not reserialize JSON before verification.
- Reject invalid signatures with a non-2xx response.
- Keep timestamp skew checks enabled unless your deployment requires otherwise.
Command Safety¶
The wrapper normalizes command syntax, but it does not invent PRC permissions or Discord role policies. Put application-level checks in your bot, web route, middleware, or custom command predicate.
from erlc_api import CommandPolicy
announce_policy = CommandPolicy(allowed={"h"}, max_length=120)
@router.command("announce", predicate=lambda _invocation, ctx: ctx.arg(0) is not None)
async def announce(ctx):
command_text = announce_policy.validate(f"h {ctx.rest()}")
...
Common Mistakes¶
- Printing
apiobjects or request headers in logs. - Verifying webhook signatures after parsing JSON.
- Treating
global_keyas a replacement forserver_key. - Building a public web endpoint that executes commands without app-level auth.
Related Pages¶
Previous Page: Custom Commands Reference | Next Page: Rate Limits, Retries, and Reliability