Back to skill

Security audit

Birdbuddy Homeassistant

Security checks for vulnerabilities and agentic risk

Overview

This Bird Buddy Home Assistant skill is mostly purpose-aligned, but the included camera component has real security issues that should be reviewed before installation.

Review or fix the included camera.py before installing, especially URL allowlisting, response-size limits, and removal of media URLs/session IDs from entity attributes and logs. Store tokens only in Home Assistant secrets or another protected secret store, rotate any token pasted into shell/config history, and verify the Telegram chat/topic because notifications may reveal household activity and feeder imagery links.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
references/camera.py:288
Finding

Unvalidated Image URL Fetch Enables Server-Side Request Forgery

Content
View full analysis
bytes | None: """Download image bytes from a URL.""" import aiohttp try: session = async_get_clientsession(self.hass) async with session.get(url, timeout=aiohttp.ClientTimeout(total=15)) as resp: if resp.status == 200: return await resp.read() LOGGER.warning("Image download failed: HTTP %s for %s", resp.status, url[:80]) except Exception as err: # pylint: disable=broad-except LOGGER.warning("Image download error: %s", err) return None ``` The URL can originate from event data without validation: ```python async def _handle_new_postcard(self, event) -> None: """Cache the thumbnail from a new postcard sighting event.""" try: medias = event.data.get("sighting", {}).get("medias", []) if medias: thumbnail_url = medias[0].get("thumbnailUrl") if thumbnail_url and thumbnail_url != self._cached_url: LOGGER.debug( "Caching postcard thumbnail for %s: %s", self.feeder.name, thumbnail_url[:60], ) image_bytes = await self._download_image(thumbnail_url) if image_bytes: self._cached_image = image_bytes self._cached_url = thumbnail_url self.async_write_ha_state() except Exception as err: # pylint: disable=broad-except LOGGER.debug("Error caching postcard thumbnail: %s", err) ``` ### Technical Analysis `_download_image` performs an HTTP request to any supplied URL without validating: - The URL scheme - The destination hostname - The resolved IP address - Redirect ...[truncated 2386 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
references/camera.py:142
Finding

Private Snapshot URLs and Watching Session Identifiers Exposed Through Entity State and Logs

Content
View full analysis
dict[str, Any]: """Expose feeder state and last snapshot URL for automations.""" return { "feeder_state": self.feeder.get("state"), "last_snapshot_url": self._cached_url, "watching_session_id": self._watching_session_id, } ``` Snapshot URLs are also partially written to debug logs: ```python self._cached_url = image_url LOGGER.debug("Got snapshot URL: %s", image_url[:80]) ``` Postcard thumbnail URLs are similarly logged: ```python if thumbnail_url and thumbnail_url != self._cached_url: LOGGER.debug( "Caching postcard thumbnail for %s: %s", self.feeder.name, thumbnail_url[:60], ) ``` ### Technical Analysis Snapshot and thumbnail URLs can contain signed query parameters or otherwise function as temporary bearer capabilities. Anyone obtaining a still-valid URL may be able to retrieve private feeder imagery without separately authenticating to Home Assistant or Bird Buddy. The complete cached URL is exposed through `extra_state_attributes`. Home Assistant entity attributes may be visible to users, dashboards, automations, integrations, state-history systems, exporters, backups, and diagnostic tools according to their access privileges. The active watching-session identifier is also exposed as an entity attribute even though the dashboard and camera rendering functionality do not require it. While the code does not demonstrate that the identifier alone authorizes session control, publishing it unnecessarily broadens exposure of internal session metadata. Logging URL prefixes is also unsafe. Signed credentials or identifying path data can appear ...[truncated 1520 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (13)

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

The skill tells users to create and use a long-lived Home Assistant access token, which is a powerful credential that can often perform broad administrative API actions. Without warning or safeguards, this encourages use of a high-value secret that may be leaked via files, terminal history, screenshots, or copied configs.

Content

Scanner excerpt · SKILL.md (reported line 37)May include surrounding context.

md
| `{{TELEGRAM_CHAT_ID}}` | Telegram chat/group ID (integer, negative for groups) | `-1001234567890` |
| `{{TELEGRAM_THREAD_ID}}` | Telegram message thread / topic ID | `42` |
| `{{HA_URL}}` | Home Assistant base URL | `http://homeassistant.local:8123` |
| `{{HA_TOKEN}}` | Long-lived HA access token | created in HA Profile → Security |

---

Credential Access

High
Category
Privilege Escalation
Confidence
88% confidence
Finding

The skill instructs users to supply token values in automation data blocks and mentions input_text helpers as an option, which can lead to credentials being stored in plaintext configuration or exposed in the Home Assistant UI/state. Mishandling these secrets can allow unauthorized use of the Telegram bot or administrative access patterns through Home Assistant automations.

Content

Scanner excerpt · SKILL.md (reported line 174)May include surrounding context.

md
Each automation calls `rest_command.birdbuddy_telegram_notify`.

You must supply `token`, `chat_id`, and `thread_id` in the `data:` block of
each action — or inject them via `input_text` helpers / secrets.yaml for a
cleaner setup.

Reload automations: **Developer Tools → YAML → Automations** or restart HA.

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · SKILL.md (reported line 188)May include surrounding context.

⚠️ The url_path must contain a hyphen — birdbuddy (no hyphen) is rejected by HA. Use bird-buddy.

Via WebSocket (curl + websocat or HA REST)

bash
# Long-lived token required

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/automations.yaml (reported line 12)May include surrounding context.

yaml
#   chat_id:   {{TELEGRAM_CHAT_ID}}       ← integer, no quotes
#   thread_id: {{TELEGRAM_THREAD_ID}}
#
# Alternatively, store credentials in secrets.yaml and reference via !secret.
#
# Notification toggles (input_boolean entities) must be created first —
# see SKILL.md Step 4.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill contains network-capable instructions, including outbound calls to Home Assistant and Telegram APIs, but does not declare any tool scope or permission boundaries. That omission makes the skill less transparent to users and reviewers, and increases the risk that sensitive operations are performed without clear consent expectations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill asks users to handle a Telegram bot token and a long-lived Home Assistant access token, but provides no security guidance on storage, redaction, rotation, or exposure risks. Long-lived tokens are highly sensitive and can grant broad control over messaging or the Home Assistant instance if copied into files, shells, logs, or screenshots.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill configures Bird Buddy events to be forwarded to Telegram through the Telegram HTTP API without warning users that bird sightings, feeder status, and related metadata will be sent to a third party. This can expose household activity patterns and device state outside Home Assistant, especially in shared groups or topics.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

The skill explicitly configures outbound transmission to api.telegram.org, moving event data from Home Assistant to a third-party service. In this context the behavior is intentional, but it is still security-relevant because it expands the trust boundary and may expose behavioral or household metadata if users are not adequately warned.

Content

Scanner excerpt · SKILL.md (reported line 115)May include surrounding context.

The final file should look like:

yaml
birdbuddy_telegram_notify:
  url: "https://api.telegram.org/bot{{TELEGRAM_BOT_TOKEN}}/sendMessage"
  method: POST
  content_type: "application/json"
  payload: >-

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 188)May include surrounding context.

⚠️ The url_path must contain a hyphen — birdbuddy (no hyphen) is rejected by HA. Use bird-buddy.

Via WebSocket (curl + websocat or HA REST)

bash
# Long-lived token required

External Transmission

Medium
Category
Data Exfiltration
Confidence
84% confidence
Finding

The test command triggers a Home Assistant service call that includes sensitive Telegram routing values and causes outbound notification delivery. While functional, it encourages direct command-line handling of secrets and third-party transmission without warning about shell history, logs, or accidental disclosure to other local users.

Content

Scanner excerpt · SKILL.md (reported line 231)May include surrounding context.

md
-H "Authorization: Bearer $HA_TOKEN" | python3 -m json.tool

# Trigger a test notification (fill in your values)
curl -s -X POST "$HA_URL/api/services/rest_command/birdbuddy_telegram_notify" \
  -H "Authorization: Bearer $HA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/rest_commands.yaml (reported line 18)May include surrounding context.

yaml
#   {{TELEGRAM_THREAD_ID}}  — message thread/topic ID (omit line if no topics)

birdbuddy_telegram_notify:
  url: "https://api.telegram.org/bot{{TELEGRAM_BOT_TOKEN}}/sendMessage"
  method: POST
  content_type: "application/json"
  payload: >-

Vague Triggers

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

This JSON manifest-style file includes free-text instructions such as 'Create a new dashboard' and 'Open the dashboard' but does not define any explicit trigger scope, exclusions, or context boundaries for when these instructions should be applied. In a manifest/config context, the activation guidance is descriptive but somewhat broad and lacks negative examples or clearer constraints.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The description states a fixed time interpretation, "00:00 UTC = 8 PM ET," which bakes in an Eastern Time locale assumption in natural-language guidance. This can violate language/locale policy expectations because the skill does not offer a user choice or document that it is intentionally region-specific.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.