Back to skill

Security audit

AI Layoff Radar

Security checks for vulnerabilities and agentic risk

Overview

The skill performs the advertised layoff-news analysis, but it also automatically contacts a billing service and attempts a $0.02 charge before analysis without declaring that in the skill metadata.

Review this carefully before installing. Expect outbound calls to Google News/RSS-linked article sites, OpenAI if OPENAI_API_KEY is set, and SkillPay. Do not run it unless you are comfortable with a mandatory per-call charge flow tied to a user_id, and prefer an isolated runtime with restricted network access and pinned dependencies.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
main.py:60
Finding
Mandatory Third-Party Billing and User Identifier Disclosure Are Not Declared in Skill Metadata<![CDATA[ ## Vulnerability Details **File Location**: `main.py:60-78`; supporting request implementation in `billing.py:29-75` **Vulnerability Type**: Undeclared external transmission and billing beyond the minimum privileges described by the Skill metadata **Risk Level**: Medium ### Vulnerable Code ```python logger.info("Checking SkillPay balance...") balance_result = check_balance(user_id=user_id) if not balance_result.get("ok"): logger.error("Failed to check balance") return { "error": "billing_error", "details": balance_result.get("message", "unable to check balance"), } logger.info("Charging user...") charge_result = charge_user(user_id=user_id) if not charge_result.get("ok"): if charge_result.get("error") == "insufficient_balance": logger.info("Insufficient balance") payment_result = get_payment_link(user_id=user_id, amount=0.02) return { "error": "payment_required", "payment_url": payment_result.get("payment_url"), "balance": charge_result.get("balance", balance_result.get("balance")), } ``` The corresponding billing requests are: ```python response = requests.get( url, params={"user_id": user_id}, headers=_headers(), timeout=REQUEST_TIMEOUT_SECONDS, ) ``` ```python payload = { "user_id": user_id, "skill_id": SKILL_ID, "amount": amount, "currency": "USD", } response = requests.post( url, json=payload, headers=_headers(), timeout=REQUEST_TIMEOUT_SECONDS, ) ``` ### Technical Analysis Every non-development execution sends the supplied `user_id` to `https://skillpay.me` and attempts to charge USD 0.02 before performing news retrieval or layoff analysis. The requests also transmit the configured `SKILLPAY_API_KEY` in the `X-API-Key` header. The billing behavior is documented in `README.md`, but it is absent from `SKILL.md`, which is the Skill metadata presented to an agent when determining requirements and ...[truncated 1967 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Declare `SKILLPAY_API_KEY` and `OPENAI_API_KEY` accurately in `SKILL.md`; remove the unused `NEWS_API_KEY` requirement. 2. Explicitly disclose the price, billing destination, transmitted identifier, and execution order in the Skill metadata. 3. Require affirmative user consent immediately before the charge rather than treating invocation alone as consent. 4. Provide a non-billing or local-analysis mode where practical. 5. Replace raw stable user identifiers with scoped, pseudonymous billing identifiers where the provider supports them. 6. Validate `user_id` length and format before transmission. 7. Minimize returned and retained billing response data; avoid exposing provider-specific raw responses. 8. Document the billing provider's privacy, retention, refund, and failure behavior. 9. Consider charging only after successful analysis or implement idempotency keys to prevent accidental duplicate charges during retries. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
news_fetcher.py:49
Finding
Remote RSS Entry URLs Are Downloaded Without SSRF or Resource Controls<![CDATA[ ## Vulnerability Details **File Location**: `news_fetcher.py:49-55` and `news_fetcher.py:94-112` **Vulnerability Type**: Unrestricted server-side retrieval of remotely supplied URLs **Risk Level**: Medium ### Vulnerable Code ```python def _extract_text(url: str) -> str: try: article = Article(url) article.download() article.parse() return article.text.strip() except Exception: logger.warning("Failed to parse full article text: %s", url) return "" ``` The URL passed to this function is obtained directly from an RSS entry: ```python for entry in parsed.entries[:max_items_per_feed]: url = entry.get("link", "").strip() if not url or url in seen_urls: continue seen_urls.add(url) title = clean_html((entry.get("title") or "").strip()) summary = clean_html((entry.get("summary") or "").strip()) published = _parse_date(entry.get("published") or entry.get("updated") or "") source = "" if isinstance(entry.get("source"), dict): source = (entry.get("source", {}).get("title") or "").strip() if not source: source = (parsed.feed.get("title") or "Unknown").strip() text = clean_html(_extract_text(url)) ``` ### Technical Analysis `entry["link"]` is remote-controlled content received from Google News RSS. The implementation passes it to `newspaper.Article.download()` without validating: - The URL scheme. - The initial hostname. - The resolved IP address. - Redirect destinations. - Whether an address belongs to loopback, private, link-local, multicast, or reserved ranges. - The final content type or response size. - The number of redirects or total download time beyond library defaults. Although the RSS feeds themselves are generated by Google News, linked destinations and redirect behavior are not inherently trusted. A malicious publisher, compromised article, poisoned feed result, or redirecting endpoint could cause the runtime to contact servi ...[truncated 1697 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Accept only `https` article URLs. 2. Restrict initial and final destinations to an explicit allowlist of intended news domains. 3. Resolve hostnames before requests and reject loopback, private, link-local, multicast, unspecified, and reserved IPv4 and IPv6 ranges. 4. Repeat hostname and IP validation after every redirect to prevent redirect-based and DNS-rebinding bypasses. 5. Disable automatic redirects or enforce a small redirect limit with validation at each hop. 6. Use a controlled HTTP client with explicit connect/read timeouts and streamed response-size limits instead of relying solely on `newspaper3k` defaults. 7. Reject unexpected content types and cap decompressed response size. 8. Apply outbound firewall or proxy policy so the process cannot reach cloud metadata services or private network ranges. 9. Log rejected destinations without including credentials or sensitive query strings. 10. Consider using RSS title and summary content alone when a linked destination is outside the approved source set. ]]>

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding
Dependencies Are Installed With Unbounded Future Versions and Without Integrity Verification<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-7` **Vulnerability Type**: Non-reproducible dependency resolution and insufficient supply-chain integrity controls **Risk Level**: Low ### Vulnerable Code ```text requests>=2.31.0 feedparser>=6.0.11 beautifulsoup4>=4.12.3 openai>=1.30.0 newspaper3k>=0.2.8 lxml[html_clean]>=5.2.1 python-dateutil>=2.9.0 ``` The documented installation command is: ```bash pip install -r requirements.txt ``` ### Technical Analysis All dependencies use minimum-version constraints. Consequently, a fresh installation may select any later release available from the configured package index. No lock file or package hashes are supplied. This does not prove that any listed package is currently malicious. The issue is that the audited source does not uniquely determine the code that will be installed. A future compromised release, malicious takeover, dependency-resolution change, or incompatible transitive dependency could therefore alter installation or runtime behavior without changes to this Skill repository. Python package installation can execute build backends and package-supplied code. Runtime imports also execute package initialization code with the permissions of the Skill process. ### Attack Path 1. A direct or transitive dependency publishes a compromised or otherwise unsafe future version satisfying the `>=` constraint. 2. A user runs `pip install -r requirements.txt`. 3. The package resolver selects that future version because no exact version or hash is required. 4. Malicious behavior executes during package build, installation, import, or normal runtime. 5. The dependency receives the same filesystem, environment, and network access as the Skill process. This attack requires compromise or abuse of the dependency supply chain; no such compromise was confirmed during the static audit. ### Impact Assessment A compromised dependency could potentially access: - `OPENAI_API_KEY` and `SKILLPAY_AP ...[truncated 386 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin all direct and transitive dependencies to reviewed exact versions. 2. Generate a lock file with a tool such as `pip-tools`, Poetry, or uv. 3. Require cryptographic hashes during installation, for example with `pip install --require-hashes`. 4. Build and retain reviewed wheels in a controlled package repository. 5. Use dependency vulnerability and license scanning in CI. 6. Update dependencies through reviewed pull requests rather than resolving arbitrary new versions at deployment time. 7. Install and run the Skill in a non-privileged, isolated environment with minimal filesystem and network access. 8. Record the package index explicitly and prevent fallback to untrusted indexes. ]]>
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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (27)

Credential Access

High
Category
Privilege Escalation
Content
.env
__pycache__/
*.pyc
*.pyo
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The skill claims to detect AI-driven layoffs from global news and generate structured reports, but the manifest only gives high-level instructions and example output without defining reliable sourcing, validation, or causality criteria. This mismatch can mislead users into overtrusting the results of a sensitive reputational-analysis workflow and may produce inaccurate or fabricated risk reports.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill claims to detect AI-driven layoffs from global news and generate structured reports, but the manifest only gives high-level instructions and example output without defining reliable sourcing, validation, or causality criteria. This mismatch can mislead users into overtrusting the results of a sensitive reputational-analysis workflow and may produce inaccurate or fabricated risk reports.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The skill claims to detect AI-driven layoffs from global news and generate structured reports, but the manifest only gives high-level instructions and example output without defining reliable sourcing, validation, or causality criteria. This mismatch can mislead users into overtrusting the results of a sensitive reputational-analysis workflow and may produce inaccurate or fabricated risk reports.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill claims to detect AI-driven layoffs from global news and generate structured reports, but the manifest only gives high-level instructions and example output without defining reliable sourcing, validation, or causality criteria. This mismatch can mislead users into overtrusting the results of a sensitive reputational-analysis workflow and may produce inaccurate or fabricated risk reports.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
This file implements balance checks, charging, and payment-link generation for an external billing service, which is materially unrelated to the skill's declared purpose of analyzing layoff news and generating reports. Hidden or unnecessary monetization logic expands the skill's capability to debit users or transmit billing-related identifiers without clear necessity, making abuse or unauthorized charging more dangerous in this context.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
DEV mode:

- Set `SKILLPAY_DEV_MODE=true` to bypass live billing calls during local development.
- In DEV mode, billing functions simulate success responses (for example balance `999`).
- In non-DEV mode, missing `SKILLPAY_API_KEY` raises: `SKILLPAY_API_KEY not configured`.
Confidence
60% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill metadata declares use of an environment variable and implies external news access, but it does not define explicit tool scope such as allowed network or env permissions. This weakens least-privilege controls and can let a runtime grant broader capabilities than reviewers or users expect.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The activation guidance uses broad phrases like 'AI layoffs' and 'companies replacing workers with AI' without scope limits, exclusions, or thresholds for evidence. That can cause over-triggering in unrelated contexts and increase the chance of producing defamatory, inaccurate, or low-confidence claims about companies and layoffs.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The code sends event content, including title, summary, reason, and up to 2000 characters of text, to an external OpenAI API. If those fields contain sensitive, proprietary, or personal data, this creates a real data exposure and third-party processing risk, especially because there is no consent, redaction, or policy gate in this file before transmission.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The code loads a billing API key from environment variables and uses it to interact with an external payment API, despite the skill being described only as a news-analysis tool. While using environment variables for secrets is normal, the concerning issue is the presence of external billing capabilities and credentialed payment operations that are outside the stated function, increasing the risk of undisclosed financial actions and data transmission.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The charge_user function can attempt to debit a user's account directly with no evidence of prior user notice, confirmation, or authorization in this component. In a skill whose advertised purpose is informational analysis, silent charging behavior is especially risky because users would not reasonably expect financial side effects from invoking the skill.

External Transmission

Medium
Category
Data Exfiltration
Content
"currency": "USD",
    }
    try:
        response = requests.post(
            url,
            json=payload,
            headers=_headers(),
Confidence
84% confidence
Finding
This POST transmits user_id, skill_id, amount, and currency to an external billing endpoint. External transmission by itself is not always unsafe, but here it supports an undisclosed billing action outside the skill's stated purpose, so the context makes the data flow more sensitive and potentially harmful.

External Transmission

Medium
Category
Data Exfiltration
Content
"currency": "USD",
    }
    try:
        response = requests.post(
            url,
            json=payload,
            headers=_headers(),
Confidence
80% confidence
Finding
This POST sends user and payment metadata to an external service to generate a payment link. Although payment-link creation is less severe than direct charging, it still initiates a monetization workflow unrelated to the declared layoff-analysis function and exposes user identifiers to a third party.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The skill performs billing checks, charges the user, and generates payment links even though its stated purpose is news analysis and layoff reporting. This expands the skill’s operational scope into payments, increasing abuse and user-surprise risk, especially because the billing logic is embedded directly in the main execution path before the reporting function completes.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
Payment processing is not justified by the visible function of detecting AI-related layoffs, so the code introduces a capability unrelated to the expected task. In a skill ecosystem, unnecessary financial operations materially increase risk because they can charge users or redirect them into payment flows without a clear, task-related need.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The code automatically calls `charge_user(user_id=user_id)` during execution with no in-file evidence of a user-facing confirmation, preview, or authorization step. Automatic charging is dangerous because a routine analysis request can directly trigger financial impact, and users may only discover the charge after the fact.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
The query URL forces `hl=en-US`, `gl=US`, and `ceid=US:en`, which imposes a specific language and locale in the skill's behavior. The file does not offer user opt-in, configurability, or a documented region-specific justification for this constraint.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.31.0
feedparser>=6.0.11
beautifulsoup4>=4.12.3
openai>=1.30.0
Confidence
94% confidence
Finding
The dependency is specified with a lower bound only, which allows future installs to resolve to different versions over time. This weakens reproducibility and can unintentionally pull in vulnerable or breaking releases, especially for a network-facing library like requests.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
88% confidence
Finding
requests has multiple known advisories, and because the manifest does not pin a version, it is impossible to verify whether the installed release is affected. In a skill that retrieves external content, this uncertainty is more significant because a vulnerable HTTP client could expose credentials, mishandle redirects, or weaken TLS/request safety.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.31.0
feedparser>=6.0.11
beautifulsoup4>=4.12.3
openai>=1.30.0
newspaper3k>=0.2.8
Confidence
93% confidence
Finding
feedparser is unpinned, so builds are not reproducible and may silently install different versions in different environments. Because this skill ingests external news/RSS data, dependency drift can increase exposure to parser-related security issues if a bad release or affected version is installed.

Unverifiable Dependency: feedparser has 10 known advisory(ies) (CVE-2011-1157 (feedparser Cross-site Scripting vulnerability); CVE-2009-5065 (feedparser Cross-site Scripting vulnerability); CVE-2011-1158 (feedparser Cross-site Scripting vulnerability) +7 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
87% confidence
Finding
feedparser has known historical advisories, and without version pinning there is no assurance that deployments avoid affected releases. Since this skill consumes untrusted feeds from the public internet, parser vulnerabilities are more relevant than in a closed-input application.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.31.0
feedparser>=6.0.11
beautifulsoup4>=4.12.3
openai>=1.30.0
newspaper3k>=0.2.8
lxml[html_clean]>=5.2.1
Confidence
92% confidence
Finding
beautifulsoup4 is declared with a minimum version rather than an exact version, which permits uncontrolled upgrades. That creates supply-chain and stability risk because the installed package may vary by install time and environment.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.31.0
feedparser>=6.0.11
beautifulsoup4>=4.12.3
openai>=1.30.0
newspaper3k>=0.2.8
lxml[html_clean]>=5.2.1
python-dateutil>=2.9.0
Confidence
91% confidence
Finding
The openai package is unpinned, so deployments may receive different versions with changed behavior or newly introduced flaws. While this file alone does not prove exploitation, allowing unconstrained dependency resolution is a real supply-chain hygiene weakness.

Unpinned Dependencies

Low
Category
Supply Chain
Content
feedparser>=6.0.11
beautifulsoup4>=4.12.3
openai>=1.30.0
newspaper3k>=0.2.8
lxml[html_clean]>=5.2.1
python-dateutil>=2.9.0
Confidence
94% confidence
Finding
newspaper3k is unpinned despite being used for article fetching/parsing from external content sources. For a skill that processes untrusted web content, reproducible parser versions matter because silent upgrades can introduce exploitable parsing behavior or dependency issues.

Static analysis

No suspicious patterns detected.