Back to skill

Security audit

dbdoctor-tools

Security checks for vulnerabilities and agentic risk

Overview

This DBDoctor skill is coherent, but it needs Review because it can execute unrestricted SQL, register database instances, and handles credentials and tokens with unsafe transport and storage practices.

Install only for trusted DBDoctor environments and authorized operators. Prefer HTTPS with certificate validation, use least-privileged DBDoctor and database accounts, avoid production write access unless explicitly approved, do not pass real database passwords on shared shells, protect or relocate the token cache, and review any execute_sql or manage_instance use before running it.

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 (6)

T09 · Insecure Skill Coding Practices

Error
Location
common/client.py:45
Finding

TLS Certificate Verification Is Disabled for Sensitive API Communications

Content
View full analysis
dict: url = f"{config.base_url}{path}" token = get_token() resp = _requests.get( url, params=params, headers=_build_headers(token), verify=False, timeout=30, ) if _is_auth_failure(resp): print("Token expired. Re-authentication required...", file=sys.stderr) token = refresh_token() resp = _requests.get( url, params=params, headers=_build_headers(token), verify=False, timeout=30, ) resp.raise_for_status() return resp.json() def post(path: str, params: dict | None = None, json_body: dict | None = None) -> dict: url = f"{config.base_url}{path}" token = get_token() resp = _requests.post( url, params=params, json=json_body, headers=_build_headers(token), verify=False, timeout=30, ) if _is_auth_failure(resp): print("Token expired. Re-authentication required...", file=sys.stderr) token = refresh_token() resp = _requests.post( url, params=params, json=json_body, headers=_build_headers(token), verify=False, timeout=30, ) resp.raise_for_status() return resp.json() ``` Authentication requests repeat the same pattern: ```python resp = requests.post( url, json=payload, headers=headers, verify=False, timeout=30, ) ``` The documentation also presents `http://host:port` as the standard value for `DBDOCTOR_URL`. ### Technical Analysis Se ...[truncated 1939 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
common/auth.py:21
Finding

Hardcoded Reversible Keys and AES-ECB Are Used for Credential Protection

Content
View full analysis
str: """ Encrypt password using AES-ECB, return Base64 encoded string. Compatible with frontend CryptoJS.AES.encrypt (ECB mode, PKCS7 padding). """ if disabled: return plain_password key = AES_KEY.encode("utf-8") cipher = AES.new(key, AES.MODE_ECB) padded_data = pad(plain_password.encode("utf-8"), AES.block_size) encrypted = cipher.encrypt(padded_data) return base64.b64encode(encrypted).decode("utf-8") ``` The legacy storage mechanism uses another repository-wide static key: ```python _STORAGE_KEY = b"dbdoctor-tools!!dbdoctor-tools!!" _ENC_PREFIX = "ENC:" def _decrypt_from_storage(value: str) -> str: if not value.startswith(_ENC_PREFIX): return value encrypted = base64.b64decode(value[len(_ENC_PREFIX):]) cipher = AES.new(_STORAGE_KEY, AES.MODE_ECB) decrypted = unpad(cipher.decrypt(encrypted), AES.block_size) return decrypted.decode("utf-8") ``` ### Technical Analysis Both AES keys are embedded in source code distributed to every user. They therefore cannot function as secrets. Any person who obtains an `ENC:` value can use the source code to decrypt it. AES-ECB is deterministic, does not use a nonce or initialization vector, and provides no integrity protection. Base64 only converts binary ciphertext to text and adds no security. The login mechanism similarly gives every installation the same ability to reproduce the encrypted representation of passwords or verification codes. Although this behavior appears intended to match the server protocol rather than to create ...[truncated 1095 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
common/auth.py:223
Finding

Authentication Token Is Persisted as Plaintext Without Explicit Access Controls

Content
View full analysis
None: """Write token to cache file""" with open(_TOKEN_CACHE_FILE, "w", encoding="utf-8") as f: f.write(token) def _load_token() -> str | None: """Read token from cache file, return None if not exists""" if not os.path.exists(_TOKEN_CACHE_FILE): return None try: with open(_TOKEN_CACHE_FILE, "r", encoding="utf-8") as f: token = f.read().strip() return token if token else None except Exception: return None def get_token() -> str: token = _load_token() if token: return token token = _login() _save_token(token) return token ``` ### Technical Analysis The authentication token is written in plaintext under the project directory using the process's ambient umask. The code does not request owner-only permissions, validate ownership or permissions before loading the token, or use an operating-system credential store. `SKILL.md` states that `.token_cache` is listed in `.gitignore`, but no `.gitignore` was present in the audited project structure. This creates an additional risk of accidental packaging or source-control inclusion. Because the token is automatically attached to requests as `auth_token`, it should be treated as an authentication credential. ### Attack Path 1. A legitimate user invokes a script and completes authentication. 2. The Skill writes the resulting session token to `.token_cache`. 3. A local user, backup process, artifact collector, or source-control operation reads or copies the file. 4. The attacker sends the stolen token in an `auth_token` header to ...[truncated 550 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/manage_instance.py:43
Finding

Database Password Is Accepted in Process Arguments and Sent in a GET Query String

Content
View full analysis
dict: params = { "Ip": ip, "Port": port, "Engine": engine, "UserAccount": db_user, "UserPassword": encrypted_password, "Role": config.role, "UserId": config.user_id, } return client.get("/drapi/CheckDBUserPrivilege", params=params) ``` The plaintext password is collected as a command-line argument: ```python parser.add_argument( "--db-password", required=True, help="Database password (plaintext, auto RSA encrypted by program)", ) ``` The encrypted credential is also placed in the registration body: ```python encrypted_password = RSAUtils.encrypt_password(db_password) check_result = check_db_connectivity( ip, port, engine, db_user, encrypted_password, ) json_body = { "engine": engine, "ip_port": f"{ip}:{port}", "dbUser": db_user, "dbPwd": encrypted_password, "dbVersion": db_version, # Additional instance registration fields omitted here. } ``` ### Technical Analysis Command-line arguments may be retained in shell history and can be visible through process-inspection interfaces to users or monitoring agents on the same system. Therefore, accepting a plaintext database password with `--db-password` creates an avoidable local disclosure channel. The connectivity test sends `UserPassword` as a GET query parameter. Query strings are routinely recorded in reverse-proxy logs, access logs, observability traces, and error reports. RSA encryption does not eliminate this metadata exposure or make a reusable e ...[truncated 1434 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding

Third-Party Dependencies Are Installed Without Version or Integrity Pinning

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/execute_sql.py:15
Finding

Arbitrary SQL Execution Lacks Enforced Read-Only and Confirmation Safeguards

Content
View full analysis
dict: return client.post( "/draArmor/sqlConsole/dmsExecuteSql", params={ "Role": config.role, "UserId": config.user_id, }, json_body={ "InstanceId": instance_id, "Database": database, "Sql": sql, "Schema": schema, "Engine": engine, "PageSize": 100, "StartPage": 1, "TenantName": tenant, "ProjectName": project, "Role": config.role, "UserId": config.user_id, }, ) ``` The command accepts arbitrary SQL and immediately submits it: ```python parser.add_argument( "--sql", required=True, help="SQL statement to execute", ) args = parser.parse_args() result = execute_sql( instance_id=args.instance_id, database=args.database, schema=args.schema, sql=args.sql, engine=args.engine, tenant=args.tenant, project=args.project, ) ``` ### Technical Analysis The script forwards arbitrary SQL without a read-only default, destructive-statement detection, multi-statement restriction, transaction wrapper, or explicit confirmation step. The documentation warns operators that SQL is unrestricted, but this warning is not an enforceable security boundary. Arbitrary SQL execution is declared functionality, so its existence is not hidden or unrelated. Nevertheless, in an Agent-operated context, incorrect task interpretation or attacker-controlled content incorporated into `--sql` can cause a privileged database mutation. The code also trusts user-supplied tenant and project values ...[truncated 1214 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
Findings (58)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill includes a privileged instance-registration workflow that accepts database connection credentials and can onboard new database instances into the platform. Even though this is disclosed, it materially expands the trust boundary beyond read-only diagnosis and creates risk of unauthorized registration, credential exposure, or onboarding of sensitive systems if invoked improperly.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill includes a privileged instance-registration workflow that accepts database connection credentials and can onboard new database instances into the platform. Even though this is disclosed, it materially expands the trust boundary beyond read-only diagnosis and creates risk of unauthorized registration, credential exposure, or onboarding of sensitive systems if invoked improperly.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The skill includes a privileged instance-registration workflow that accepts database connection credentials and can onboard new database instances into the platform. Even though this is disclosed, it materially expands the trust boundary beyond read-only diagnosis and creates risk of unauthorized registration, credential exposure, or onboarding of sensitive systems if invoked improperly.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill includes a privileged instance-registration workflow that accepts database connection credentials and can onboard new database instances into the platform. Even though this is disclosed, it materially expands the trust boundary beyond read-only diagnosis and creates risk of unauthorized registration, credential exposure, or onboarding of sensitive systems if invoked improperly.

Content

No source excerpt is available for this finding.

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding

The skill uses 'file_write' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Content

No source excerpt is available for this finding.

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding

The skill uses 'network' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Content

No source excerpt is available for this finding.

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding

The skill uses 'env' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
97% confidence
Finding

The code decrypts stored secrets using a hardcoded static AES key embedded in source and uses AES-ECB mode, which is insecure and easily recoverable by anyone with code access. This means legacy '.env' secrets protected with the ENC: scheme are effectively reversible, undermining any secrecy of stored credentials.

Content

Scanner excerpt · common/config.py (reported line 40)May include surrounding context.

python
def _decrypt_from_storage(value: str) -> str:
    """Decrypt an ENC: prefixed value from legacy .env file. Returns plaintext as-is."""
    if not value.startswith(_ENC_PREFIX):
        return value
    encrypted = base64.b64decode(value[len(_ENC_PREFIX):])

Credential Access

High
Category
Privilege Escalation
Confidence
84% confidence
Finding

Automatically loading a local '.env' file as a fallback introduces a credential ingestion path from the filesystem, which can be abused if the working tree or deployment package is modified. In a tool that connects to database infrastructure, accepting local secret material increases the risk of credential substitution, accidental inclusion of secrets, and use of untrusted configuration.

Content

Scanner excerpt · common/config.py (reported line 55)May include surrounding context.

python
def _try_load_dotenv():
    """Try to load .env file (legacy fallback)"""
    try:
        from dotenv import load_dotenv
        env_path = Path(__file__).parent.parent / '.env'

Credential Access

High
Category
Privilege Escalation
Confidence
82% confidence
Finding

The code loads credentials from a predictable path relative to the package root if '.env' exists, without any trust validation. In environments where an attacker can write files into the package or workspace, they could influence authentication targets or credentials used by the tool.

Content

Scanner excerpt · common/config.py (reported line 58)May include surrounding context.

python
"""Try to load .env file (legacy fallback)"""
    try:
        from dotenv import load_dotenv
        env_path = Path(__file__).parent.parent / '.env'
        if env_path.exists():
            load_dotenv(dotenv_path=env_path)
            return True

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Confidence
80% confidence
Finding

YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Content

Scanner excerpt · scripts/manage_instance.py (reported line 23)May include surrounding context.

python
ter new database instance to DBDoctor platform.
"""

import argparse
import base64
import json
import sys

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5

from common import client
from common.config import config


class RSAUtils:
    """RSA encryption utility class (used to encrypt database password during management)"""
    
    PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuvJKNhSl6pmfNYqLLADJ1M59E3TW4ldYcq0PGpMlDLiNbjHW1Jt14zuNPxc+x2HFUPTHgngSHhURD0ZXJjYCkLmkOKVxv/9nZS3Thp5oJNXjk6wZEqneaaNdYT4KgSVc7DhO3oXXUFyrNzNNffs5paQ1Zd2JZhDYZxgpMlh2FMixkIz8znX8HDvLP8Fg5ImzoA50ljoV6w52EBRdo9+YO3zsflMFdLJnj10SDJyFcmI6rhXoE/C3eFMVyNdoPYQFVgHkyl6HTE92OVzSB41u/F9kQaawBll5KsgUs6XwmSK9NWVBz6i5M/vgP9A2Yz6aS+ZeenDm2gjjOiwETSqRsQIDAQAB
-----END PUBLIC KEY-----"""

    @staticmethod
    def encrypt_password(password: str) -> str:
        """
        RSA public key encryption for password
        :param password: Plaintext password
        :

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · common/config.py (reported line 14)May include surrounding context.

python
"""
test_env.py - Test .env file configuration loading

Used to verify if .env file loads environment variables correctly
"""

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · common/config.py (reported line 93)May include surrounding context.

python
"""
test_env.py - Test .env file configuration loading

Used to verify if .env file loads environment variables correctly
"""

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · common/config.py (reported line 121)May include surrounding context.

python
"""
test_env.py - Test .env file configuration loading

Used to verify if .env file loads environment variables correctly
"""

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/test_env.py (reported line 2)May include surrounding context.

python
"""
test_env.py - Test .env file configuration loading

Used to verify if .env file loads environment variables correctly
"""

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/test_env.py (reported line 4)May include surrounding context.

python
"""
test_env.py - Test .env file configuration loading

Used to verify if .env file loads environment variables correctly
"""

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/test_env.py (reported line 19)May include surrounding context.

python
"""
test_env.py - Test .env file configuration loading

Used to verify if .env file loads environment variables correctly
"""

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

This module sends the user's email address to request a verification code and later submits the verification code to the login API. Although the code prompts for entering the code, it does not clearly disclose to the user that these values are transmitted to remote endpoints.

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 · common/auth.py (reported line 76)May include surrounding context.

python
payload = {"email": email}

    try:
        resp = requests.post(url, json=payload, headers=headers, verify=False, timeout=30)
        resp.raise_for_status()
    except requests.RequestException as e:
        raise AuthError(f"Failed to send verification code: {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 · common/auth.py (reported line 154)May include surrounding context.

python
payload = {"email": email}

    try:
        resp = requests.post(url, json=payload, headers=headers, verify=False, timeout=30)
        resp.raise_for_status()
    except requests.RequestException as e:
        raise AuthError(f"Failed to send verification code: {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 · common/auth.py (reported line 197)May include surrounding context.

python
payload = {"email": email}

    try:
        resp = requests.post(url, json=payload, headers=headers, verify=False, timeout=30)
        resp.raise_for_status()
    except requests.RequestException as e:
        raise AuthError(f"Failed to send verification code: {e}")

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
99% confidence
Finding

The request disables TLS certificate verification with verify=False while transmitting authentication-related data. This allows a man-in-the-middle attacker to intercept or modify traffic, potentially capturing email addresses, verification-code workflow details, or redirecting the client to a spoofed service; in this skill context, the code handles authentication to a database diagnosis platform, so compromised sessions can expose sensitive operational data.

Content

Scanner excerpt · common/auth.py (reported line 76)May include surrounding context.

python
payload = {"email": email}

    try:
        resp = requests.post(url, json=payload, headers=headers, verify=False, timeout=30)
        resp.raise_for_status()
    except requests.RequestException as e:
        raise AuthError(f"Failed to send verification code: {e}")

Tainted flow: 'payload' from input (line 147, user input) → requests.post (network output)

Medium
Category
Data Flow
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.

Content

Scanner excerpt · common/auth.py (reported line 76)May include surrounding context.

python
payload = {"email": email}

    try:
        resp = requests.post(url, json=payload, headers=headers, verify=False, timeout=30)
        resp.raise_for_status()
    except requests.RequestException as e:
        raise AuthError(f"Failed to send verification code: {e}")

Tainted flow: 'payload' from input (line 147, user input) → requests.post (network output)

Medium
Category
Data Flow
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.

Content

Scanner excerpt · common/auth.py (reported line 154)May include surrounding context.

python
payload = {"email": email}

    try:
        resp = requests.post(url, json=payload, headers=headers, verify=False, timeout=30)
        resp.raise_for_status()
    except requests.RequestException as e:
        raise AuthError(f"Failed to send verification code: {e}")

Tainted flow: 'payload' from input (line 147, user input) → requests.post (network output)

Medium
Category
Data Flow
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.

Content

Scanner excerpt · common/auth.py (reported line 197)May include surrounding context.

python
payload = {"email": email}

    try:
        resp = requests.post(url, json=payload, headers=headers, verify=False, timeout=30)
        resp.raise_for_status()
    except requests.RequestException as e:
        raise AuthError(f"Failed to send verification code: {e}")

Static analysis

No suspicious patterns detected.