Back to skill

Security audit

qlik

Security checks for vulnerabilities and agentic risk

Overview

This Qlik Cloud skill mostly matches its stated purpose, but it needs review because its scripts can expose the API key or run unintended local code if inputs or tenant settings are unsafe.

Install only after reviewing and preferably fixing the shell scripts. Use a minimally scoped Qlik API key, set QLIK_TENANT only to the exact HTTPS Qlik Cloud tenant you control, avoid storing long-lived keys in broadly readable files, and do not expose these scripts to untrusted queries or IDs until Python interpolation and temporary-file handling are hardened.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/qlik-search.sh:21
Finding
Arbitrary Python Code Execution Through Unsafe Argument Interpolation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/qlik-search.sh:21-32` **Vulnerability Type**: Python code injection **Risk Level**: High ### Vulnerable Code ```bash TENANT="${QLIK_TENANT%/}" [[ "$TENANT" != http* ]] && TENANT="https://$TENANT" ENCODED_QUERY=$(python3 -c "import urllib.parse; print(urllib.parse.quote('''$QUERY'''))") curl -sL \ -H "Authorization: Bearer ${QLIK_API_KEY}" \ -H "Content-Type: application/json" \ "${TENANT}/api/v1/items?query=${ENCODED_QUERY}&limit=50" | python3 -c " import json import sys query = '''$QUERY''' timestamp = '$TIMESTAMP' ``` The same unsafe construction is also present in other scripts, including: - `scripts/qlik-answers-ask.sh:57-64` - `scripts/qlik-lineage.sh:110,122-132` - `scripts/qlik-users-search.sh:26-28,37-42` - Multiple scripts that insert identifiers directly into double-quoted `python3 -c` programs ### Technical Analysis The script constructs Python source code by directly substituting the user-controlled `QUERY` shell variable into triple-quoted Python literals. Shell quoting does not provide Python-language escaping. An input containing a terminating triple quote can escape the intended string literal and add arbitrary Python statements. The value is interpolated twice: once while URL-encoding the query and again while formatting the response. Exploitation can therefore occur before the HTTP request is sent. For example, a query shaped like the following breaks out of the literal: ```text x'''); print("INJECTED"); # ``` The first Python command becomes structurally equivalent to: ```python import urllib.parse print(urllib.parse.quote('''x''')) print("INJECTED") #''')) ``` An attacker can replace the harmless `print` operation with calls such as `__import__("os").system(...)`, file operations, or network operations. The resulting code runs with the same operating-system identity and environment as the Skill. In `qlik-lineage.sh`, the vulnerable `QRI` value may also origi ...[truncated 1450 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Never construct Python source from shell variables. Pass data through environment variables, standard input, or positional arguments. For example: ```bash ENCODED_QUERY=$( QUERY="$QUERY" python3 -c ' import os import urllib.parse print(urllib.parse.quote(os.environ["QUERY"])) ' ) ``` Use the same approach for response formatting: ```bash curl ... | QUERY="$QUERY" TIMESTAMP="$TIMESTAMP" python3 -c ' import json import os import sys query = os.environ["QUERY"] timestamp = os.environ["TIMESTAMP"] data = json.load(sys.stdin) # Process data without generating Python source. ' ``` Apply this correction to every interpolated value in every `python3 -c` block, including identifiers, QRI values, directions, levels, response bodies, and questions. Additional hardening should include: 1. Validate UUID arguments against a strict UUID expression before use. 2. Accept only `upstream`, `downstream`, or `both` for lineage direction. 3. Parse limits and levels as bounded integers in shell before invoking Python or `curl`. 4. Use `urllib.parse.urlencode` or `curl --get --data-urlencode` for query parameters. 5. Add regression tests containing quotes, triple quotes, newlines, backslashes, and Python syntax. 6. Avoid embedding API responses in Python source; provide them through standard input. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/qlik-health.sh:15
Finding
Bearer Token Disclosure Through Unvalidated or Plaintext Tenant URLs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/qlik-health.sh:15-21` **Vulnerability Type**: Insecure credential transport and destination validation **Risk Level**: High ### Vulnerable Code ```bash TENANT="${QLIK_TENANT%/}" [[ "$TENANT" != http* ]] && TENANT="https://$TENANT" curl -sL \ -H "Authorization: Bearer ${QLIK_API_KEY}" \ -H "Content-Type: application/json" \ "${TENANT}/api/v1/users/me" | python3 -c " ``` Equivalent tenant normalization and authenticated request behavior appears throughout all 37 scripts. ### Technical Analysis The scripts only prepend `https://` when the configured value does not begin with the characters `http`. Consequently, the following are accepted without rejection: ```text http://attacker.example http://internal-service https://arbitrary-non-qlik-host.example ``` Every API operation then attaches the Qlik bearer token to the selected destination: ```http Authorization: Bearer <QLIK_API_KEY> ``` Using `http://` exposes the token to passive and active network interception. Allowing an arbitrary HTTPS hostname sends the token directly to that host. The `-L` option also enables redirects; redirect behavior should be constrained even where a particular curl version protects sensitive headers during cross-host redirects. Communication with the configured Qlik tenant is necessary for the declared functionality, but accepting plaintext HTTP and unrestricted destinations is not necessary and violates least-privilege credential handling. ### Attack Path 1. An attacker modifies the `QLIK_TENANT` environment variable, influences a generated configuration, or persuades an operator to use an attacker-controlled tenant URL. 2. The script accepts the value because it begins with `http`. 3. The script invokes `curl` and attaches `QLIK_API_KEY` as a bearer token. 4. If the destination is attacker-controlled, the attacker receives the token directly. 5. If plaintext HTTP is used, an on-path attacker can captur ...[truncated 1037 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Validate the tenant URL before sending any authenticated request: 1. Parse the URL using a real URL parser rather than a string-prefix test. 2. Require the scheme to be exactly `https`. 3. Reject embedded user information, fragments, control characters, and malformed ports. 4. Require an approved Qlik Cloud hostname suffix or an explicit administrator-managed allowlist. 5. Resolve and validate the destination where protection against internal-network targeting is required. 6. Do not follow redirects to a different origin. 7. Require explicit administrative configuration for nonstandard or private Qlik deployments. Harden curl invocations: ```bash curl \ --silent \ --show-error \ --fail-with-body \ --proto '=https' \ --proto-redir '=https' \ --max-redirs 0 \ -H "Authorization: Bearer ${QLIK_API_KEY}" \ -H "Content-Type: application/json" \ "${TENANT}/api/v1/users/me" ``` If redirects are operationally necessary, inspect and allowlist their destination before repeating the authenticated request. Credential handling should also be improved: - Store the API key in a secret manager rather than plaintext documentation such as `TOOLS.md`. - Inject it into the process only for the duration of the request. - Use a dedicated, minimally scoped Qlik identity. - Separate read-only operations from destructive or state-changing operations. - Rotate the key immediately if it may have been sent to an untrusted destination. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/qlik-app-delete.sh:24
Finding
Predictable Temporary File Enables Symlink Overwrite and Response Tampering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/qlik-app-delete.sh:24-37` **Vulnerability Type**: Insecure temporary-file handling **Risk Level**: Medium ### Vulnerable Code ```bash HTTP_CODE=$(curl -sL -w "%{http_code}" -o /tmp/qlik_delete_response.txt -X DELETE \ -H "Authorization: Bearer ${QLIK_API_KEY}" \ -H "Content-Type: application/json" \ "${TENANT}/api/v1/apps/${APP_ID}") RESPONSE=$(cat /tmp/qlik_delete_response.txt 2>/dev/null || echo "") python3 -c " import json import sys app_id = '$APP_ID' http_code = '$HTTP_CODE' response = '''$RESPONSE''' timestamp = '$TIMESTAMP' ``` ### Technical Analysis The deletion script writes its HTTP response to the fixed, globally predictable path: ```text /tmp/qlik_delete_response.txt ``` Shared temporary directories are generally writable by other local users and processes. An attacker can create the target as a symbolic link before execution or replace it during execution. Because the script neither creates the file securely nor verifies its ownership and type, `curl` may follow the link and write through it. The file is also never removed. It can persist after execution and may expose Qlik API error details to another process, depending on the file mode and system configuration. A local attacker can additionally populate or replace the file to manipulate the response consumed by the script. The response is subsequently interpolated into Python source, compounding the separate Python injection weakness if attacker-controlled content contains terminating triple quotes. ### Attack Path 1. A local attacker predicts the fixed `/tmp/qlik_delete_response.txt` path. 2. Before the Skill runs, the attacker creates that path as a symbolic link to a file writable by the Skill’s operating-system account. 3. The Agent invokes `qlik-app-delete.sh`. 4. `curl -o` opens the predictable path and may follow the symbolic link. 5. The HTTP response overwrites or corrupts the linked target file. 6. Altern ...[truncated 1078 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Create an unpredictable, process-specific temporary file and remove it reliably: ```bash TMP_RESPONSE=$(mktemp "${TMPDIR:-/tmp}/qlik-delete.XXXXXX") chmod 600 "$TMP_RESPONSE" trap 'rm -f "$TMP_RESPONSE"' EXIT HTTP_CODE=$( curl --silent --show-error \ --write-out '%{http_code}' \ --output "$TMP_RESPONSE" \ --request DELETE \ -H "Authorization: Bearer ${QLIK_API_KEY}" \ -H "Content-Type: application/json" \ "${TENANT}/api/v1/apps/${APP_ID}" ) RESPONSE=$(cat "$TMP_RESPONSE") ``` Further hardening should include: 1. Use a private runtime directory owned by the Agent where possible. 2. Set a restrictive `umask`, such as `umask 077`, before creating temporary files. 3. Never reuse a fixed temporary filename across executions. 4. Install the cleanup trap immediately after successful file creation. 5. Avoid interpolating the response into Python source; pass the file path as a positional argument or provide the response through standard input. 6. Add concurrency tests to ensure simultaneous deletions cannot consume each other’s responses. 7. Require explicit user confirmation before invoking the destructive app-deletion operation. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (76)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The supplied code implements a narrow app-listing function only. It authenticates with QLIK_TENANT and QLIK_API_KEY, calls Qlik Cloud app/item listing endpoints, optionally filters by space, and formats the returned app metadata. There is no evidence in this code chunk of the many other declared capabilities such as health checks, app management beyond listing, reload execution, Insight Advisor queries, automations, AutoML, Qlik Answers AI, alerts, user/license management, file handling, or lineage. This is a material description-versus-behavior mismatch because the declared purpose describes a broad 37-tool integration, while the actual code chunk performs just one limited listing operation.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The supplied code implements one narrow Qlik Cloud app-auditing utility: it reads tenant/API key environment variables, calls the apps listing endpoint, and analyzes duplicate app names. This fits only a small slice of 'app management' or health/audit behavior. It does not implement the vast majority of the declared capabilities, such as search, reload operations, natural-language analytics, automations, AutoML, Qlik Answers AI, alerts, spaces, users, licenses, data files, or lineage. The description therefore materially overstates and misrepresents the actual behavior and primary purpose of this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The description claims a comprehensive Qlik Cloud integration with many operational and analytics features, but the code chunk only checks connectivity and fetches the current user profile from Qlik Cloud. While health checks are one declared feature, the supplied code does not substantiate the broader declared purpose and instead represents a much narrower skill. This is a material description-versus-behavior mismatch due to the substantially overstated scope and primary capability set.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The supplied code only performs one specific reload-related function: fetch reload status for a provided reload ID using Qlik tenant and API key credentials. While this behavior is within the broad declared domain of Qlik Cloud and reloads, the declared description claims a complete 37-tool integration spanning many unrelated capabilities that are not present here. This is a material description-versus-behavior mismatch because the actual code chunk has a much narrower primary purpose than the declared functionality.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The code chunk has a narrow, specific function: POST to the Qlik Cloud reloads API to trigger an app reload and report the result. The declared description presents a much broader skill with many capabilities across the Qlik platform. While app reloads are one listed capability, this code alone does not accurately represent the declared scope and primary purpose. There is no evidence of unrelated or dangerous undeclared behavior, but there is a substantial description-to-behavior mismatch because the implementation is far more limited than advertised.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The supplied code is narrowly focused on searching Qlik Cloud items via the /api/v1/items endpoint and formatting the results. It does not implement the wide range of capabilities claimed in the description, such as management operations, reloads, AI features, lineage, or user/license handling. While search is one of the declared capabilities, the declared description presents the skill as a complete multi-tool Qlik integration, whereas this code chunk represents only a small subset of that functionality. Therefore the description does not accurately represent what this specific code chunk actually does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The description presents a comprehensive Qlik Cloud integration with many operational and analytics capabilities. The actual code only retrieves current-user and tenant-related information from a single Qlik API endpoint. While this behavior is within the general Qlik Cloud domain, it does not substantiate the declared breadth or primary purpose of the skill. This is a material description-to-behavior mismatch because the code chunk supports only a small subset of the claimed functionality.

Ae1

High
Category
analysis-evasion
Content
| **Actual data values** (KPIs, numbers, trends) | `qlik-insight.sh` | "what is total sales", "which store has lowest stock" |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| **Actual data values** (KPIs, numbers, trends) | `qlik-insight.sh` | "what is total sales", "which store has lowest stock" |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| **Actual data values** (KPIs, numbers, trends) | `qlik-insight.sh` | "what is total sales", "which store has lowest stock" |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| **Actual data values** (KPIs, numbers, trends) | `qlik-insight.sh` | "what is total sales", "which store has lowest stock" |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| **Actual data values** (KPIs, numbers, trends) | `qlik-insight.sh` | "what is total sales", "which store has lowest stock" |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| **Actual data values** (KPIs, numbers, trends) | `qlik-insight.sh` | "what is total sales", "which store has lowest stock" |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

External Script Fetching

High
Category
Supply Chain
Content
TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/data-alerts/${ALERT_ID}" | python3 -c "
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL -X POST \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/data-alerts/${ALERT_ID}/actions/evaluate" | python3 -c "
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/data-alerts?limit=${LIMIT}" | python3 -c "
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
# If no thread ID, create one first
if [[ -z "$THREAD_ID" ]]; then
  THREAD_NAME="Conversation: ${TIMESTAMP}"
  THREAD_RESPONSE=$(curl -sL -X POST \
    -H "Authorization: Bearer ${QLIK_API_KEY}" \
    -H "Content-Type: application/json" \
    -d "{\"name\": \"${THREAD_NAME}\"}" \
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
fi

# Invoke the question on the thread
RESPONSE=$(curl -sL -X POST \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  -d "{\"input\":{\"prompt\":$(echo "$QUESTION" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read().strip()))'),\"promptType\":\"thread\",\"includeText\":true}}" \
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/assistants?limit=${LIMIT}" | python3 -c "
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
fi
BODY="$BODY}}"

curl -sL -X POST \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  -d "$BODY" \
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

# Get app metadata which includes table/field info
curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/apps/${APP_ID}/data/metadata" | python3 -c "
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/apps/${APP_ID}" | python3 -c "
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
TENANT="${QLIK_TENANT%/}"
[[ "$TENANT" != http* ]] && TENANT="https://$TENANT"

curl -sL \
  -H "Authorization: Bearer ${QLIK_API_KEY}" \
  -H "Content-Type: application/json" \
  "${TENANT}/api/v1/apps/${APP_ID}/data/lineage" | python3 -c "
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
# Use /items API for space filtering (personal or specific space)
  URL="${TENANT}/api/v1/items?resourceType=app&spaceId=${SPACE}&limit=${LIMIT}"
  
  curl -sL \
    -H "Authorization: Bearer ${QLIK_API_KEY}" \
    -H "Content-Type: application/json" \
    "$URL" | python3 -c "
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
"
else
  # Use /apps API for all apps (no space filter)
  curl -sL \
    -H "Authorization: Bearer ${QLIK_API_KEY}" \
    -H "Content-Type: application/json" \
    "${TENANT}/api/v1/apps?limit=${LIMIT}" | python3 -c "
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Static analysis

No suspicious patterns detected.