Skip to content

Security

This page summarises the package's threat model: what v1 defends against, what it does not, and what stays the host's responsibility.

Pen-testing aid

Use php artisan chatbot:inspect-prompt to view the exact message array the LLM would receive for any route, without making a live request. See Console commands.

Attack surface

The package mediates between a host Laravel app and a third-party LLM. The attack surface is:

  1. Context forgery / replay — forging or replaying a signed envelope to impersonate another user or escalate privilege.
  2. Prompt injection via context data — user-controlled data reaching the system prompt as instructions rather than data.
  3. Bot-output XSS — the LLM returns content the widget renders as HTML, enabling script injection.
  4. Worker / budget exhaustion — runaway streams, recursive tool loops, oversize prompts.
  5. Tool-call abuse — a model coerced into invoking tools repeatedly or with attacker-chosen arguments.

What v1 defends against

ThreatMitigation
Context forgery / replayHMAC-SHA256 ContextEnvelope keyed on app.key. Verify also binds user id, route, channel, and version. TTL 900s. Tampered, expired, or mismatched tokens raise InvalidEnvelopeException (HTTP 403).
Tag-shape injectionContextSanitizer strips tags listed in chatbot.sanitizer_tags from context values before assembly.
Bot-output XSSThe widget passes LLM output through a DOMPurify allowlist before insertion.
Per-IP floodingRateLimiter enforces chatbot.throttle.per_minute / per_day per IP+channel.
Worker exhaustionSSE streams abort on client disconnect; wall-clock cap chatbot.stream_duration (default 60 s). Tool calls bounded by chatbot.tools.default_timeout.
Prompt-size blowupchatbot.token_cap (default 32768) bounds assembled input tokens. A user message that alone exceeds the cap raises ChatbotTokenCapExceededException.
API budget exhaustionDailyUsageTracker enforces chatbot.daily_quota.input / .output per user per day (UTC-reset). Exhaustion raises ChatbotQuotaExceededException.
Tool-call abusechatbot.tools.max_calls_per_turn caps total invocations per user message.
Identity spoofing via tool argumentsChatbotTool::handle() and authorize() receive the threaded actor as a typed first parameter — injected by the framework, never by the LLM. ToolRegistry rejects identity-shaped parameter names at boot. See ADR-0003.
Malformed / oversize tool argumentsStrict JSON-schema validation pre-authorize(). Failures count against max_calls_per_turn and persist as rejected_schema. Per-string-field cap chatbot.tools.default_max_arg_length (default 10240 bytes).

What v1 does NOT defend against

GapNotes
Sophisticated prompt injectionMulti-turn, encoded, or indirect injection (e.g. instructions hidden in a user's order notes) is not detected or blocked.
Indirect prompt injection via client extractorsWhen a channel allowlists extractors, untrusted page content reaches the LLM every turn. The package wraps each block in <client-extractor name="…" trust="untrusted-page-content"> tags and prepends a system-prompt rule treating contents as data — a soft defence. Combined with mutating tools on the same channel, a determined indirect injection can still succeed. Keep mutating tools and extractors on disjoint channels where possible. See ADR-0004.
Post-render content driftDOMPurify runs at insertion time. Dynamic DOM mutations after render are outside the widget's control.
Base64 / obfuscated payloadsThe sanitizer operates on plaintext. It does not decode or classify encoded content.
Two-pass content classificationNo secondary classifier runs on LLM output before display.
Automatic retry / model fallbackThe package surfaces provider errors; it does not retry or switch models. (OpenAiCompatibleClient does retry once without tools on a 400 tools-rejection — a compatibility fallback, not a general policy.)
Tool result replay / dedupCross-turn replay is wired in: invocations within chatbot.tools.replay_freshness (default 300 s) are replayed into the prompt on later turns. Within a single turn, re-invoking the same tool is not deduplicated.
Outbound tool side effectsArgument-level sandboxing is in scope. Network egress, filesystem isolation, capability boxing remain host/infrastructure concerns.

Host responsibilities

The package does not absolve the host of broader security and compliance work:

  • Privacy disclosure — tell users their messages go to a third-party LLM.
  • Subprocessor list — include the LLM vendor in your DPA and privacy notice.
  • Retention — configure chatbot.retention_days (per channel) to match your policy.
  • Cost governance — track token spend, set vendor-side hard caps. The package's daily_quota is a per-user soft cap, not a billing safety net.
  • Vendor outages — choose your provider; the package speaks the OpenAI protocol but doesn't probe vendor health.
  • Auth & sessionsContextEnvelope carries userId from Laravel's configured auth guard. If that guard is mis-scoped, the envelope inherits the problem.

See also

Released under the MIT License.