Skip to content

feat(http-action): safe REST action bot plugin (0.1.0)#34

Merged
rmyndharis merged 3 commits into
mainfrom
feat/http-action
Jul 11, 2026
Merged

feat(http-action): safe REST action bot plugin (0.1.0)#34
rmyndharis merged 3 commits into
mainfrom
feat/http-action

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

Summary

Adds http-action, a plugin that triggers a safe REST API request from a WhatsApp command and maps the JSON response back to chat — one request per message, one reply. It connects WhatsApp to an existing HTTPS API without webhook middleware: e.g. cek-order INV-001GET {baseUrl}/orders/INV-001 → a reply rendered from the response.

Behavior

A configured trigger (exact/prefix, optional case-sensitivity) on an inbound message selects an action, builds a request to a fixed HTTPS origin, fetches it through ctx.net.fetch, parses the JSON body, and renders a reply template. Supports GET and JSON POST (with a bodyTemplate), Bearer / API-key / no auth, query params, and per-action replyTemplate / notFoundTemplate / errorTemplate.

Safety posture

  • Fixed origin. baseUrl is required, HTTPS, credential-free, and declared in allowConfigHosts (a code-side default would be invisible to the net gate, so it is forbidden). The request path is server-relative only — protocol-relative //, absolute URLs, fragments, and control chars are rejected — and each substituted segment is URL-encoded, so a message cannot inject a path segment or change the host.
  • No arbitrary code. No eval, no free regex, no loops. Prototype keys (__proto__/constructor/prototype) are rejected in template paths; POST bodies are JSON-escaped and re-parsed before send; dangerous headers (hop-by-hop, x-forwarded-*) and CRLF are blocked. Response bodies are capped at 256 KiB.
  • Reliability. Storage-backed idempotency (fail-closed, 3-day TTL, throttled prune) so a redelivered message id never double-fires; in-memory per-chat cooldown (fail-open). The handler returns { continue: true } immediately and floats the fetch/render/send, so a slow upstream never stalls the inbound hook.

Structure

config.ts (validation) · url-template.ts (prototype-safe rendering) · client.ts (fixed-origin fetch + response cap) · matcher.ts (triggers + arg parsing) · reliability.ts (dedup + cooldown + prune) · index.ts (lifecycle + off-dispatch handler). Secrets (authToken) are marked secret: true in configSchema so the dashboard masks and round-trips them.

Verification

npm test (373 tests, all green), npm run typecheck, npm run catalog:check, node package.mjs http-action (zip + sha256), and the loader-contract instantiation all pass. Three sample use cases — order status, stock lookup, ticket creation — run end-to-end through the real message path.

Notes

  • Status is development. minOpenWAVersion / testedOpenWAVersion are intentionally unset pending install and runtime testing on a real OpenWA instance; both will be filled from that test before moving to beta.
  • Follow-up: per-action cooldown and a sender allowlist are noted as easy extensions but not in this PR.

Trigger safe REST API requests from WhatsApp commands and map JSON
responses back to chat. One request per message, one reply.

Safe by default: fixed https origin (allowConfigHosts, required),
server-relative path validation, dangerous-header blocklist, CRLF
rejection, prototype-safe templating with URL-encoded path segments
and JSON-safe POST bodies, 256 KiB response cap, and an off-dispatch
handler so a slow upstream never stalls the inbound hook.

86 tests (node:test); passes typecheck, catalog:check, build, and the
loader contract. Order-status, stock-lookup, and ticket-creation use
cases run end-to-end through the real message path. Dedup/cooldown
hardening is deferred to a follow-up.
Storage-backed idempotency (claim, fail-closed, 3-day TTL) so a redelivered
message id never double-fires; a throttled prune keeps ctx.storage from
growing unbounded. In-memory per-chat cooldown (allowCooldown, fail-open,
LRU-capped) rate-limits a single chat. Both gates run in handleMessage
before the upstream call. Updated the 0.1.0 changelog to cover the full
MVP. 106 tests; typecheck, catalog:check, build, and the three end-to-end
use cases still pass.
Review-driven fixes:
- Enroll http-action in tsconfig include so the CI typecheck actually
  validates it; it had been excluded, masking a type error.
  FetchResponse.statusText is now optional to match PluginNetResponse.
- Reject CR/LF/NUL in rendered header values (a templated attacker field
  could otherwise inject headers), reject '..' path segments, reject a
  query string in baseUrl, and screen apiKeyHeader against the dangerous-
  header blocklist.
- Rework dedup into a read-only hasSeen check plus a mark written only
  after a successful send, so a transient send failure retries on
  redelivery instead of being dropped; the marker is presence-based and
  cooldown runs before any mark.
- Route request/prune failures through logger.error so the Error context
  is kept, surface the parsed body to notFound/error templates, avoid
  splitting a surrogate pair when truncating, and strip C0 control chars
  from rendered replies.

381 tests; typecheck (now covering http-action), catalog:check, build,
and the three end-to-end use cases all pass.
@rmyndharis rmyndharis merged commit 3e89170 into main Jul 11, 2026
1 check passed
@rmyndharis rmyndharis deleted the feat/http-action branch July 11, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant