fix(gateway): bound teams webhook body size, document deserialization safety#948
fix(gateway): bound teams webhook body size, document deserialization safety#948antigenius0910 wants to merge 1 commit into
Conversation
… safety The teams webhook parses the inbound Bot Framework Activity JSON *before* JWT auth, because validating the token requires serviceUrl/channelId from the body. That makes the parse reachable unauthenticated. - Add a 256 KB pre-auth body-size guard (returns 413), mirroring the feishu adapter's WEBHOOK_BODY_LIMIT pattern. Real activities are a few KB; this shrinks the unauthenticated parse attack surface from axum's 2 MB default. - Document why the serde_json::from_str into Activity is safe, in response to an OX untrusted-deserialization finding (false positive): Activity is a derive-only strict DTO with no custom Deserialize, no side-effectful Drop, and no enum dispatch; serde_json cannot instantiate arbitrary types, and the recommended "strict DTO + validate after" pattern is already in place (JWT, activity-type, tenant-allowlist). No functional change to valid traffic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OpenAB PR ScreeningThis is auto-generated by the OpenAB project-screening flow for context collection and reviewer handoff.
Screening reportscreened #948 and moved the project item from `Incoming` to `PR-Screening`.GitHub comment: #948 (comment) IntentThis PR tries to close out an OX security finding against the Teams webhook deserialization path. The object-injection/RCE concern appears to be a false positive for FeatFix/security hardening. Adds a 256 KiB pre-auth body-size guard and documents the Teams Who It ServesDeployers and agent runtime operators running the Teams gateway adapter. Maintainers also get clearer security-review context. Rewritten PromptHarden the Microsoft Teams gateway webhook against unauthenticated oversized request bodies. Reject bodies above the Teams activity limit before Merge PitchLow-risk defense-in-depth. The main reviewer concern is whether 256 KiB is comfortably above legitimate Teams payloads and whether focused tests cover the boundary. Best-Practice ComparisonOpenClaw and Hermes mostly do not apply; this is not scheduling, persistence, routing, or daemon state. The relevant principle is explicit gateway boundary control before processing unauthenticated input. Implementation OptionsConservative: current guard plus comment. Comparison TablePosted in the comment. RecommendationUse the balanced path: advance screening, but ask review to verify focused coverage for oversized Teams bodies and a representative valid activity. |
What problem does this solve?
An OX security scan flagged
serde_json::from_str(&body)intoActivityin the Teams adapter (teams.rs) as untrusted deserialization (object-injection / RCE). On review this is a false positive for this code, but it surfaced one genuine, already-bounded risk worth hardening: the Bot FrameworkActivityJSON is parsed before JWT auth (the token can't be validated withoutserviceUrl/channelIdfrom the body), so the parse is reachable unauthenticated.This PR documents the deserialization-safety rationale at the call site and adds a defense-in-depth pre-auth body-size cap.
Closes #
Discord Discussion URL: https://discord.com/channels/1491295327620169908/1491365157010542652/1509845175579705414
At a Glance
Prior Art & Industry Research
Not applicable — this is a trivial, self-contained security hardening (input size bound) plus an inline code comment. The pattern is already established within this repo: the feishu adapter uses the same
WEBHOOK_BODY_LIMIT+body.len()check returning413(feishu.rs:2112, feishu.rs:2214).Proposed Solution
const WEBHOOK_BODY_LIMIT: usize = 256 * 1024;and reject oversized bodies with413 PAYLOAD_TOO_LARGEbefore parsing/auth. Mirrors the feishu adapter (which uses 1 MB); 256 KB is more conservative and still far above any real activity (a few KB).serde_json::from_strintoActivityis safe:Activityis a derive-only strict DTO (String/Option<_>/ nested structs) with no customDeserialize, no side-effectfulDrop, and no enum dispatch; serde_json's data model cannot instantiate arbitrary types (unlike bincode/serde_yaml/rmp-serde); and the recommended "strict DTO + validate after" pattern is already in place (JWT → activity-type → tenant allowlist). DoS is bounded by serde_json's recursion limit (128) and the new body cap.Why this approach?
The OX finding's threat class (object injection / RCE) does not apply to
serde_json+ a derive-only DTO, so no behavioral change to deserialization is warranted — changing the target type or adding#[serde(deny_unknown_fields)]would only risk breaking valid Teams traffic when Microsoft adds Activity fields. The only real residual is unauthenticated resource use, which a body cap addresses at near-zero cost and zero impact on valid traffic.Alternatives Considered
route_layer(DefaultBodyLimit::max(..))in main.rs — works, but the explicit in-handlerbody.len()check is consistent with the existing feishu adapter, doesn't depend on middleware ordering, and keeps the limit visible next to the parse it protects.#[serde(deny_unknown_fields)]onActivity— rejected: Microsoft adds fields to Bot Framework activities over time; this would break inbound messages for no security gain.Validation
Rust changes:
cargo checkpassescargo testpasses (existing 20 teams tests pass; no behavior change to valid traffic)cargo clippycleanAll PRs:
413for bodies over 256 KB and is a no-op for normal-sized activities (real activities are a few KB, well under the cap). No change to the parse/auth/dispatch path for valid traffic.🤖 Generated with Claude Code