[scan] Security attack vector mapping #2

Open
opened 2026-03-23 12:54:26 +00:00 by Virgil · 1 comment
Member

Map every external input entry point: function, file:line, input source, flows into, validation, attack vector.


Implementation Plan (Spark)

Security Attack Vector Map

Scope

  • Reviewed /workspace/CLAUDE.md and all PHP sources under /workspace/src.
  • /workspace/CODEX.md is not present in this repository, so there were no additional Codex-specific repo conventions to apply.
  • No inbound HTTP webhook controller, request parser, or signature verification code exists in this repository. Webhook-related logic here is limited to credential intake and outbound webhook use.
  • Core\Plug\Concern\ManagesTokens comes from the external lthn/php dependency and is not present in this repository, so token storage/retrieval internals were not inspectable here.

Primary Findings

  1. Outbound webhook sinks trust any previously assigned webhook URL in posting and deletion flows; host validation only exists in the auth helpers, not in the send/delete paths.
  2. Telegram bot tokens are embedded directly in request URLs at every Bot API call site, which enlarges the leakage surface to URL logging in the surrounding HTTP stack.
  3. Outbound message payloads forward caller-controlled content with rich formatting enabled by default (mrkdwn for Slack, HTML parse mode for Telegram, unrestricted Discord content/embeds), so untrusted input can become mention/formatting injection.

Webhook Validation And Webhook URL Entry Points

Entry point File:line External data Validation / sink
Discord webhook setter src/Discord/Auth.php:37 withWebhook(string $webhookUrl) Stores raw webhook URL on the object.
Discord webhook auth request src/Discord/Auth.php:57 $params['webhook_url'] Reads caller-supplied webhook URL.
Discord prefix check src/Discord/Auth.php:67 webhook_url Only checks str_starts_with(..., 'https://discord.com/api/webhooks/').
Discord webhook verification sink src/Discord/Auth.php:72 webhook_url Performs GET against the supplied Discord webhook URL.
Discord credential return src/Discord/Auth.php:80 webhook_url, metadata Returns the webhook URL for downstream persistence/use.
Discord account lookup sink src/Discord/Auth.php:90 previously stored webhook URL getAccount() uses the stored webhook URL directly.
Discord post setter src/Discord/Post.php:26 withWebhook(string $webhookUrl) Stores raw webhook URL with no provider validation in this class.
Discord publish entry src/Discord/Post.php:40 text, media, params, stored webhook URL Only checks for non-empty URL before use.
Discord outbound webhook sink src/Discord/Post.php:99 stored webhook URL Posts directly to {$this->webhookUrl}?wait=true.
Discord delete setter src/Discord/Delete.php:25 withWebhook(string $webhookUrl) Stores raw webhook URL with no provider validation in this class.
Discord delete entry src/Discord/Delete.php:37 id, stored webhook URL Only checks for non-empty URL before use.
Discord delete sink src/Discord/Delete.php:43 stored webhook URL, message id Deletes via {$this->webhookUrl}/messages/{$id}.
Slack webhook setter src/Slack/Auth.php:37 withWebhook(string $webhookUrl) Stores raw webhook URL on the object.
Slack webhook auth request src/Slack/Auth.php:57 $params['webhook_url'] Reads caller-supplied webhook URL.
Slack prefix check src/Slack/Auth.php:67 webhook_url Only checks str_starts_with(..., 'https://hooks.slack.com/').
Slack webhook verification sink src/Slack/Auth.php:72 webhook_url Performs POST test request to the supplied webhook URL.
Slack validation shortcut src/Slack/Auth.php:77 response body Treats Slack invalid_payload as proof that the webhook is valid.
Slack credential return src/Slack/Auth.php:81 webhook_url Returns the webhook URL for downstream persistence/use.
Slack post setter src/Slack/Post.php:26 withWebhook(string $webhookUrl) Stores raw webhook URL with no provider validation in this class.
Slack publish entry src/Slack/Post.php:40 text, media, params, stored webhook URL Only checks for non-empty URL before use.
Slack outbound webhook sink src/Slack/Post.php:92 stored webhook URL Posts directly to the configured webhook URL.

Notes

  • Validation is confined to Auth::requestAccessToken() for Discord and Slack. If a caller bypasses that path and injects a webhook URL through withWebhook() on Post or Delete, the send/delete classes do not re-validate the destination before issuing HTTP requests.
  • There is no inbound Slack, Discord, or Telegram webhook signature verification code in this repository.

Bot Token Handling Entry Points And Sinks

Entry point File:line External data Token handling
Telegram bot token setter src/Telegram/Auth.php:39 withBotToken(string $botToken) Stores the raw bot token on the object.
Telegram access-token request src/Telegram/Auth.php:59 $params['bot_token'] Reads caller-supplied bot token.
Telegram token verification sink src/Telegram/Auth.php:69 bot token Calls GET https://api.telegram.org/bot{token}/getMe.
Telegram credential return src/Telegram/Auth.php:83 bot token Returns the raw token as 'access_token'.
Telegram account lookup sink src/Telegram/Auth.php:93 previously stored bot token getAccount() embeds the token in the request URL.
Telegram publish token read src/Telegram/Post.php:46 token from ManagesTokens::accessToken() Loads a stored token for outbound messaging.
Telegram sendMessage sink src/Telegram/Post.php:80 bot token Sends to /bot{token}/sendMessage.
Telegram sendMediaGroup sink src/Telegram/Post.php:121 bot token Sends to /bot{token}/sendMediaGroup.
Telegram single-media sink src/Telegram/Post.php:167 bot token Sends to /bot{token}/{method}.
Telegram delete token read src/Telegram/Delete.php:43 token from accessToken() Loads a stored token for deletion.
Telegram delete sink src/Telegram/Delete.php:52 bot token Sends to /bot{token}/deleteMessage.
Telegram read me() token read src/Telegram/Read.php:39 token from accessToken() Loads a stored token for account lookup.
Telegram read me() sink src/Telegram/Read.php:44 bot token Sends to /bot{token}/getMe.
Telegram chat lookup token read src/Telegram/Read.php:65 token from accessToken() Loads a stored token for chat lookup.
Telegram chat lookup sink src/Telegram/Read.php:70 bot token Sends to /bot{token}/getChat.
Telegram updates token read src/Telegram/Read.php:94 token from accessToken() Loads a stored token for update listing.
Telegram updates sink src/Telegram/Read.php:99 bot token Sends to /bot{token}/getUpdates.
Telegram chat listing token read src/Telegram/Chats.php:31 token from accessToken() Loads a stored token for chat discovery.
Telegram chat listing sink src/Telegram/Chats.php:37 bot token Sends to /bot{token}/getUpdates.
Telegram member-count token read src/Telegram/Chats.php:73 token from accessToken() Loads a stored token for member count lookup.
Telegram member-count sink src/Telegram/Chats.php:78 bot token Sends to /bot{token}/getChatMemberCount.
Telegram admin-list token read src/Telegram/Chats.php:92 token from accessToken() Loads a stored token for administrator lookup.
Telegram admin-list sink src/Telegram/Chats.php:97 bot token Sends to /bot{token}/getChatAdministrators.

Notes

  • Every Telegram API call in this repository places the bot token inside the URL path rather than in headers or request bodies.
  • Because ManagesTokens is external to this repository, this review can only map token consumption sites here, not how the token is persisted or redacted elsewhere.

Message Injection And Untrusted Content Sinks

Entry point File:line Untrusted input Injection surface
Discord publish text src/Discord/Post.php:48 $text Forwarded to Discord content without mention restrictions.
Discord media embed fields src/Discord/Post.php:56 `media[*]['url' 'path'
Discord custom embed src/Discord/Post.php:81 $params['embed'] Full embed object is appended as-is.
Discord sender impersonation fields src/Discord/Post.php:86 $params['username'], $params['avatar_url'] Caller can override webhook display identity.
Discord TTS toggle src/Discord/Post.php:94 $params['tts'] Caller can trigger text-to-speech delivery.
Slack mrkdwn text src/Slack/Post.php:49 $text Forwarded as mrkdwn, enabling formatting and mention expansion.
Slack media block fields src/Slack/Post.php:60 `media[*]['url' 'path'
Slack sender impersonation fields src/Slack/Post.php:80 $params['username'], $params['icon_emoji'], $params['icon_url'] Caller can override webhook display identity.
Telegram text message src/Telegram/Post.php:62 $text, $params['parse_mode'] Defaults to parse_mode = HTML, so raw text is interpreted as HTML formatting.
Telegram reply routing src/Telegram/Post.php:76 $params['reply_to_message_id'] Caller can attach output to an existing message thread.
Telegram media caption src/Telegram/Post.php:107 $text, $params['parse_mode'] First media item caption inherits raw text and parse mode.
Telegram media-group serialization src/Telegram/Post.php:121 media array Serializes caller-controlled media metadata into JSON for Telegram.
Telegram single-media caption src/Telegram/Post.php:158 caption, parse mode Reuses caller-controlled caption and formatting mode.

Notes

  • No provider-specific escaping, mention suppression, or allowlist of formatting modes exists in this repository before outbound payload construction.
  • This repository does not expose an inbound message parser for Slack or Discord. The only inbound message-like data flow is Telegram update polling via getUpdates.

Remote Update Ingestion Points

Entry point File:line Remote data source Observed behavior
Telegram recent-chat listing src/Telegram/Read.php:92 Telegram getUpdates response Iterates over remote message / channel_post chat objects with no schema validation beyond null checks.
Telegram entity listing src/Telegram/Chats.php:29 Telegram getUpdates response Iterates over remote message, channel_post, and my_chat_member chat objects with no schema validation beyond null checks.

Absent Controls In This Repository

  • No inbound webhook request authenticity verification for Slack, Discord, or Telegram.
  • No local redaction layer for bot tokens or webhook URLs before they are returned from auth flows.
  • No output-layer escaping or mention suppression for Slack mrkdwn, Discord mentions/embeds, or Telegram HTML formatting.
Map every external input entry point: function, file:line, input source, flows into, validation, attack vector. --- ## Implementation Plan (Spark) # Security Attack Vector Map ## Scope - Reviewed `/workspace/CLAUDE.md` and all PHP sources under `/workspace/src`. - `/workspace/CODEX.md` is not present in this repository, so there were no additional Codex-specific repo conventions to apply. - No inbound HTTP webhook controller, request parser, or signature verification code exists in this repository. Webhook-related logic here is limited to credential intake and outbound webhook use. - `Core\Plug\Concern\ManagesTokens` comes from the external `lthn/php` dependency and is not present in this repository, so token storage/retrieval internals were not inspectable here. ## Primary Findings 1. Outbound webhook sinks trust any previously assigned webhook URL in posting and deletion flows; host validation only exists in the auth helpers, not in the send/delete paths. 2. Telegram bot tokens are embedded directly in request URLs at every Bot API call site, which enlarges the leakage surface to URL logging in the surrounding HTTP stack. 3. Outbound message payloads forward caller-controlled content with rich formatting enabled by default (`mrkdwn` for Slack, `HTML` parse mode for Telegram, unrestricted Discord content/embeds), so untrusted input can become mention/formatting injection. ## Webhook Validation And Webhook URL Entry Points | Entry point | File:line | External data | Validation / sink | | --- | --- | --- | --- | | Discord webhook setter | `src/Discord/Auth.php:37` | `withWebhook(string $webhookUrl)` | Stores raw webhook URL on the object. | | Discord webhook auth request | `src/Discord/Auth.php:57` | `$params['webhook_url']` | Reads caller-supplied webhook URL. | | Discord prefix check | `src/Discord/Auth.php:67` | `webhook_url` | Only checks `str_starts_with(..., 'https://discord.com/api/webhooks/')`. | | Discord webhook verification sink | `src/Discord/Auth.php:72` | `webhook_url` | Performs `GET` against the supplied Discord webhook URL. | | Discord credential return | `src/Discord/Auth.php:80` | `webhook_url`, metadata | Returns the webhook URL for downstream persistence/use. | | Discord account lookup sink | `src/Discord/Auth.php:90` | previously stored webhook URL | `getAccount()` uses the stored webhook URL directly. | | Discord post setter | `src/Discord/Post.php:26` | `withWebhook(string $webhookUrl)` | Stores raw webhook URL with no provider validation in this class. | | Discord publish entry | `src/Discord/Post.php:40` | `text`, `media`, `params`, stored webhook URL | Only checks for non-empty URL before use. | | Discord outbound webhook sink | `src/Discord/Post.php:99` | stored webhook URL | Posts directly to `{$this->webhookUrl}?wait=true`. | | Discord delete setter | `src/Discord/Delete.php:25` | `withWebhook(string $webhookUrl)` | Stores raw webhook URL with no provider validation in this class. | | Discord delete entry | `src/Discord/Delete.php:37` | `id`, stored webhook URL | Only checks for non-empty URL before use. | | Discord delete sink | `src/Discord/Delete.php:43` | stored webhook URL, message id | Deletes via `{$this->webhookUrl}/messages/{$id}`. | | Slack webhook setter | `src/Slack/Auth.php:37` | `withWebhook(string $webhookUrl)` | Stores raw webhook URL on the object. | | Slack webhook auth request | `src/Slack/Auth.php:57` | `$params['webhook_url']` | Reads caller-supplied webhook URL. | | Slack prefix check | `src/Slack/Auth.php:67` | `webhook_url` | Only checks `str_starts_with(..., 'https://hooks.slack.com/')`. | | Slack webhook verification sink | `src/Slack/Auth.php:72` | `webhook_url` | Performs `POST` test request to the supplied webhook URL. | | Slack validation shortcut | `src/Slack/Auth.php:77` | response body | Treats Slack `invalid_payload` as proof that the webhook is valid. | | Slack credential return | `src/Slack/Auth.php:81` | `webhook_url` | Returns the webhook URL for downstream persistence/use. | | Slack post setter | `src/Slack/Post.php:26` | `withWebhook(string $webhookUrl)` | Stores raw webhook URL with no provider validation in this class. | | Slack publish entry | `src/Slack/Post.php:40` | `text`, `media`, `params`, stored webhook URL | Only checks for non-empty URL before use. | | Slack outbound webhook sink | `src/Slack/Post.php:92` | stored webhook URL | Posts directly to the configured webhook URL. | ### Notes - Validation is confined to `Auth::requestAccessToken()` for Discord and Slack. If a caller bypasses that path and injects a webhook URL through `withWebhook()` on `Post` or `Delete`, the send/delete classes do not re-validate the destination before issuing HTTP requests. - There is no inbound Slack, Discord, or Telegram webhook signature verification code in this repository. ## Bot Token Handling Entry Points And Sinks | Entry point | File:line | External data | Token handling | | --- | --- | --- | --- | | Telegram bot token setter | `src/Telegram/Auth.php:39` | `withBotToken(string $botToken)` | Stores the raw bot token on the object. | | Telegram access-token request | `src/Telegram/Auth.php:59` | `$params['bot_token']` | Reads caller-supplied bot token. | | Telegram token verification sink | `src/Telegram/Auth.php:69` | bot token | Calls `GET https://api.telegram.org/bot{token}/getMe`. | | Telegram credential return | `src/Telegram/Auth.php:83` | bot token | Returns the raw token as `'access_token'`. | | Telegram account lookup sink | `src/Telegram/Auth.php:93` | previously stored bot token | `getAccount()` embeds the token in the request URL. | | Telegram publish token read | `src/Telegram/Post.php:46` | token from `ManagesTokens::accessToken()` | Loads a stored token for outbound messaging. | | Telegram sendMessage sink | `src/Telegram/Post.php:80` | bot token | Sends to `/bot{token}/sendMessage`. | | Telegram sendMediaGroup sink | `src/Telegram/Post.php:121` | bot token | Sends to `/bot{token}/sendMediaGroup`. | | Telegram single-media sink | `src/Telegram/Post.php:167` | bot token | Sends to `/bot{token}/{method}`. | | Telegram delete token read | `src/Telegram/Delete.php:43` | token from `accessToken()` | Loads a stored token for deletion. | | Telegram delete sink | `src/Telegram/Delete.php:52` | bot token | Sends to `/bot{token}/deleteMessage`. | | Telegram read `me()` token read | `src/Telegram/Read.php:39` | token from `accessToken()` | Loads a stored token for account lookup. | | Telegram read `me()` sink | `src/Telegram/Read.php:44` | bot token | Sends to `/bot{token}/getMe`. | | Telegram chat lookup token read | `src/Telegram/Read.php:65` | token from `accessToken()` | Loads a stored token for chat lookup. | | Telegram chat lookup sink | `src/Telegram/Read.php:70` | bot token | Sends to `/bot{token}/getChat`. | | Telegram updates token read | `src/Telegram/Read.php:94` | token from `accessToken()` | Loads a stored token for update listing. | | Telegram updates sink | `src/Telegram/Read.php:99` | bot token | Sends to `/bot{token}/getUpdates`. | | Telegram chat listing token read | `src/Telegram/Chats.php:31` | token from `accessToken()` | Loads a stored token for chat discovery. | | Telegram chat listing sink | `src/Telegram/Chats.php:37` | bot token | Sends to `/bot{token}/getUpdates`. | | Telegram member-count token read | `src/Telegram/Chats.php:73` | token from `accessToken()` | Loads a stored token for member count lookup. | | Telegram member-count sink | `src/Telegram/Chats.php:78` | bot token | Sends to `/bot{token}/getChatMemberCount`. | | Telegram admin-list token read | `src/Telegram/Chats.php:92` | token from `accessToken()` | Loads a stored token for administrator lookup. | | Telegram admin-list sink | `src/Telegram/Chats.php:97` | bot token | Sends to `/bot{token}/getChatAdministrators`. | ### Notes - Every Telegram API call in this repository places the bot token inside the URL path rather than in headers or request bodies. - Because `ManagesTokens` is external to this repository, this review can only map token consumption sites here, not how the token is persisted or redacted elsewhere. ## Message Injection And Untrusted Content Sinks | Entry point | File:line | Untrusted input | Injection surface | | --- | --- | --- | --- | | Discord publish text | `src/Discord/Post.php:48` | `$text` | Forwarded to Discord `content` without mention restrictions. | | Discord media embed fields | `src/Discord/Post.php:56` | `media[*]['url'|'path'|'title'|'description']` | Caller controls embed image URL, title, and description. | | Discord custom embed | `src/Discord/Post.php:81` | `$params['embed']` | Full embed object is appended as-is. | | Discord sender impersonation fields | `src/Discord/Post.php:86` | `$params['username']`, `$params['avatar_url']` | Caller can override webhook display identity. | | Discord TTS toggle | `src/Discord/Post.php:94` | `$params['tts']` | Caller can trigger text-to-speech delivery. | | Slack mrkdwn text | `src/Slack/Post.php:49` | `$text` | Forwarded as `mrkdwn`, enabling formatting and mention expansion. | | Slack media block fields | `src/Slack/Post.php:60` | `media[*]['url'|'path'|'alt_text'|'name']` | Caller controls image URL and alt text in block payloads. | | Slack sender impersonation fields | `src/Slack/Post.php:80` | `$params['username']`, `$params['icon_emoji']`, `$params['icon_url']` | Caller can override webhook display identity. | | Telegram text message | `src/Telegram/Post.php:62` | `$text`, `$params['parse_mode']` | Defaults to `parse_mode = HTML`, so raw text is interpreted as HTML formatting. | | Telegram reply routing | `src/Telegram/Post.php:76` | `$params['reply_to_message_id']` | Caller can attach output to an existing message thread. | | Telegram media caption | `src/Telegram/Post.php:107` | `$text`, `$params['parse_mode']` | First media item caption inherits raw text and parse mode. | | Telegram media-group serialization | `src/Telegram/Post.php:121` | media array | Serializes caller-controlled media metadata into JSON for Telegram. | | Telegram single-media caption | `src/Telegram/Post.php:158` | caption, parse mode | Reuses caller-controlled caption and formatting mode. | ### Notes - No provider-specific escaping, mention suppression, or allowlist of formatting modes exists in this repository before outbound payload construction. - This repository does not expose an inbound message parser for Slack or Discord. The only inbound message-like data flow is Telegram update polling via `getUpdates`. ## Remote Update Ingestion Points | Entry point | File:line | Remote data source | Observed behavior | | --- | --- | --- | --- | | Telegram recent-chat listing | `src/Telegram/Read.php:92` | Telegram `getUpdates` response | Iterates over remote `message` / `channel_post` chat objects with no schema validation beyond null checks. | | Telegram entity listing | `src/Telegram/Chats.php:29` | Telegram `getUpdates` response | Iterates over remote `message`, `channel_post`, and `my_chat_member` chat objects with no schema validation beyond null checks. | ## Absent Controls In This Repository - No inbound webhook request authenticity verification for Slack, Discord, or Telegram. - No local redaction layer for bot tokens or webhook URLs before they are returned from auth flows. - No output-layer escaping or mention suppression for Slack `mrkdwn`, Discord mentions/embeds, or Telegram HTML formatting.
Author
Member

Security Scan: Attack Vector Map completed. Details in agent log.

## Security Scan: Attack Vector Map completed. Details in agent log.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

-

Dependencies

No dependencies set.

Reference: core/php-plug-chat#2
No description provided.