Back to skill

Security audit

NoahAI medical conference query

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent NoahAI medical conference search skill, with expected API-token and network use, plus some privacy and dependency-installation cautions.

Install only if you are comfortable sending conference, drug, disease, author, institution, and target search terms to noah.bio using NOAH_API_TOKEN. Avoid sensitive private queries in shared terminals or agent traces because the scripts print query payloads to stderr. Prefer installing requests in an isolated virtual environment with pinned dependencies.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (1)

T08 · Insecure Dependencies

Note
Location
SKILL.md:334
Finding

Unpinned Third-Party Dependency Installation Guidance

Content
View full analysis

Vulnerability Details

File Locations:

  • SKILL.md:334-338
  • scripts/search_conferences.py:24-27
  • scripts/search_presentations.py:25-28
  • scripts/search_chained.py:27-30

Vulnerability Type: T08: Insecure Dependencies
Risk Level: Low

Vulnerable Code

SKILL.md:334-338:

markdown
## Dependencies

- Python 3.8+
- `requests` library (`pip install requests`)
- Environment variable `NOAH_API_TOKEN` — register at [noah.bio](https://noah.bio) to obtain your API key.

scripts/search_conferences.py:24-27:

python
try:
    import requests
except ImportError:
    print("[ERROR] Missing dependency: requests\nInstall it with: pip install requests", file=sys.stderr)
    sys.exit(1)

scripts/search_presentations.py:25-28:

python
try:
    import requests
except ImportError:
    print("[ERROR] Missing dependency: requests\nInstall it with: pip install requests", file=sys.stderr)
    sys.exit(1)

scripts/search_chained.py:27-30:

python
try:
    import requests
except ImportError:
    print("[ERROR] Missing dependency: requests\nInstall it with: pip install requests", file=sys.stderr)
    sys.exit(1)

Technical Analysis

The project instructs users to install requests using an unversioned pip install requests command. It does not provide a dependency lock file, an exact reviewed version, package hashes, a trusted package-index declaration, or isolated-environment guidance.

Consequently, dependency resolution is mutable: the package and transitive dependency versions installed depend on the configured package index and the state of that index at installation time. This weakens build reproducibility and supply-chain integrity. Although requests is a well-known package and the scripts do not install it automatically, users are directly prompted to run the unsafe installation command when the import fails.

The guidance also conflicts with the statement in SKILL.md:348 that the Skill does not require runtime ...[truncated 1775 chars]

Remediation
View remediation

Remediation Suggestions

  1. Add a reviewed dependency manifest with an exact version, for example:
text
requests==2.32.5
  1. Generate and verify cryptographic hashes for the dependency and its transitive dependencies, then install with:
bash
python -m pip install --require-hashes -r requirements.txt
  1. Commit a lock file generated by a dependency-management tool and update it through a controlled review process.

  2. Recommend installation in an isolated virtual environment rather than a shared or system Python environment:

bash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --require-hashes -r requirements.txt
  1. Document the trusted package index explicitly and avoid unreviewed alternate indexes or --extra-index-url configurations.

  2. Replace the runtime error messages with instructions that reference the reviewed requirements file rather than the unpinned package name.

  3. Correct the packaging statement in SKILL.md so it accurately states whether dependency installation is required.

  4. Use an automated dependency-update process that verifies hashes, runs tests, and requires review before accepting new versions.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (10)

Tainted flow: 'headers' from os.environ.get (line 46, credential/environment) → requests.post (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 · scripts/search_chained.py (reported line 53)May include surrounding context.

python
print(f"[INFO] Endpoint: {url}", file=sys.stderr)
    print(f"[INFO] Query payload:\n{json.dumps(payload, indent=2)}", file=sys.stderr)
    try:
        response = requests.post(url, headers=headers, json=payload, timeout=30, allow_redirects=False)
        response.raise_for_status()
    except requests.exceptions.ConnectionError as e:
        raise ConnectionError(f"Cannot connect to API server: {url}\nDetails: {e}")

Tainted flow: 'headers' from os.environ.get (line 54, credential/environment) → requests.post (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 · scripts/search_conferences.py (reported line 61)May include surrounding context.

python
print(f"[INFO] Endpoint: {url}", file=sys.stderr)
    print(f"[INFO] Query payload:\n{json.dumps(payload, indent=2)}", file=sys.stderr)
    try:
        response = requests.post(url, headers=headers, json=payload, timeout=30, allow_redirects=False)
        response.raise_for_status()
    except requests.exceptions.ConnectionError as e:
        raise ConnectionError(f"Cannot connect to API server: {url}\nDetails: {e}")

Tainted flow: 'headers' from os.environ.get (line 55, credential/environment) → requests.post (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 · scripts/search_presentations.py (reported line 62)May include surrounding context.

python
print(f"[INFO] Endpoint: {url}", file=sys.stderr)
    print(f"[INFO] Query payload:\n{json.dumps(payload, indent=2)}", file=sys.stderr)
    try:
        response = requests.post(url, headers=headers, json=payload, timeout=30, allow_redirects=False)
        response.raise_for_status()
    except requests.exceptions.ConnectionError as e:
        raise ConnectionError(f"Cannot connect to API server: {url}\nDetails: {e}")

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill advertises and documents capabilities that involve environment variable access, reading query files, writing output files, and making network requests, but it does not declare an explicit tool/permission scope. That creates an authorization and transparency gap: a host agent may expose broader capabilities than intended, and users/reviewers cannot verify least-privilege boundaries from the manifest alone.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
60% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

md
- It requires exactly one environment variable: `NOAH_API_TOKEN`. Store it in the environment or a local `.env` file, and never place it inline in commands, chats, or packaged files.
- The token is scoped to read medical public details only and cannot access private user records.
- The skill does not intentionally persist request parameters locally. Any server-side retention is determined by the NoahAI API service and its operational logging policies.
- It does not request persistent or system-level privileges and does not modify system configuration.
- The skill is source-file based (Python scripts only) and does not require runtime installs, package downloads, or external bootstrap steps.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script logs the full JSON query payload to stderr before every request, which can expose sensitive user-supplied search terms such as drug interests, disease areas, institutions, or researcher names in logs, terminals, CI output, or shared agent traces. In a medical/biopharma search context, these queries may reveal confidential research interests or strategic business activity, making the logging more sensitive than generic search telemetry.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% 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 · scripts/search_chained.py (reported line 53)May include surrounding context.

python
print(f"[INFO] Endpoint: {url}", file=sys.stderr)
    print(f"[INFO] Query payload:\n{json.dumps(payload, indent=2)}", file=sys.stderr)
    try:
        response = requests.post(url, headers=headers, json=payload, timeout=30, allow_redirects=False)
        response.raise_for_status()
    except requests.exceptions.ConnectionError as e:
        raise ConnectionError(f"Cannot connect to API server: {url}\nDetails: {e}")

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% 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 · scripts/search_conferences.py (reported line 61)May include surrounding context.

python
print(f"[INFO] Endpoint: {url}", file=sys.stderr)
    print(f"[INFO] Query payload:\n{json.dumps(payload, indent=2)}", file=sys.stderr)
    try:
        response = requests.post(url, headers=headers, json=payload, timeout=30, allow_redirects=False)
        response.raise_for_status()
    except requests.exceptions.ConnectionError as e:
        raise ConnectionError(f"Cannot connect to API server: {url}\nDetails: {e}")

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The script sends user-supplied medical search terms and an API credential to an external third-party service without any explicit consent prompt or privacy warning at execution time. In a medical-domain skill, query terms may reveal sensitive research interests or potentially sensitive health-related topics, so silent external transmission increases privacy and compliance risk.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
84% confidence
Finding

This code transmits user-provided query content to an external network service. While external transmission is core to the skill's purpose, it still constitutes a real data-exposure boundary because conference/drug/disease queries are sent off-box to a remote provider, which is more sensitive in a medical context.

Content

Scanner excerpt · scripts/search_presentations.py (reported line 62)May include surrounding context.

python
print(f"[INFO] Endpoint: {url}", file=sys.stderr)
    print(f"[INFO] Query payload:\n{json.dumps(payload, indent=2)}", file=sys.stderr)
    try:
        response = requests.post(url, headers=headers, json=payload, timeout=30, allow_redirects=False)
        response.raise_for_status()
    except requests.exceptions.ConnectionError as e:
        raise ConnectionError(f"Cannot connect to API server: {url}\nDetails: {e}")

Static analysis

No suspicious patterns detected.