Back to skill

Security audit

DataPulse

Security checks for vulnerabilities and agentic risk

Overview

DataPulse appears to be a real content collection and monitoring skill, but it has under-disclosed credential, session, and configurable network behaviors that warrant review before installation.

Review this skill carefully before installing. It is not just a URL reader: it can persist collected content, watch rules, alert routes, entity data, browser login state, and local operational state. Avoid enabling LLM entity extraction with custom API endpoints unless you provide a throwaway key explicitly, do not rely on the local Markdown secret fallback, keep alert tokens and Playwright session files in a private directory, and only configure native helper commands from trusted absolute paths. I did not find evidence of a hardcoded exfiltration endpoint or automatic destructive behavior, but the credential and helper scoping issues are significant.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
datapulse/core/entities.py:363
Finding

Implicit Local API Key Exfiltration Through a Caller-Controlled LLM Endpoint

Content
View full analysis
str: key = env_name.upper().strip() if not key: return "" candidate_paths = [] configured = os.getenv("DATAPULSE_LOCAL_SECRET_FILE", "").strip() if configured: candidate_paths.append(Path(configured).expanduser()) else: candidate_paths.append(_DEFAULT_OBSIDIAN_SECRET_FILE) for path in candidate_paths: value = _read_secret_file(path).get(key) if value: return value return "" def get_secret(env_name: str, *, default: str = "", required: bool = False, required_for: str | None = None) -> str: env_defined = env_name in os.environ if env_defined: value = os.getenv(env_name, "").strip() else: value = (default or "").strip() if not value and not env_defined: value = _load_local_secret(env_name) if required and not value: raise ValueError(f"{required_for or env_name} is required") return value ``` The MCP tool exposes a caller-controlled LLM base URL: ```python @app.tool() async def extract_entities( url: str, mode: str = "fast", store_entities: bool = True, llm_api_key: str | None = None, llm_model: str = "gpt-4o-mini", llm_api_base: str = "https://api.openai.com/v1", ) -> str: return await _run_extract_entities( ...[truncated 3687 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
datapulse/core/alerts.py:177
Finding

Alert Tokens and Browser Sessions Are Persisted Without Explicit Restrictive Permissions

Content
View full analysis
None: self.path.parent.mkdir(parents=True, exist_ok=True) payload = {"routes": self.routes} self.path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") ``` The normalized route data includes plaintext authorization values and Telegram bot tokens: ```python if channel == "webhook": webhook_url = str(payload.get("webhook_url", previous.get("webhook_url", "")) or "").strip() if webhook_url: normalized["webhook_url"] = webhook_url authorization = str(payload.get("authorization", previous.get("authorization", "")) or "").strip() if authorization == "***" and str(previous.get("authorization", "")).strip(): authorization = str(previous.get("authorization", "")).strip() if authorization: normalized["authorization"] = authorization headers = cls._normalize_headers(payload.get("headers", previous.get("headers", {}))) if headers: normalized["headers"] = headers elif channel == "telegram": telegram_bot_token = str( payload.get("telegram_bot_token", previous.get("telegram_bot_token", "")) or "" ).strip() if telegram_bot_token == "***" and str(previous.get("telegram_bot_token", "")).strip(): telegram_bot_token = str(previous.get("telegram_bot_token", "")).strip() if telegram_bot_token: normalized["telegram_bot_token"] = telegram_bot_token ``` Playwright browser state is also written without an explicit owner-only mode: ```python async def _login(platform: str) -> str: state_path = Path(session_path(platform)) state_path.parent.mkdir(parents=True, exist_ok=True) start_ ...[truncated 2165 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
datapulse/collectors/generic.py:315
Finding

Automatic Redirects Allow Internal Requests Before SSRF Validation

Content
View full analysis
str: session = self._build_http_session() try: with session.get( url, timeout=self.timeout, allow_redirects=True, stream=True, headers={ "User-Agent": "Mozilla/5.0", "Accept": "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8", }, ) as resp: resp.raise_for_status() safe, reason = validate_external_url(resp.url) if not safe: raise ValueError(f"Blocked redirect target: {reason}") content_type = (resp.headers.get("Content-Type") or "").lower() if content_type and not any(ct in content_type for ct in self.allowed_content_types): raise ValueError(f"Unsupported content type: {content_type}") ``` The URL validator correctly rejects private and special-purpose destinations when it is called: ```python def validate_external_url(url: str) -> tuple[bool, str]: parsed = urlparse(url) if parsed.scheme.lower() not in _ALLOWED_SCHEMES: return False, "Only http/https URLs are allowed." host = (parsed.hostname or "").strip().lower().rstrip(".") if not host: return False, "Missing hostname." blocked_hosts = {"localhost", "localhost.localdomain"} blocked_suffix = (".local", ".internal", ".lan", ".home") if host in blocked_hosts or any(host.endswith(s) for s in blocked_suffix): return False, "Local/internal hosts are blocked." try: ip_obj = ipaddress.ip_address(host) ...[truncated 2680 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
datapulse/collectors/native_bridge.py:110
Finding

Configurable Helper Processes Inherit the Entire Parent Environment

Content
View full analysis
list[str]: raw = os.getenv(BRIDGE_CMD_ENV, "").strip() if not raw: return [] return shlex.split(raw) def _bridge_env(self) -> dict[str, str]: env = dict(os.environ) if not env.get(BRIDGE_STATE_DIR_ENV, "").strip(): env[BRIDGE_STATE_DIR_ENV] = self._state_dir() if not env.get("DATAPULSE_SESSION_DIR", "").strip(): env["DATAPULSE_SESSION_DIR"] = str(session_dir()) return env ``` It invokes that executable with the complete inherited environment: ```python completed = subprocess.run( command, input=json.dumps(request_payload, ensure_ascii=True), capture_output=True, text=True, timeout=timeout_seconds, check=False, cwd=self._workdir(), env=self._bridge_env(), ) ``` The factuality backend has the same environment inheritance behavior: ```python def _factuality_backend_callable_path() -> str: return str(os.getenv(FACTUALITY_BACKEND_CALLABLE_ENV, "") or "").strip() def _factuality_backend_command() -> list[str]: raw = str(os.getenv(FACTUALITY_BACKEND_CMD_ENV, "") or "").strip() if not raw: return [] return shlex.split(raw) ``` ```python completed = subprocess.run( command, input=json.dumps(request_payload, ensure_ascii=True), capture_output=True, text=True, timeout=timeout_seconds, check=False, cwd=_factuality_backend_workdir(), env=dict(os.environ), ) ``` The factuality implementation can also dynamically import and execute an environment-selected Python callable: ```python def _resolve_factuality_backend_callable(path: str) -> ...[truncated 2377 chars]
Remediation
View remediation
Vulnerability Patterns
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (167)

Tainted flow: 'req' from os.getenv (line 433, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · datapulse/collectors/twitter.py (reported line 58)May include surrounding context.

python
try:
            req = urllib.request.Request("https://api.fxtwitter.com/", method="HEAD",
                                         headers={"User-Agent": "Mozilla/5.0"})
            urllib.request.urlopen(req, timeout=5)
            return {"status": "ok", "message": "FxTwitter API reachable", "available": True}
        except Exception as exc:
            return {"status": "warn", "message": f"FxTwitter unreachable: {exc}", "available": False}

Tainted flow: 'req' from os.getenv (line 106, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The request destination is derived from FXTWITTER_API_URL, which comes from the environment and is used to build an outbound URL without validation. If an attacker can influence environment configuration, they can redirect requests to arbitrary hosts, potentially causing SSRF, internal network probing, or unintended transmission of queried tweet identifiers and usage metadata.

Content

Scanner excerpt · datapulse/collectors/twitter.py (reported line 107)May include surrounding context.

python
for _ in range(2):
            try:
                req = urllib.request.Request(api_url, headers={"User-Agent": "Mozilla/5.0"})
                with urllib.request.urlopen(req, timeout=20) as resp:
                    data = json.loads(resp.read().decode())
                if data.get("code") != 200:
                    return ParseResult.failure(original_url, f"FxTwitter code {data.get('code')}: {data.get('message', 'unknown')}")

Tainted flow: 'req' from os.getenv (line 106, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · datapulse/collectors/twitter.py (reported line 434)May include surrounding context.

python
api_url = f"https://api.fxtwitter.com/{profile}"
        try:
            req = urllib.request.Request(api_url, headers={"User-Agent": "Mozilla/5.0"})
            with urllib.request.urlopen(req, timeout=20) as resp:
                data = json.loads(resp.read().decode())
            if data.get("code") != 200:
                return ParseResult.failure(original_url, f"Profile API error: {data.get('message', '')}")

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · datapulse/core/alerts.py (reported line 80)May include surrounding context.

python
valid = {f.name for f in cls.__dataclass_fields__.values()}
        return cls(**{k: v for k, v in data.items() if k in valid})


class AlertStore:
    """File-backed store for watch alert events."""

    def __init__(self, path: str | None = None):
        self.path = Path(path or alerts_path_from_env()).expanduser()
        self.events: list[AlertEvent] = []
        self.max_items = int(os.getenv("DATAPULSE_MAX_ALERTS", "500"))
        self._load()

    def _load(self) -> None:
        if not self.path.exists():
            self.events = []
            return
        try:
            raw = json.loads(self.path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            self.events = []
            return
        rows = raw if isinstance(raw, list) else raw.get("events", []) if isinstance(raw, dict) else []
        loaded: list[AlertEvent] = []
        for row in rows if isinstance(rows, list) else []:
            if not isinstance(row, dict):

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding

The code implements a narrow agent wrapper: it parses input text for URLs, rejects messages without URLs, forwards detected URLs to a batch reader with a minimum-confidence filter, and returns serialized results plus health information. This does align partially with content collection and confidence scoring, but it does not demonstrate web search, trending topics, or watch/triage workflows. The declared description is broader than the observed behavior and suggests capabilities not present in the supplied code chunk. No clearly undeclared sensitive capability appears, but the description does not accurately represent this chunk’s primary purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The code is a narrowly scoped native content collector bridge, not a full cross-platform search/trending/watch workflow component. It supports certain hostnames (e.g. Xiaohongshu, Zhihu, Douyin, Kuaishou, Tieba), builds a JSON request, runs an external sidecar command, manages timeout/workdir/state/session environment variables, and parses normalized content results. While this partially fits 'content collection' and adds confidence/provenance metadata, the declared description materially overstates the behavior by claiming web search, trending topics, and watch/triage workflows, none of which appear in this code chunk. Additionally, invoking an external subprocess and managing session/state storage are notable capabilities/resources not reflected in the declared permissions or description.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

This code chunk is overwhelmingly a frontend presentation layer: HTML markup, CSS styling, client-side state management, form helpers, localization, dashboard rendering, and fetch wrappers to call backend APIs. It supports workflows named mission board, cockpit, triage, alerts, routes, and stories, which partially aligns with the declared watch/triage workflow aspect. However, the declared description emphasizes substantive capabilities like cross-platform content collection, web search, trending topics, and confidence scoring. None of those core capabilities are actually implemented here; at most, the UI exposes fields and displays for such concepts. The primary purpose of this chunk is a web console/operations interface, which is materially narrower and different from the declared end-user capability set.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description presents a high-level content collection and search/triage capability set, but the supplied code chunk only provides a local utility cache. It does not collect content, perform web searches, analyze trends, score confidence, or implement watch/triage workflows. While caching could be a supporting internal component of such a system, this code chunk’s actual behavior is materially different from the declared primary purpose and lacks the stated end-user capabilities.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

This code chunk does not implement the broad operational capabilities claimed in the description. It only reads environment variables, normalizes primitive types, parses lists, and constructs a configuration object for search gateway tuning. While the presence of SearchGatewayConfig loosely relates to web search, the actual behavior is strictly runtime configuration management, not content collection, trending analysis, confidence scoring, or watch/triage workflows. Because the declared purpose suggests a much broader functional skill than what this code actually does, this is a material description-behavior mismatch for the supplied chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description emphasizes content collection, web search, trending topic detection, confidence scoring, and watch/triage workflows. The supplied code chunk does not implement those functions. Instead, it is focused on named entity extraction and normalization, with a fast heuristic path and an opt-in LLM-backed extraction path using requests to call a chat completions API. While entity extraction could be a supporting subcomponent of a larger content-analysis system, this specific code chunk's primary behavior is materially different from the declared purpose and includes an undeclared external LLM API capability.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The supplied code chunk’s primary purpose is persistent storage and querying of entities/relations in a local JSON file. It does not implement the declared higher-level functions such as cross-platform collection, web search, trending topics, confidence scoring, or watch/triage workflows. While an entity store could be a supporting component of a larger system, this chunk by itself is materially different from the declared description. It also accesses the local filesystem via a configurable path/environment variable, which is not indicated in the declared permissions.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description suggests substantial application functionality around collecting content, searching the web, identifying trends, scoring confidence, and supporting watch/triage workflows. In contrast, this code chunk is a narrow infrastructure utility for logger setup. It does not implement any of the declared end-user capabilities, nor does it access web/content resources or workflow mechanisms. While logging can be a supporting detail within a larger system, this specific chunk's actual behavior is materially different from the declared purpose, so it should be flagged as a mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description suggests a broad content collection/search and workflow system, but this code chunk specifically implements low-level media retrieval logic for certain XHS-related domains. It performs network access to download binary media and injects Referer headers for domain-specific access requirements. Those capabilities are not clearly represented by the declared purpose, which does not mention media downloading, domain-specific scraping/access logic, or binary content retrieval. This is more than a mere supporting detail because the chunk’s primary function is specialized remote media access rather than search, trending analysis, confidence scoring, or triage workflows.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The supplied code does not implement content collection, web search, trending-topic discovery, or confidence scoring. Its primary function is operational monitoring for a daemon: maintaining status fields, aggregating run metrics, recording errors, and writing JSON/HTML status outputs to the local filesystem. While the declared description mentions 'watch/triage workflows,' this code only supports status reporting for such a daemon rather than performing triage itself. That makes the actual behavior materially different from the declared purpose, and it also introduces undeclared filesystem persistence and static status-page generation.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description presents a content-collection/search/triage skill, but the provided code chunk is purely an internal resilience utility module. It does not perform content collection, web search, trending-topic analysis, confidence scoring, or watch/triage workflows. Instead, its primary purpose is retry and circuit-breaker behavior for service calls. This is a materially different purpose rather than a minor supporting detail within the described functionality, so the description does not accurately represent the code shown.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The supplied code chunk is narrowly focused on watch scheduling infrastructure. It converts schedule strings to intervals, computes next run times, selects due watch missions, enforces a daemon lock via a PID lockfile, and runs a polling loop that invokes reader.run_due_watches() with status tracking. This aligns only partially with the broad 'watch/triage workflows' portion of the description, but it does not implement or expose the other prominently declared capabilities such as cross-platform content collection, web search, trending topics, or confidence scoring. Because the declared description presents a much broader functional scope while the actual code is specifically a scheduler/daemon helper, this is a material description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description focuses on content collection, web search, trending topic analysis, confidence scoring, and workflow triage. The supplied code does none of those things. Instead, it implements credential-management helpers: it reads secrets from environment variables, optionally falls back to a local markdown-based secret store in an Obsidian/iCloud path, parses referenced env files, caches secret values, validates required configuration, and masks secrets for diagnostics. These are materially different capabilities and involve access to local credential files/resources not suggested by the description. This is not merely a supporting detail of the declared behavior; the code chunk’s primary purpose is secret handling, which is undeclared.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding

The declared description emphasizes cross-platform content collection, web search, trending topics, confidence scoring, and watch/triage workflows. The supplied code chunk instead performs local heuristic text analysis over already-provided items: it normalizes text, classifies stance using marker lists, extracts likely factual claims from sentences, groups topic stances, and reports contradictions. That could be a supporting component in a larger fact-checking or triage system, but on its own it does not implement the core declared capabilities such as collection, search, trending-topic discovery, or workflow management. Because the actual behavior is materially narrower and different in primary purpose, this chunk does not accurately match the declared description.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description suggests a broad content-collection and analysis skill with web search, trending topics, confidence scoring, and watch/triage workflows. This code chunk does not implement those higher-level capabilities. Instead, it manages source metadata: normalization, governance defaults, source matching against URLs, subscription handling, source packs, JSON file persistence, and simple authority-weight mapping. While source resolution and authority weights could support a larger collection/scoring system, this specific code is primarily catalog/configuration infrastructure, not search, trending analysis, or triage workflow execution. Therefore the description materially overstates and mischaracterizes the behavior of this code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
86% confidence
Finding

The declared description partially matches the triage/workflow aspect: this file clearly supports watch/triage workflows and confidence-related governance scoring. However, the supplied code chunk is not about cross-platform content collection, web search, or trending topics. Its primary behavior is review queue triage plus evidence governance/grounding. More importantly, it has an undeclared capability to call externally configured backend logic through dynamic imports or subprocess execution, which is security-relevant and not reflected in the description or permissions. That makes the description materially incomplete relative to the actual behavior.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description focuses on content collection, search, trend analysis, scoring, and triage workflows. The supplied code chunk instead implements browser-based authentication/session management utilities. It launches a non-headless Chromium instance, directs the user to platform login URLs, polls for login completion using cookies/localStorage and a Xiaohongshu account API check, and writes Playwright storage state to a session file. Those are materially different capabilities from the declared purpose and are not merely incidental implementation details of content collection. The code also targets only specific platforms (xhs, wechat) rather than providing the described broad collection/search/triage functionality.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The declared description presents an end-user or agent-facing content collection/search/trending/watch workflow capability. This code chunk does not implement those workflows directly; instead, it is an internal smoke suite for testing whether existing platform readers work against configured sample URLs. It reads environment variables, runs validation scenarios, prints PASS/FAIL results, and probes source catalog/feed generation. While it touches related domains such as cross-platform content reading and confidence thresholds, its primary purpose is diagnostics/testing rather than collection, web search, trending topic analysis, confidence scoring logic, or watch/triage workflow functionality. Therefore the description does not accurately represent this specific code chunk.

Content

No source excerpt is available for this finding.

Self-Modification

High
Category
Rogue Agent
Confidence
95% confidence
Finding

The CLI advertises a self-update path that leads users toward installing new code from a remote repository. In a skill/agent environment, self-modification materially increases risk because the tool can alter its own execution environment beyond the expected data-handling scope.

Content

Scanner excerpt · datapulse/cli.py (reported line 833)May include surrounding context.

python
return

    print("🔔 Update available")
    print("   Recommended: datapulse --self-update")


def _run_self_update() -> None:

Self-Modification

High
Category
Rogue Agent
Confidence
98% confidence
Finding

The _run_self_update function implements self-modification by fetching a remote release tag and then upgrading the package. This creates a supply-chain and remote-code-introduction risk, especially dangerous for agent-operated tools that should not mutate their runtime autonomously.

Content

Scanner excerpt · datapulse/cli.py (reported line 836)May include surrounding context.

python
print("   Recommended: datapulse --self-update")


def _run_self_update() -> None:
    current = datapulse.__version__
    latest_tag, error = _fetch_latest_release_tag()
    if not latest_tag:

Self-Modification

High
Category
Rogue Agent
Confidence
90% confidence
Finding

Including --self-update in the published skill contract exposes self-modification as part of the interface available to integrations and agents. That increases the chance that orchestration layers or automated users invoke a dangerous maintenance action unintentionally.

Content

Scanner excerpt · datapulse/cli.py (reported line 967)May include surrounding context.

python
"--troubleshoot",
                "--skill-contract",
                "--check-update",
                "--self-update",
                "--version",
            ],
            "env": {

Static analysis

No suspicious patterns detected.