Back to skill

Security audit

Global Nuclear Brief

Security checks for vulnerabilities and agentic risk

Overview

This skill performs the disclosed task of fetching public nuclear policy news and sending it to Contextual to generate a brief, with no evidence of hidden persistence, local data theft, or destructive behavior.

Install only if you are comfortable sending your search topic, audience, and fetched article content to Apify and Contextual. Use scoped API keys with spending limits where possible, keep them out of commits and logs, and treat the generated brief as a draft because public news snippets can influence the model output.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
bin/nuclear_brief.py:91
Finding

Untrusted News Content Is Passed Directly to the Generation Model

Content
View full analysis

Vulnerability Details

File Location: bin/nuclear_brief.py:91-107, bin/nuclear_brief.py:116-136, bin/nuclear_brief.py:185-195, and SKILL.md:28
Vulnerability Type: Indirect prompt injection through untrusted retrieved content
Risk Level: Medium

Vulnerable Code

python
def build_knowledge(items: List[Dict[str, Any]], limit: int) -> List[str]:
    knowledge: List[str] = []
    count = 0
    for raw in items:
        if count >= limit:
            break
        article = normalize_article(raw)
        if not article["title"] or not article["link"]:
            continue
        parts = [
            f"Title: {article['title']}",
            f"Source: {article['source'] or 'Unknown'}",
            f"Date: {article['date'] or 'Unknown'}",
            f"Link: {article['link']}",
        ]
        if article["snippet"]:
            parts.append(f"Snippet: {article['snippet']}")
        knowledge.append("\n".join(parts))
        count += 1
    return knowledge
python
def generate_brief(
    token: str,
    model: str,
    topic: str,
    audience: str,
    knowledge: List[str],
    max_tokens: int,
    timeout: int,
) -> str:
    system_prompt = (
        "You are a policy analyst assistant. Use ONLY the provided knowledge. "
        "If evidence is missing, say so. Do not invent facts."
    )
    user_prompt = (
        f"Create a concise nuclear energy policy brief for {audience}.\n"
        f"Topic focus: {topic}.\n\n"
        "Format:\n"
        "1. Top changes (3-5 bullets)\n"
        "2. Why it matters (3 bullets)\n"
        "3. Risks and uncertainties (2-3 bullets)\n"
        "4. Recommended actions (3 bullets)\n"
        "5. Sources (list of titles + links)\n"
    )
    payload = {
        "model": model,
        "messages": [{"role": "user", "content": user_prompt}],
        "knowledge": knowledge,
        "system_pro
...[truncated 2784 chars]
Remediation
View remediation

Remediation Suggestions

  1. Explicitly identify every retrieved article field as untrusted data in the system prompt.
  2. Add a high-priority instruction that commands found in titles, snippets, sources, dates, and URLs must never be followed.
  3. Wrap each article and field in clear structured delimiters, preferably a documented JSON schema rather than concatenated free-form text.
  4. Validate links before model submission and output:
    • Permit only https URLs.
    • Reject credentials, control characters, malformed hosts, and unexpected schemes.
    • Consider an allowlist of trusted publishers where appropriate.
  5. Limit the length of every external field and reject control characters or abnormal content.
  6. Validate the generated response before printing it:
    • Enforce the required five-section structure.
    • Confirm that every cited URL appeared in the retrieved dataset.
    • Reject newly invented or modified links.
    • Ensure cited titles correspond to retrieved records.
  7. Do not require unconditional verbatim forwarding. Permit the calling agent or a deterministic validator to reject malformed, unsafe, or instruction-bearing output.
  8. Consider a two-stage design in which retrieved data is first converted into a strict factual schema and only validated fields are used for generation.

T08 · Insecure Dependencies

Note
Location
bin/nuclear_brief.py:10
Finding

Mutable and Caller-Selectable Third-Party Apify Actor Is Not Pinned or Allowlisted

Content
View full analysis

Vulnerability Details

File Location: bin/nuclear_brief.py:10-12, bin/nuclear_brief.py:43-61, and bin/nuclear_brief.py:153
Vulnerability Type: Unpinned remote supply-chain component
Risk Level: Low

Vulnerable Code

python
APIFY_API_URL = "https://api.apify.com/v2/acts/{actor_id}/run-sync-get-dataset-items"
CONTEXTUAL_API_URL = "https://api.contextual.ai/v1/generate"
DEFAULT_ACTOR = "practicaltools/apify-google-news-scraper"
python
def fetch_news(
    token: str,
    actor_id: str,
    query: str,
    language: str,
    country: str,
    timeframe: str,
    max_articles: int,
    timeout: int,
) -> List[Dict[str, Any]]:
    url = APIFY_API_URL.format(actor_id=actor_id)
    payload = {
        "searchTerm": query,
        "language": language,
        "country": country,
        "timeframe": timeframe,
        "maxArticles": max_articles,
    }
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json",
    }
    data = http_post_json(url, payload, headers, timeout)
python
parser.add_argument("--actor", default=DEFAULT_ACTOR, help="Apify actor ID")

Technical Analysis

The workflow relies on the remotely maintained practicaltools/apify-google-news-scraper actor without pinning an audited immutable build or version. The --actor option also permits the caller to select an arbitrary actor identifier without an allowlist.

The Apify token is transmitted only to the expected api.apify.com host, so the implementation does not directly send that credential to an arbitrary network domain. Nevertheless, the selected remote actor executes under the user’s Apify account and controls the dataset supplied to the remainder of the workflow.

A compromised, replaced, behaviorally modified, or intentionally selected actor could return manipulated data, produce unexpectedly large responses, consume ac ...[truncated 1559 chars]

Remediation
View remediation

Remediation Suggestions

  1. Pin the Apify dependency to an audited immutable actor build or version where the platform supports it.
  2. Remove the public --actor option unless actor selection is required for declared functionality.
  3. If actor selection is necessary, enforce an explicit allowlist of reviewed actor identifiers and versions.
  4. Use an Apify token with the minimum possible permissions, resource limits, and spending limits.
  5. Validate actor responses against a strict schema, including:
    • Maximum response size.
    • Maximum number and length of fields.
    • Required field types.
    • Permitted URL schemes.
  6. Document that the actor is a remotely maintained third-party trust boundary.
  7. Monitor actor ownership, version changes, execution cost, and abnormal response patterns.
  8. Fail closed when actor provenance or the expected immutable version cannot be verified.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (5)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill requires environment secrets and live network access but does not declare an explicit tool scope such as allowed-tools or permissions. That creates an authorization and review gap: a runner may permit broader execution than intended, and users or platform controls cannot easily verify what capabilities the skill is supposed to use.

Content

No source excerpt is available for this finding.

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 · bin/nuclear_brief.py (reported line 10)May include surrounding context.

python
import urllib.request
from typing import Any, Dict, List

APIFY_API_URL = "https://api.apify.com/v2/acts/{actor_id}/run-sync-get-dataset-items"
CONTEXTUAL_API_URL = "https://api.contextual.ai/v1/generate"
DEFAULT_ACTOR = "practicaltools/apify-google-news-scraper"
DEFAULT_QUERY = "nuclear energy policy OR nuclear power regulation"

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 · bin/nuclear_brief.py (reported line 11)May include surrounding context.

python
from typing import Any, Dict, List

APIFY_API_URL = "https://api.apify.com/v2/acts/{actor_id}/run-sync-get-dataset-items"
CONTEXTUAL_API_URL = "https://api.contextual.ai/v1/generate"
DEFAULT_ACTOR = "practicaltools/apify-google-news-scraper"
DEFAULT_QUERY = "nuclear energy policy OR nuclear power regulation"

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This code sends the assembled article knowledge and user-specified topic/audience to the Contextual API, which is a network transmission of potentially sensitive user or system-derived data. Although the script name and argument parser imply remote services are used, there is no explicit user-facing disclosure at the point of transmission, no confirmation prompt, and no comment/docstring warning that article content will be sent to a third-party model endpoint.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The markdown shows users exporting APIFY_API_TOKEN and CONTEXTUAL_API_KEY, which are sensitive credentials. The file does not include any caution about securely storing, not logging, or not sharing these values, so the skill description omits a basic warning about privacy/security-sensitive behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.