Back to skill

Security audit

FHIR Questionnaire Designer

Security checks for vulnerabilities and agentic risk

Overview

This healthcare questionnaire helper is mostly purpose-aligned, but it needs review because one lookup script can send clinical terms to arbitrary servers and the dependency install is not pinned.

Review and constrain network use before installing. Keep terminology lookups limited to trusted HTTPS FHIR terminology servers, avoid putting patient-identifying details in search terms, and consider pinning dependencies before running setup.sh. Generated questionnaire and CodeSystem files are user-directed local outputs, not hidden persistence.

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
scripts/query_valueset.py:46
Finding
Unrestricted terminology server allows sensitive query disclosure and unintended network access<![CDATA[ ## Vulnerability Details **File Location**: `scripts/query_valueset.py`, lines 46–53, 128–135, 190–197, and 342 **Vulnerability Type**: Server-Side Request Forgery risk and sensitive information disclosure **Risk Level**: Medium ### Complete Code Snippet ```python url = f"{server_url}/CodeSystem/$lookup?{parse.urlencode(params)}" try: print(f"Accessing: {url}", file=sys.stderr) req = request.Request(url) req.add_header("Accept", "application/fhir+json") with request.urlopen(req, timeout=30) as response: data = json.loads(response.read().decode()) ``` The same pattern is used for ValueSet expansion: ```python params = { "url": valueset_url } url = f"{server_url}/ValueSet/$expand?{parse.urlencode(params)}" try: print(f"Accessing: {url}", file=sys.stderr) req = request.Request(url) req.add_header("Accept", "application/fhir+json") with request.urlopen(req, timeout=30) as response: data = json.loads(response.read().decode()) ``` The server destination is supplied directly through a command-line argument: ```python parser.add_argument( "--server", default=DEFAULT_SERVER, help=f"FHIR server URL (default: {DEFAULT_SERVER})" ) ``` ### Technical Analysis The `--server` argument accepts an arbitrary URL and is concatenated directly with FHIR operation paths before being passed to `urllib.request.urlopen`. The implementation does not: - Require HTTPS. - Restrict destinations to the terminology servers documented in `SKILL.md`. - Reject loopback, private, link-local, or cloud metadata addresses. - Reject URLs containing embedded credentials. - Resolve and validate destination IP addresses. - Constrain or validate HTTP redirects. User-provided LOINC codes, search terms, and ValueSet canonical URLs are encoded into GET query strings. These values are then transmitted to the selected server and may also be exposed in proxy logs, web server logs, command output, and network monitoring syste ...[truncated 1914 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace unrestricted `--server` handling with an allowlist of approved HTTPS origins, such as: - `https://tx.fhir.org` - `https://hapi.fhir.org` - `https://r4.ontoserver.csiro.au` 2. Parse the URL with `urllib.parse.urlsplit` and reject: - Schemes other than HTTPS. - Embedded usernames or passwords. - Fragments and malformed ports. - Unexpected paths where only configured FHIR base paths should be accepted. 3. Resolve the hostname and reject loopback, private, link-local, multicast, reserved, and unspecified IP addresses using Python's `ipaddress` module. 4. Repeat destination validation after DNS resolution and before connection to reduce DNS rebinding risk. 5. Disable automatic redirects or validate every redirect target using the same scheme, host, and IP restrictions. 6. If custom servers are operationally required, make them an explicit opt-in feature and display a warning that query terms will be transmitted to that server. 7. Avoid printing full URLs containing potentially sensitive query parameters. Log only the destination host and operation name. 8. Consider using POST-based FHIR operations where supported so sensitive parameters are not placed in URLs and routine access logs. ]]>

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding
Unpinned dependency installation creates a supply-chain and reproducibility risk<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt`, line 1; `setup.sh`, lines 19 and 30 **Vulnerability Type**: Unbounded third-party dependency resolution **Risk Level**: Low ### Complete Code Snippet ```text jsonschema>=4.0.0 ``` The setup script installs the unresolved requirement directly: ```bash if command -v uv &> /dev/null; then echo "Using uv..." cd "$SKILL_DIR" if [ ! -d ".venv" ]; then uv venv fi uv pip install -r requirements.txt ``` ```bash elif command -v python3 &> /dev/null; then echo "Using python3 venv..." cd "$SKILL_DIR" if [ ! -d ".venv" ]; then python3 -m venv .venv fi .venv/bin/pip install -r requirements.txt ``` ### Technical Analysis The requirement uses a lower bound without an upper bound or exact version. Each installation may therefore resolve to a different future version of `jsonschema` and its transitive dependencies. The setup process also does not use a lock file, package hashes, or an explicitly trusted package index. This makes installation behavior dependent on the current package index, resolver state, and newly published package versions. This is not evidence that the current `jsonschema` package is malicious. The risk is that future package compromise, dependency compromise, incompatible releases, or use of an attacker-controlled package index could introduce unreviewed code into the environment. ### Attack Path 1. A user or automated agent executes `setup.sh`. 2. `pip` or `uv` resolves `jsonschema>=4.0.0` and its transitive dependencies against the configured package index. 3. The resolver selects whatever compatible versions are available at installation time. 4. If a selected release or configured index is compromised, malicious or unsafe package content is downloaded and installed. 5. The installed code runs when the validator imports `jsonschema`, with the privileges of the user running the Skill. ### Impact Assessment A comp ...[truncated 450 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `jsonschema` and all transitive dependencies to reviewed versions using a lock file. 2. Generate and verify cryptographic hashes for every package artifact. 3. Install with hash enforcement, for example through a generated requirements file and `pip install --require-hashes`. 4. Configure an explicit trusted package index rather than relying on ambient pip configuration. 5. Review dependency updates before regenerating the lock file. 6. Use automated vulnerability and provenance scanning for locked dependencies. 7. Keep installation inside the existing project-specific virtual environment and avoid running `setup.sh` with administrative privileges. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The description emphasizes questionnaire-definition assistance and terminology lookup from official coding APIs for LOINC and SNOMED CT. The actual script serves a different purpose: generating reusable custom CodeSystem and ValueSet FHIR resources under a proprietary Welshare URL base. This is a materially different primary function from code lookup, and it introduces undeclared behavior of generating and saving local resource files. There is no evidence of API access, official terminology lookup, or processing of requirement ideation docs. Therefore the code chunk does not accurately match the declared description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The description emphasizes generation of FHIR questionnaire definitions from plain-text requirement ideation documents and code lookup utilities for LOINC and SNOMED CT via official APIs. This code chunk instead serves a different purpose: post hoc inspection of an existing FHIR Questionnaire to extract and summarize LOINC codings. While optional validation may rely on a separate LOINC search helper, the core behavior is extraction/reporting, not questionnaire creation or broad terminology lookup. The mismatch is material because the primary function and supported terminologies differ from the declared description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The description partially overlaps with the code because both concern FHIR terminology support and questionnaire construction. However, this code chunk specifically handles LOINC answer lists and ValueSet expansion/search, and can emit a Questionnaire item template. It does not process plain requirement ideation documents, and it does not implement SNOMED CT or broader clinical concept lookup for conditions, findings, observations, medications, or procedures. The external resource usage is a generic FHIR terminology server (tx.fhir.org), not obviously the 'official coding APIs' claimed. Therefore the supplied code does not fully and accurately match the declared description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The description emphasizes questionnaire generation support from ideation documents and terminology lookup against LOINC/SNOMED CT APIs. The provided code does something materially different: it validates existing FHIR Questionnaire JSON files using a local schema and extra semantic rules. There is no code for parsing plain requirements, generating questionnaire definitions, calling external coding APIs, or looking up LOINC/SNOMED CT codes. While validation is related to FHIR questionnaires, it is an undeclared primary capability compared with the stated purpose, so this is a clear description-behavior mismatch.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
| prefix | 0..1 | string | Display prefix (e.g., "1.", "a)") |
| text | 0..1 | string | Primary text for the item |
| type | 1..1 | code | Type of item (see Item Types below) |
| enableWhen | 0..* | BackboneElement | Conditional display rules |
| enableBehavior | 0..1 | code | all \| any (default: all) |
| required | 0..1 | boolean | Whether item must be answered |
| repeats | 0..1 | boolean | Whether item can repeat |
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documentation advertises and instructs use of scripts that perform network access and local file reads/writes, but it does not declare any explicit tool scope such as permissions or allowed-tools. This creates an execution-governance gap where an agent may invoke broader capabilities than a reviewer or policy engine expects, increasing the risk of unintended file modification or outbound requests.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This markdown file provides example questionnaires that request personally identifiable information and health data, including name, date of birth, address, depression screening responses, medical history, allergies, surgeries, family history, tobacco use, and alcohol consumption. Under the markdown-specific warning rule, the description should disclose that these patterns involve sensitive personal and medical data and should be used with appropriate privacy and consent safeguards, but no such warning appears in the file.

Tainted flow: 'codesystem_file' from input (line 344, user input) → open (file write)

Medium
Category
Data Flow
Content
codesystem_file = output_dir / f"CodeSystem-{id}.json"
    valueset_file = output_dir / f"ValueSet-vs-{id}.json"

    with open(codesystem_file, 'w') as f:
        json.dump(codesystem, f, indent=2)

    with open(valueset_file, 'w') as f:
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'valueset_file' from input (line 345, user input) → open (file write)

Medium
Category
Data Flow
Content
with open(codesystem_file, 'w') as f:
        json.dump(codesystem, f, indent=2)

    with open(valueset_file, 'w') as f:
        json.dump(valueset, f, indent=2)

    # Success message
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Vague Triggers

Low
Confidence
76% confidence
Finding
This is a JSON manifest-type file, so vague-trigger checks apply. The description and purpose describe a generic 'comprehensive questionnaire template' and 'starting point for complex clinical questionnaires' without any explicit scope, invocation conditions, or exclusion criteria, which could make the skill/template applicable in overly broad contexts.

Natural-Language Policy Violations

Low
Confidence
81% confidence
Finding
This JSON schema includes a natural-language `language` property described as the base language of the resource, but it does not indicate that language selection should be user-driven or explicitly opted into. Because SQP-3 applies to all file types and covers language/locale policy concerns in natural-language text or config values, this is a mild policy concern in a schema that constrains locale-bearing content.

Unpinned Dependencies

Low
Category
Supply Chain
Content
jsonschema>=4.0.0
Confidence
83% confidence
Finding
The dependency is specified as a lower-bounded range (`jsonschema>=4.0.0`) rather than being pinned to an exact version, which makes builds non-reproducible and can silently pull in newly released versions with breaking changes or supply-chain issues. In a skill that may run scripts and process healthcare-related questionnaire definitions, unexpected dependency updates could affect reliability and, in the worst case, introduce vulnerable transitive code into the environment.

Missing User Warnings

Low
Confidence
90% confidence
Finding
The script sends the user's search term directly to an external terminology server (tx.fhir.org) without any explicit notice or consent at runtime. In a healthcare context, free-text queries may contain sensitive clinical terms or even patient-related information, so this creates a real data exposure/privacy risk even though the transmission is over HTTPS and appears functionally intended.

Static analysis

No suspicious patterns detected.