Skip to content

refactor(whatsapp): centralize account policy#70922

Open
mcaxtr wants to merge 1 commit into
mainfrom
refactor/whatsapp-centralize-account-policy
Open

refactor(whatsapp): centralize account policy#70922
mcaxtr wants to merge 1 commit into
mainfrom
refactor/whatsapp-centralize-account-policy

Conversation

@mcaxtr

@mcaxtr mcaxtr commented Apr 24, 2026

Copy link
Copy Markdown
Member

Summary

  • add a canonical WhatsApp-local account policy seam in extensions/whatsapp/src/account-policy.ts
  • migrate inbound policy, send, heartbeat recipient resolution, and action target auth to consume that seam instead of re-deriving multi-account policy locally
  • add focused account-policy coverage and keep the existing WhatsApp consumer tests pinned to preserved behavior

Preserved behavior

  • defaultAccount selection and omitted-account fallback behavior
  • accounts.default inheritance rules, including non-inheritance of default-only authDir and selfChatMode
  • direct-chat outbound authorization and group-JID handling
  • same-phone / self-chat handling
  • pairing-store read behavior in allowlist vs pairing modes
  • heartbeat recipient selection semantics
  • action authorization account resolution

Tests

  • pnpm test extensions/whatsapp/src/account-policy.test.ts
  • pnpm test extensions/whatsapp/src/inbound/access-control.test.ts
  • pnpm test extensions/whatsapp/src/heartbeat-recipients.test.ts
  • pnpm test extensions/whatsapp/src/action-runtime.test.ts
  • pnpm test extensions/whatsapp/src/send.test.ts
  • pnpm test extensions/whatsapp/src/accounts.test.ts
  • pnpm check:changed

Notes

  • keeps the PR focused on the WhatsApp plugin and directly related tests
  • future changes to account selection, allowlists, DM/group defaults, and direct-target auth should now be mostly isolated to the new seam

@aisle-research-bot

aisle-research-bot Bot commented Apr 24, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟠 High WhatsApp outbound allowlist bypass when allowFrom is empty (default allow-all)
2 🟡 Medium Outbound allowFrom policy bypass: any WhatsApp group JID is always authorized
1. 🟠 WhatsApp outbound allowlist bypass when allowFrom is empty (default allow-all)
Property Value
Severity High
CWE CWE-284
Location extensions/whatsapp/src/resolve-outbound-target.ts:18-43

Description

The outbound target authorization logic treats an empty allowFrom list as allow all direct targets, effectively disabling outbound allowlisting when allowFrom is omitted/empty.

  • Input: allowFrom comes from configuration (e.g., account/channel allowFrom), and may be undefined/empty for default configs.
  • Sink: resolveWhatsAppOutboundTarget() authorizes sending to arbitrary E.164 WhatsApp targets.
  • Issue: when allowFrom is empty, the code returns { ok: true } for any non-group target, enabling outbound messages to arbitrary numbers.

Vulnerable code:

if (hasWildcard || allowList.length === 0) {
  return { ok: true, to: normalizedTo };
}

This is security-sensitive because other call paths (e.g., action authorization via resolveWhatsAppDirectTargetAuthorization() in account-policy.ts) pass configuredAllowFrom which is empty when not configured, resulting in an implicit allow-all posture.

Recommendation

Adopt a default-deny posture for outbound direct messages unless an explicit allow rule exists.

Suggested change:

  • Only allow all when an explicit wildcard "*" is configured.
  • If allowFrom is empty, deny (or require an explicit config flag like allowAllOutbound: true).
// deny-by-default unless wildcard or explicit match
if (hasWildcard) {
  return { ok: true, to: normalizedTo };
}
if (allowList.length === 0) {
  return { ok: false, error: whatsappAllowFromPolicyError(normalizedTo) };
}
if (allowList.includes(normalizedTo)) {
  return { ok: true, to: normalizedTo };
}
return { ok: false, error: whatsappAllowFromPolicyError(normalizedTo) };

Also update documentation/tests to reflect the intended policy (and ensure configs without allowFrom do not silently become unrestricted).

2. 🟡 Outbound allowFrom policy bypass: any WhatsApp group JID is always authorized
Property Value
Severity Medium
CWE CWE-284
Location extensions/whatsapp/src/resolve-outbound-target.ts:35-37

Description

resolveWhatsAppOutboundTarget treats any target that matches @​g.us group JID format as automatically authorized, bypassing the configured allowFrom allowlist.

  • Input: untrusted to/chatJid is provided by callers (e.g., tool/action parameters)
  • Policy: allowFrom is intended to restrict which targets can be messaged
  • Bypass: group JIDs return { ok: true } before evaluating wildcard/empty/allowlist membership

This allows an actor who can invoke WhatsApp send actions to message arbitrary groups (if they can supply/guess a group JID), even when outbound messaging is restricted to specific contacts via allowFrom.

Vulnerable code:

if (isWhatsAppGroupJid(normalizedTo)) {
  return { ok: true, to: normalizedTo };
}

Recommendation

Apply allowlist enforcement consistently to group JIDs, or introduce a separate, explicit configuration for allowed group targets.

Example: require group JIDs to be explicitly allowlisted unless a dedicated allowAllGroups (or groupAllowTo) flag/list is configured:

const isGroup = isWhatsAppGroupJid(normalizedTo);

if (hasWildcard || allowList.length === 0) {
  return { ok: true, to: normalizedTo };
}// Enforce allowlist for both users and groups
if (allowList.includes(normalizedTo)) {
  return { ok: true, to: normalizedTo };
}

return { ok: false, error: whatsappAllowFromPolicyError(normalizedTo) };

If the intent is to keep groups open by default, make that an explicit policy (e.g., groupAllowFrom / groupAllowTo) and document the security implications.


Analyzed PR: #70922 at commit 844b652

Last updated on: 2026-04-24T04:33:19Z

@greptile-apps

greptile-apps Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR centralizes WhatsApp account policy resolution into a new account-policy.ts seam, eliminating duplicated logic across inbound-policy.ts, heartbeat-recipients.ts, action-runtime-target-auth.ts, and send.ts. The refactor is well-structured: the new resolveWhatsAppAccountPolicy and resolveWhatsAppDirectTargetAuthorization functions compose cleanly from existing primitives, and the accompanying tests cover all key invariants including inheritance rules, default fallbacks, self-chat classification, and group-JID pass-through.

Confidence Score: 5/5

Safe to merge — all callers are covered by tests, behaviour is preserved, and the normalization change (trimming whitespace entries) is a net fix.

No P0/P1 findings. The one subtle behaviour difference — normalizeConfiguredAllowEntries now trims and filters blank entries before they reach downstream comparators, whereas the old inbound-policy.ts deferred this to normalizeE164 at comparison time — is a correctness improvement verified by the new account-policy tests. The groupAllowFrom fallback moved from explicit pre-processing into resolveEffectiveAllowFromLists, which the 'falls back groupAllowFrom to allowFrom when unset' test confirms works identically.

No files require special attention.

Reviews (1): Last reviewed commit: "refactor(whatsapp): centralize account p..." | Re-trigger Greptile

@openclaw-barnacle openclaw-barnacle Bot added channel: whatsapp-web Channel integration: whatsapp-web size: L maintainer Maintainer-authored PR labels Apr 24, 2026
@mcaxtr

mcaxtr commented Apr 24, 2026

Copy link
Copy Markdown
Member Author

Aisle follow-up:

I reviewed both reported items against this PR diff and origin/main.

  1. Group JIDs always authorized: this is pre-existing behavior, not introduced by this PR. extensions/whatsapp/src/resolve-outbound-target.ts already allowed group JIDs before the refactor, and extensions/whatsapp/src/resolve-outbound-target.test.ts already pinned valid group JIDs as allowed regardless of mode. This PR preserves that behavior through the new account-policy module and adds focused coverage for it in extensions/whatsapp/src/account-policy.test.ts.

  2. Account ID canonicalization mismatch: the cited account-config/auth-dir code is also unchanged from origin/main, so this PR did not create that mismatch. The concrete action-bypass path described by Aisle also does not line up with the current runtime path: action auth returns the resolved account id, and sendReactionWhatsApp then looks up the active WhatsApp controller by that same account id. A malformed id such as work!!! would not find the controller registered as work.

I agree both topics can be considered for separate hardening/policy follow-up, especially if we want stricter group outbound authorization or canonical account-id validation. I do not think either is a patch-introduced regression in this centralization PR.

@mcaxtr
mcaxtr force-pushed the refactor/whatsapp-centralize-account-policy branch from eaf3288 to 18310f4 Compare April 24, 2026 04:23
@mcaxtr
mcaxtr force-pushed the refactor/whatsapp-centralize-account-policy branch from 18310f4 to 844b652 Compare April 24, 2026 04:30
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jun 14, 2026
@clawsweeper

clawsweeper Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Codex review: found issues before merge. Reviewed July 3, 2026, 3:00 AM ET / 07:00 UTC.

Summary
The branch adds a WhatsApp-local account-policy seam and rewires inbound policy, outbound send, heartbeat recipient resolution, and action target authorization to consume it, with focused tests for preserved behavior.

PR surface: Source +41, Tests +237. Total +278 across 7 files.

Reproducibility: not applicable. this is a behavior-preserving refactor PR rather than a bug report. The reviewable checks are current-main mergeability, source parity, and authorization-policy sequencing.

Review metrics: 1 noteworthy metric.

  • Policy Consumers Moved: 4 runtime consumers. Inbound policy, outbound send, heartbeat recipient resolution, and action target authorization would share one account-policy decision point, so parity mistakes affect multiple WhatsApp paths.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🌊 off-meta tidepool
Patch quality: 🦐 gold shrimp
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Refresh the branch against current main and resolve stale WhatsApp conflicts.
  • Replace broad Plugin SDK config-runtime imports with narrow subpaths.
  • Coordinate landing order with adjacent WhatsApp authorization PRs.

Risk before merge

  • [P1] The branch is CONFLICTING/DIRTY against current main, including stale WhatsApp file layout, so maintainers cannot trust the actual merge result until it is refreshed.
  • [P1] The added account-policy module imports openclaw/plugin-sdk/config-runtime; current main's boundary guard rejects bundled plugin imports from that broad subpath.
  • [P2] Centralizing default account selection, allowFrom/groupAllowFrom fallback, self-chat handling, outbound send account routing, heartbeat policy, and action target authorization can affect existing WhatsApp configs if parity drifts.
  • [P1] The seam intentionally preserves empty direct allowFrom allow-all plus group/newsletter pass-through behavior, so landing should be sequenced with the open WhatsApp authorization PRs.

Maintainer options:

  1. Refresh And Repair Imports (recommended)
    Rebase or rebuild the branch on current main, resolve the stale WhatsApp conflicts, and move the new account-policy imports to narrow SDK subpaths before another merge attempt.
  2. Sequence With Authorization PRs
    Maintainers can land this seam before or after the target-facts, per-group allowFrom, and allowSendTo work once the preserved authorization semantics are explicitly accepted.
  3. Pause Until Policy Settles
    If maintainers want stricter outbound or group authorization instead of preserved behavior, pause this refactor until the policy PRs establish the final seam shape.

Next step before merge

  • [P2] Needs author or maintainer refresh plus sequencing judgment with related WhatsApp authorization PRs; this is not a safe standalone automation repair while the branch is conflicting and maintainer-labeled.

Security
Cleared: No patch-introduced supply-chain, secret-handling, workflow, or concrete new authorization bypass was found; preserved WhatsApp authorization semantics remain a maintainer merge-risk decision.

Review findings

  • [P2] Use narrow Plugin SDK config subpaths — extensions/whatsapp/src/account-policy.ts:1-6
Review details

Best possible solution:

Refresh the account-policy seam on current main, replace broad config-runtime imports with narrow Plugin SDK subpaths, preserve current WhatsApp send/inbound behavior, and land only after maintainers accept the sequencing with adjacent WhatsApp authorization work.

Do we have a high-confidence way to reproduce the issue?

Not applicable: this is a behavior-preserving refactor PR rather than a bug report. The reviewable checks are current-main mergeability, source parity, and authorization-policy sequencing.

Is this the best way to solve the issue?

No for the submitted branch state. A WhatsApp-local account-policy seam is a plausible cleanup, but this branch conflicts with current main and imports from a config SDK barrel that current main's boundary guard rejects.

Full review comments:

  • [P2] Use narrow Plugin SDK config subpaths — extensions/whatsapp/src/account-policy.ts:1-6
    Current main's config boundary guard reports imports from openclaw/plugin-sdk/config-runtime in repo code and tells callers to use narrow config subpaths instead. This new module imports config types and resolveDefaultGroupPolicy from that broad barrel, so a refreshed branch will fail boundary checks until those imports move to focused subpaths such as config-contracts and runtime-group-policy.
    Confidence: 0.93

Overall correctness: patch is incorrect
Overall confidence: 0.86

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 8ed6c78b7891.

Label changes

Label justifications:

  • P3: This is maintainer-owned WhatsApp refactor work with no confirmed active user regression, but it needs refresh and review before merge.
  • merge-risk: 🚨 compatibility: The PR centralizes WhatsApp default account, allowFrom, groupAllowFrom, fallback, and store-read behavior that existing configs may depend on.
  • merge-risk: 🚨 message-delivery: The changed seam feeds outbound send account selection and heartbeat recipient behavior, so a parity mistake could misroute or suppress WhatsApp messages.
  • merge-risk: 🚨 security-boundary: The changed seam feeds inbound sender authorization and outbound action target authorization decisions.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🌊 off-meta tidepool and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Not applicable: The external contributor proof gate does not apply because this is a member-authored, maintainer-labeled internal refactor PR.
Evidence reviewed

PR surface:

Source +41, Tests +237. Total +278 across 7 files.

View PR surface stats
Area Files Added Removed Net
Source 5 160 119 +41
Tests 2 237 0 +237
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 7 397 119 +278

What I checked:

  • Root policy read: Root AGENTS.md was read fully; its PR review policy requires whole-surface review and treats plugin auth, fallback, config, and compatibility surfaces as merge-risk-sensitive. (AGENTS.md:26, 8ed6c78b7891)
  • Scoped plugin policy read: extensions/AGENTS.md was read fully; bundled plugin production code should import through Plugin SDK subpaths and stay inside the plugin boundary. (extensions/AGENTS.md:19, 8ed6c78b7891)
  • Live PR state: Live GitHub reports this PR open, non-draft, maintainer-labeled, head 844b652, and CONFLICTING/DIRTY against base ed6094b. (844b65293991)
  • Patch adds broad config-runtime import: The added account-policy module imports config types and resolveDefaultGroupPolicy from openclaw/plugin-sdk/config-runtime. (extensions/whatsapp/src/account-policy.ts:1, 844b65293991)
  • Current main rejects that import shape: The current config boundary guard records broad config-runtime imports as violations and tells callers to use narrow Plugin SDK config subpaths. (scripts/lib/config-boundary-guard.mjs:200, 8ed6c78b7891)
  • Current main uses narrow WhatsApp imports: Current main's WhatsApp inbound policy imports config contracts from config-contracts and resolveDefaultGroupPolicy from runtime-group-policy, not config-runtime. (extensions/whatsapp/src/inbound-policy.ts:3, 8ed6c78b7891)

Likely related people:

  • mcaxtr: Authored this PR and multiple merged WhatsApp account, inbound, outbound, and target-routing changes that this seam centralizes. (role: feature-history owner and recent area contributor; confidence: high; commits: 844b65293991, 458a52610a4d, eb67ac5cbe5f; files: extensions/whatsapp/src/inbound-policy.ts, extensions/whatsapp/src/accounts.ts, extensions/whatsapp/src/send.ts)
  • Bluetegu: Authored the merged WhatsApp group/direct system prompt work that participates in the same account/group config surface this refactor must preserve. (role: adjacent feature owner; confidence: medium; commits: 08bc16853ef4; files: extensions/whatsapp/src/account-types.ts, extensions/whatsapp/src/inbound-policy.ts)
  • Dallin Romney: Local blame in this shallow checkout points the current WhatsApp policy and boundary-guard lines to the latest current-main snapshot commit; this is useful as recent provenance but not strong feature ownership. (role: recent current-main source contributor; confidence: low; commits: 8604dbdc93fb; files: extensions/whatsapp/src/inbound-policy.ts, extensions/whatsapp/src/accounts.ts, scripts/lib/config-boundary-guard.mjs)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 22, 2026
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Jun 28, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: whatsapp-web Channel integration: whatsapp-web maintainer Maintainer-authored PR merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: L status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant