Repository navigation
Harden /slack/events and report real client IP to Sentry - #466
Conversation
- Return 400 for a /slack/events body that is not a JSON object. Before, json.loads raised and the handler returned an unhandled 500 (Sentry issue 7822478670, an empty curl POST). - Use payload.get() in the url_verification path so a payload with no token or challenge cannot raise KeyError. - Add a Sentry before_send hook that sets REMOTE_ADDR from the X-Forwarded-For entry the ALB appends. The aiohttp integration uses request.remote, so every event showed the ALB's private IP. Hop count is configurable with SENTRY_TRUSTED_PROXY_HOPS (default 1). - poetry update: sentry-sdk 2.74.0, zipcodes 3.0.1, coverage 7.17.2, ruff 0.17.4, pytest-asyncio 1.4.1.post1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| if not forwarded_for: | ||
| return event | ||
|
|
||
| hops = config("SENTRY_TRUSTED_PROXY_HOPS", default=1, cast=int) |
There was a problem hiding this comment.
low bug, confidence medium, pass review
Invalid SENTRY_TRUSTED_PROXY_HOPS raises inside before_send
If the env var is non-numeric, int() raises ValueError on every event that has REMOTE_ADDR and X-Forwarded-For. Sentry swallows hook exceptions and may drop the event or log errors, so a config typo silently loses events. The value should be parsed defensively with a fallback to 1.
Model output from untrusted input. Check it before you act on it.
There was a problem hiding this comment.
Fixed in 4adf12d. Confirmed that Sentry drops the event when before_send raises (sentry_sdk/client.py). A value that is not a positive integer now logs a warning and falls back to 1. Tests cover abc, empty, 0, -2 and 1.5.
| [[package]] | ||
| name = "sentry-sdk" | ||
| version = "2.73.0" | ||
| version = "2.74.0" |
There was a problem hiding this comment.
low supply-chain, confidence low, pass supply-chain
sentry-sdk runtime dependency bumped to a release under 72 hours old
The lockfile moves sentry-sdk from 2.73.0 to 2.74.0, a release the prefilter flagged as published about 49 hours earlier. sentry-sdk is a main (runtime) dependency, and it loads and runs inside the bot, so a compromised release would run with the bot's environment and secrets. The package is from the official PyPI registry with a wheel and no install scripts, and nothing in the bundle shows tampering. The same freshness flag applies to coverage 7.17.2 and ruff 0.17.4, which are dev-only. A maintainer may want to let the release age or compare it with the upstream tag before merging.
Model output from untrusted input. Check it before you act on it.
🔴 Hold deployment: Claude of Duty identified 4 items requiring attentionMission: Review the code. Protect the branch. Deploy the future. Caution Recently published dependency and 2 other hard block(s) block this PR. No waiver can clear them.
Run 42390834691, attempt 1 for head Claude of Duty never approves a PR. A human reviewer gives the final approval. 📋 Full review record: reasons, checks, signals, finding details, model summaries, token usageReviewed snapshot
Reasons the gate failed
No waiver line is shown. At least one reason cannot be waived, so a waiver would not change the result. Checks (code, no model)
Signals (context only, never block)
Dependency changes
Finding details
Blocking is decided by code from category and severity, never by the model. A finding in a blocking category blocks only at high or critical severity.
The Review pass completed and reported no findings. The Security pass completed and reported no findings. Model summaries
Token usage
|
before_send ran int() on the env var. A bad value raised, and Sentry drops the event when before_send raises. Now a value that is not a positive integer logs a warning and uses 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| [[package]] | ||
| name = "sentry-sdk" | ||
| version = "2.73.0" | ||
| version = "2.74.0" |
There was a problem hiding this comment.
low supply-chain, confidence low, pass supply-chain
Runtime dependency sentry-sdk bumped to a release under 72h old
The lockfile moves sentry-sdk from 2.73.0 to 2.74.0, which the prefilter shows was published about 49 hours before the PR. The source is the official PyPI registry and the change is plausibly an ordinary `poetry update`. Even so, sentry-sdk runs in production with the app's DSN and request data, and a very fresh release has had little time for the community to spot a compromised or yanked upload. Coverage 7.17.2 and ruff 0.17.4 (dev-only) are also under 72 hours old. Consider waiting for the release to age or checking the release against upstream before merge. This is a precaution, not evidence of harm.
Model output from untrusted input. Check it before you act on it.
|
Missing or malformed Slack signature headers still give an unhandled exception (and a Sentry event) here, which is the same class of bug this PR sets out to fix.
So with Two ways to fix it:
A test that posts to |
validate_request_signature read X-Slack-Request-Timestamp and X-Slack-Signature with headers[...] and int(). A missing header raised KeyError, and a non-numeric timestamp raised ValueError. Callers catch only InvalidSlackSignature and InvalidTimestamp, so a POST without Slack headers still gave an unhandled 500 and a Sentry error. Now both cases raise InvalidSlackSignature, so every caller returns its normal rejection status (401 for events, 500 for url_verification). Tests cover missing headers and a bad timestamp at the unit level and through /slack/events, and fail on the old code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Fixed in ee8800a with option 1. |
| [[package]] | ||
| name = "sentry-sdk" | ||
| version = "2.73.0" | ||
| version = "2.74.0" |
There was a problem hiding this comment.
low supply-chain, confidence low, pass supply-chain
Runtime dependency sentry-sdk bumped to a release under 72 hours old
The lockfile moves sentry-sdk, a production dependency that runs in the bot process at runtime, to 2.74.0. The registry metadata shows it was published about 49 hours before review, which the prefilter flags as a fresh-publish hard block. coverage 7.17.2 and ruff 0.17.4 (dev-only, run in tests and CI) are in the same position. The source is the official PyPI registry, the package has no install scripts, and the PR gives a plausible reason (a general poetry update). Nothing in the bundle shows tampering. The risk is only that very new releases have had little time for community vetting or yanking. A maintainer may want to confirm the new hashes against the upstream releases or wait out the cooldown.
Model output from untrusted input. Check it before you act on it.
The url_verification test matched aiohttp's 'Error handling request' text. Check for an aiohttp.server record with exc_info instead, so a reworded message cannot hide an unhandled exception. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| [[package]] | ||
| name = "sentry-sdk" | ||
| version = "2.73.0" | ||
| version = "2.74.0" |
There was a problem hiding this comment.
low supply-chain, confidence low, pass supply-chain
Runtime dependency bumped to a release published ~49 hours earlier
sentry-sdk 2.74.0 is in the main (runtime) dependency group and was published about 49 hours before this review, which is under the 72-hour freshness threshold. coverage 7.17.2 and ruff 0.17.4 (both dev-only) are also under it. The registry is the official PyPI and nothing in the delta looks malicious. The bump comes from a blanket poetry update, not from anything the Sentry change needs. A brief hold, or pinning to the previous sentry-sdk release, would let any bad release be yanked before it reaches production.
Model output from untrusted input. Check it before you act on it.
Summary
Fixes the problems found while triaging Sentry issue 7822478670. In that issue, a curl client sent an empty POST to
/slack/events, and the bot returned an unhandled 500./slack/eventsnow returns 400 when the body is not a JSON object (empty, invalid, or a list). Before,request.json()raised before the signature check, so any client could create a Sentry error.payload.get()fortokenandchallenge.REMOTE_ADDRfromrequest.remote, which is the ALB's private IP (172.20.x.x). A newbefore_sendhook (also applied to transactions) replaces it with the IP that the ALB appended toX-Forwarded-For. It uses the rightmost entry, because the client can forge the entries to the left of it.SENTRY_TRUSTED_PROXY_HOPSsets the hop count (default 1). If PII settings removedREMOTE_ADDR, the hook does not add an IP.poetry update): sentry-sdk 2.73.0 to 2.74.0, zipcodes 3.0.1, coverage 7.17.2, ruff 0.17.4, pytest-asyncio 1.4.1.post1.Testing
tests/unit/test_sentry_client_ip.py, and bad-body and url_verification tests intests/endpoints/slack/test_slack_events.py.pytest: 269 passed, 10 skipped.ruff checkandruff format --checkare clean.X-Forwarded-For: 6.6.6.6, 203.0.113.7gaveREMOTE_ADDR: 203.0.113.7in the event.Fixes OPERATIONCODE-PYBOT-7S
🤖 Generated with Claude Code