T01 · Skill Instruction Hijacking
- Location
scripts/diff.py:216- Finding
Indirect Prompt Injection Through Scraped Competitor Content
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill mostly matches its competitor-monitoring purpose, but it needs review because it can persistently scrape configured sites, turn scraped text into agent instructions, store sensitive license data, and send reports to external chat services.
Review before installing. Remove or replace the bundled active competitors, restrict monitored URLs to intended public http/https domains, disable or rewrite the [AGENT] prompt blocks so scraped pages are treated only as data, pin dependencies, set explicit retention/deletion rules, and only configure chat delivery channels where you are comfortable sending the digest contents and using those service tokens.
scripts/diff.py:216Indirect Prompt Injection Through Scraped Competitor Content
scripts/scrape.py:67Server-Side Request Forgery Through Unrestricted Monitoring URLs
scripts/license.py:43License Key Exposed in Process Arguments and Plaintext Storage
requirements.txt:4Unpinned Third-Party Dependencies Permit Unreviewed Future Releases
The description claims a broad competitor-radar capability spanning six monitoring signals plus scheduled digest delivery and same-day alerts. The actual code chunk is narrowly scoped to GitHub activity only: repo listing, star counts, weekly star delta, new repos, and recent releases. That partially overlaps with the declared GitHub-related signal, but it does not implement the other five signals, alerting, or messaging integrations. The code’s primary purpose is therefore materially narrower than the declared purpose, making the description inaccurate for this supplied code chunk.
The code chunk does not perform competitor monitoring, content diffing, RSS/blog tracking, job tracking, GitHub analysis, alerting, or message delivery to Slack/Telegram/WhatsApp/Discord. Instead, its primary purpose is monetization and access control: validating a Gumroad license, persisting license information, and reporting account tier. While licensing could be a supporting component of a larger product, this code materially differs from the declared skill description and introduces undeclared capabilities involving external API calls to Gumroad and local license storage. Therefore this chunk is a clear description-behavior mismatch.
The code aligns partially with the declared competitor-monitoring purpose: it can discover relevant URLs, scrape pricing pages, capture homepage hero text for positioning, and collect blog/RSS updates. However, the declared description promises a broader 6-signal monitoring system with notification delivery and alerting. This code chunk only covers a subset of those signals and mainly performs scraping plus local snapshot storage. There is no implementation for job postings, GitHub metrics/releases, funding/pricing keyword alerting, digest generation, or sending results to Slack/Telegram/WhatsApp/Discord. Because several core declared capabilities are absent, the description overstates what this supplied code chunk actually does.
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.
"Are they moving upmarket? Simplifying? Cutting the free tier? Adding enterprise? "
"What should competitors know about this?"
)
return prompt_hint
def llm_interpret_hero_diff(diff_result: dict, competitor_name: str) -> str:
The code embeds pricing-derived llm_prompt text from upstream data directly into a digest as an [AGENT: ...] instruction block intended for later model expansion. Because that upstream content ultimately comes from scraped competitor-controlled pages, an attacker can plant prompt-injection text that is preserved and later executed/interpreted by an agent, potentially causing data exfiltration, tool misuse, or unsafe downstream actions.
The code similarly inserts homepage-derived llm_prompt text into an agent instruction block, turning untrusted website content into future agent directives. In this skill context, competitor websites are explicitly monitored and their content changes are attacker-controllable, which makes prompt-injection especially relevant and increases the likelihood of malicious instruction propagation into Slack/Discord digests or later automation steps.
The top-level description says the skill delivers digests and alerts to Slack, Telegram, WhatsApp, or Discord, but it does not clearly warn that collected competitor data will be transmitted to external third-party messaging platforms. This is dangerous because users may unintentionally exfiltrate scraped or derived monitoring data into services with separate retention, access control, and compliance properties.
The README describes weekly digests and archives but does not clearly warn users that competitor configurations, snapshots, jobs, digests, and alerts are stored persistently, with snapshots noted elsewhere as 'never deleted.' This creates a real transparency and data-retention risk because users may enable the skill without understanding that collected content is retained long-term and could accumulate sensitive or proprietary third-party data.
The README advertises broad natural-language triggers such as 'What changed at Notion this week?' and 'Who is Figma hiring?' that overlap with ordinary chat. In an agent environment, this can cause unintended skill invocation and unauthorized scraping, querying, or delivery actions when a user is merely conversing rather than explicitly invoking the tool.
The skill instructs use of shell execution, file reads/writes, environment inspection, and network access but does not declare any tool scope or allowed-tools boundary. That increases the blast radius of prompt injection or operator error because the runtime may grant broader capabilities than users expect, especially for a user-invocable skill that scrapes sites and edits local state.
The skill advertises sending digests and alerts to Slack, Telegram, WhatsApp, or Discord without clearly warning that competitor data and possibly derived analysis will be transmitted to third-party services. That creates a data-sharing and privacy risk, especially if users track sensitive targets or include internal annotations in stored digests.
The trigger phrases are broad enough to overlap with ordinary conversation, increasing the chance the skill activates on ambiguous user input. In a skill with shell, network, and file-write behaviors, accidental activation can lead to unintended scraping, local state modification, cron creation, or outbound actions without sufficiently explicit user intent.
This code performs outbound network requests to attacker-influenced URLs from competitor configuration using curl via subprocess. Although it avoids shell injection by passing an argument list, it still enables SSRF-style behavior, allowing the skill to reach internal services, cloud metadata endpoints, or other unintended network locations if an attacker can add or modify competitor URLs.
def curl_fetch(url: str, timeout: int = 10) -> str | None:
try:
result = subprocess.run(
[
"curl", "-sL", "--max-time", str(timeout),
"--user-agent", "Mozilla/5.0",
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
+ ["--channels"] + channels
)
try:
subprocess.run(cmd, timeout=30)
except Exception as e:
print(f"[alert] Delivery error: {e}", file=sys.stderr)
finally:
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
return False
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
resp = result.stdout
if '"ok":false' in resp or '"error"' in resp:
print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
return False
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
resp = result.stdout
if '"ok":false' in resp or '"error"' in resp:
print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
return False
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
resp = result.stdout
if '"ok":false' in resp or '"error"' in resp:
print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
return False
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
resp = result.stdout
if '"ok":false' in resp or '"error"' in resp:
print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
return False
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
resp = result.stdout
if '"ok":false' in resp or '"error"' in resp:
print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
return False
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
resp = result.stdout
if '"ok":false' in resp or '"error"' in resp:
print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
return False
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
resp = result.stdout
if '"ok":false' in resp or '"error"' in resp:
print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
return False
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
resp = result.stdout
if '"ok":false' in resp or '"error"' in resp:
print(f"[deliver/slack] API error: {resp[:200]}", file=sys.stderr)
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
chunks = chunk_message(formatted, PLATFORM_LIMITS["telegram"])
for chunk in chunks:
url = f"https://api.telegram.org/bot{token}/sendMessage"
payload = {
"chat_id": chat_id,
"text": chunk,
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
formatted = format_for_whatsapp(text)
chunks = chunk_message(formatted, PLATFORM_LIMITS["whatsapp"])
url = f"https://api.twilio.com/2010-04-01/Accounts/{sid}/Messages.json"
for chunk in chunks:
cmd = [
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
"""Run a sibling script and return its JSON stdout."""
cmd = ["python3", str(SCRIPTS_DIR / script)] + list(args)
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
if result.returncode != 0:
print(f"[digest] {script} exited {result.returncode}: {result.stderr[:200]}",
file=sys.stderr)
No suspicious patterns detected.