Back to skill

Security audit

OData v4 Query

Security checks for vulnerabilities and agentic risk

Overview

This skill is a read-only OData query helper with disclosed profile storage and credential handling, though its package should be cleaned up to remove stale bytecode files.

Before installing, verify you trust the OData endpoints saved in profiles because environment-provided tokens or headers will be sent to those services during queries. Prefer a repackaged source-only release without __pycache__ files, and review the local profile file if multiple services or defaults are configured.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (22)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
This finding is substantively the same issue: the skill claims to be a read-only query tool while also handling persistent configuration and authentication-related profile references. Even if credentials are kept in environment variables, managing auth/profile settings and writing config to disk introduces side effects and a broader capability set than users are led to expect, increasing the risk of misuse or accidental state changes.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
This finding is substantively the same issue: the skill claims to be a read-only query tool while also handling persistent configuration and authentication-related profile references. Even if credentials are kept in environment variables, managing auth/profile settings and writing config to disk introduces side effects and a broader capability set than users are led to expect, increasing the risk of misuse or accidental state changes.

Ae1

High
Category
analysis-evasion
Content
config.py list` and use its default profile. Read [references/configuration.md](references/configuration.md) when no endpoint is available or the user wants to
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
t, property, key, and navigation-property names. Read [references/discovery.md](references/discovery.md) when the schema or supported features are not already k
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
nd`, `$count`, or `$search` only when needed. Read [references/query-syntax.md](references/query-syntax.md) for syntax, literals, functions, keys, and nested ex
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
Read [references/responses-and-errors.md](references/responses-and-errors.md) when parsing response shapes, handling pagination/counts, or diagnosing HTTP/OData
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Skill ships a __pycache__ directory that normal discovery skips

High
Category
Supply Chain
Content
Python may load .pyc from this directory even when decoy .py sources look clean (PEP 552 UNCHECKED_HASH).
Confidence
95% confidence
Finding
Skill ships Python bytecode (__pycache__/ or .pyc/.pyo). Discovery skips these paths, so malicious bytecode can score SAFE while decoy sources look clean.

Skill ships Python bytecode (.pyc/.pyo) that normal analysis skips

High
Category
Supply Chain
Content
Bytecode is excluded from content analysis; a malicious .pyc can execute while source decoys remain clean.
Confidence
95% confidence
Finding
Skill ships Python bytecode (__pycache__/ or .pyc/.pyo). Discovery skips these paths, so malicious bytecode can score SAFE while decoy sources look clean.

Skill ships a __pycache__ directory that normal discovery skips

High
Category
Supply Chain
Content
Python may load .pyc from this directory even when decoy .py sources look clean (PEP 552 UNCHECKED_HASH).
Confidence
95% confidence
Finding
Skill ships Python bytecode (__pycache__/ or .pyc/.pyo). Discovery skips these paths, so malicious bytecode can score SAFE while decoy sources look clean.

Skill ships Python bytecode (.pyc/.pyo) that normal analysis skips

High
Category
Supply Chain
Content
Bytecode is excluded from content analysis; a malicious .pyc can execute while source decoys remain clean.
Confidence
95% confidence
Finding
Skill ships Python bytecode (__pycache__/ or .pyc/.pyo). Discovery skips these paths, so malicious bytecode can score SAFE while decoy sources look clean.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

External Transmission

Medium
Category
Data Exfiltration
Content
Bearer token profile:

```text
python scripts/odata_config.py set production --service-root https://api.example.com/odata/ --odata-version 4.0 --bearer-env PROD_ODATA_TOKEN --default
```

API-key/custom-header profile:
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
API-key/custom-header profile:

```text
python scripts/odata_config.py set warehouse --service-root https://data.example.com/odata/ --header-env X-API-Key=WAREHOUSE_API_KEY
```

Basic authentication profile:
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This code performs HTTP(S) GET requests using headers built from environment variables, including bearer tokens, basic auth, and arbitrary header values. Although the module docstring says the tool is read-only, there is no confirmation prompt, print/log disclosure, or inline warning to users that credentials or other sensitive header data may be sent to a remote service.

External Transmission

Medium
Category
Data Exfiltration
Content
path = pathlib.Path(temp_dir) / "services.json"
            value = profiles.empty_config()
            value["profiles"]["production"] = {
                "service_root": "https://api.example.test/odata",
                "odata_version": "4.01",
                "bearer_env": "PROD_ODATA_TOKEN",
                "headers_from_env": {"X-Tenant": "PROD_TENANT"},
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
path = pathlib.Path(temp_dir) / "services.json"
            value = profiles.empty_config()
            value["profiles"]["production"] = {
                "service_root": "https://api.example.test/odata",
                "odata_version": "4.01",
                "bearer_env": "PROD_ODATA_TOKEN",
                "headers_from_env": {"X-Tenant": "PROD_TENANT"},
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
path = pathlib.Path(temp_dir) / "services.json"
            value = profiles.empty_config()
            value["profiles"]["production"] = {
                "service_root": "https://api.example.test/odata",
                "odata_version": "4.01",
                "bearer_env": "PROD_ODATA_TOKEN",
                "headers_from_env": {"X-Tenant": "PROD_TENANT"},
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
path = pathlib.Path(temp_dir) / "services.json"
            value = profiles.empty_config()
            value["profiles"]["production"] = {
                "service_root": "https://api.example.test/odata",
                "odata_version": "4.01",
                "bearer_env": "PROD_ODATA_TOKEN",
                "headers_from_env": {"X-Tenant": "PROD_TENANT"},
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
path = pathlib.Path(temp_dir) / "services.json"
            value = profiles.empty_config()
            value["profiles"]["production"] = {
                "service_root": "https://api.example.test/odata",
                "odata_version": "4.01",
                "bearer_env": "PROD_ODATA_TOKEN",
                "headers_from_env": {"X-Tenant": "PROD_TENANT"},
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
"headers_from_env": parse_headers(args.header_env),
    }
    for field in ("bearer_env", "basic_user_env", "basic_password_env"):
        option = getattr(args, field)
        if option:
            profile[field] = option
    value["profiles"][args.name] = validate_profile(args.name, profile)
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
def query_pairs(args: argparse.Namespace) -> list[tuple[str, str]]:
    pairs: list[tuple[str, str]] = []
    for name in ("select", "filter", "orderby", "top", "skip", "count", "expand", "search"):
        value = getattr(args, name)
        if value is not None:
            pairs.append(("$" + name, str(value).lower() if isinstance(value, bool) else str(value)))
    seen = {key for key, _ in pairs}
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
args.service_root = profile["service_root"]
    args.odata_version = args.odata_version or profile.get("odata_version", "4.0")
    for field in ("bearer_env", "basic_user_env", "basic_password_env"):
        if not getattr(args, field, None):
            setattr(args, field, profile.get(field))
    configured_headers = [f"{name}={env_name}" for name, env_name in profile.get("headers_from_env", {}).items()]
    args.header_env = configured_headers + list(getattr(args, "header_env", []))
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Static analysis

Detected: suspicious.dynamic_code_execution, suspicious.exposed_secret_literal

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/test_odata_get.py:16

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/test_odata_profiles.py:12

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
references/configuration.md:26