Skip to content

fix(gateway): bound teams webhook body size, document deserialization safety#948

Open
antigenius0910 wants to merge 1 commit into
openabdev:mainfrom
antigenius0910:bugfix/teams-webhook-body-limit
Open

fix(gateway): bound teams webhook body size, document deserialization safety#948
antigenius0910 wants to merge 1 commit into
openabdev:mainfrom
antigenius0910:bugfix/teams-webhook-body-limit

Conversation

@antigenius0910
Copy link
Copy Markdown
Contributor

@antigenius0910 antigenius0910 commented May 29, 2026

What problem does this solve?

An OX security scan flagged serde_json::from_str(&body) into Activity in 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 Framework Activity JSON is parsed before JWT auth (the token can't be validated without serviceUrl/channelId from 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

inbound POST /webhook/teams
        |
        v
[ body.len() > 256 KB ? ] --yes--> 413 PAYLOAD_TOO_LARGE   <-- NEW pre-auth guard
        | no
        v
serde_json::from_str -> Activity (strict derive-only DTO)
        |  (parse happens before auth: Bot Framework needs serviceUrl/channelId)
        v
JWT validation -> activity-type gate -> tenant allowlist -> dispatch

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 returning 413 (feishu.rs:2112, feishu.rs:2214).

Proposed Solution

  1. Pre-auth body-size guard — add const WEBHOOK_BODY_LIMIT: usize = 256 * 1024; and reject oversized bodies with 413 PAYLOAD_TOO_LARGE before 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).
  2. Inline suppression rationale — document why serde_json::from_str into Activity is safe: Activity is a derive-only strict DTO (String / Option<_> / nested structs) with no custom Deserialize, no side-effectful Drop, 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-handler body.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)] on Activity — rejected: Microsoft adds fields to Bot Framework activities over time; this would break inbound messages for no security gain.
  • Just suppress the OX finding, no code change — the suppression is correct, but the pre-auth body cap is cheap defense-in-depth worth landing alongside the documentation.

Validation

Rust changes:

  • cargo check passes
  • cargo test passes (existing 20 teams tests pass; no behavior change to valid traffic)
  • cargo clippy clean

All PRs:

  • Manual testing — verified the gateway crate builds; confirmed the new guard returns 413 for 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

… 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>
@antigenius0910 antigenius0910 requested a review from thepagent as a code owner May 29, 2026 12:24
@github-actions github-actions Bot added closing-soon PR missing Discord Discussion URL — will auto-close in 3 days pending-maintainer and removed closing-soon PR missing Discord Discussion URL — will auto-close in 3 days labels May 29, 2026
@shaun-agent
Copy link
Copy Markdown
Contributor

shaun-agent commented May 29, 2026

OpenAB PR Screening

This is auto-generated by the OpenAB project-screening flow for context collection and reviewer handoff.
Click 👍 if you find this useful. Human review will be done within 24 hours. We appreciate your support and contribution 🙏

Screening report screened #948 and moved the project item from `Incoming` to `PR-Screening`.

GitHub comment: #948 (comment)
Project action: https://github.com/orgs/openabdev/projects/1, item PVTI_lADOEFbZWM4BUUALzguKYHo now in PR-Screening

Intent

This 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 serde_json into a strict derive-only DTO, but the report exposed a real operator-visible issue: /webhook/teams parses the request body before JWT validation because Bot Framework auth needs fields from the activity body.

Feat

Fix/security hardening. Adds a 256 KiB pre-auth body-size guard and documents the Teams Activity deserialization safety rationale.

Who It Serves

Deployers and agent runtime operators running the Teams gateway adapter. Maintainers also get clearer security-review context.

Rewritten Prompt

Harden the Microsoft Teams gateway webhook against unauthenticated oversized request bodies. Reject bodies above the Teams activity limit before serde_json::from_str, return 413, preserve existing auth/dispatch behavior, and verify oversized plus normal activity paths.

Merge Pitch

Low-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 Comparison

OpenClaw 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 Options

Conservative: current guard plus comment.
Balanced: guard plus focused Teams tests.
Ambitious: shared webhook body-limit policy across adapters.

Comparison Table

Posted in the comment.

Recommendation

Use the balanced path: advance screening, but ask review to verify focused coverage for oversized Teams bodies and a representative valid activity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants