Back to skill

Security audit

analytics-engineer

Security checks for vulnerabilities and agentic risk

Overview

This analytics skill is coherent and not malicious, but its examples include unsafe credential, database, and outbound automation patterns that need review before use.

Review and harden the examples before using them in a real environment. Use managed secrets, pinned dependencies, HTTPS and host allowlists for Tableau, read-only least-privilege warehouse roles, validated table and column allowlists, and explicit approval before refreshes, production dbt runs, or email alerts.

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

T09 · Insecure Skill Coding Practices

Error
Location
references/examples.md:615
Finding
Tableau credentials can be transmitted to a caller-controlled endpoint<![CDATA[ ## Vulnerability Details **File Location**: `references/examples.md:615-634` **Vulnerability Type**: Unrestricted credential transmission **Risk Level**: High ### Vulnerable Code ```python def __init__(self, tableau_server_url, username, password): self.server_url = tableau_server_url self.username = username self.password = password self.auth_token = None self.site_id = None def authenticate(self): """Authenticate with Tableau Server""" auth_url = f"{self.server_url}/api/3.10/auth/signin" payload = { 'credentials': { 'name': self.username, 'password': self.password, 'site': {'contentUrl': ''} } } response = requests.post(auth_url, json=payload) ``` ### Technical Analysis The destination used to transmit the Tableau username and password is constructed directly from the caller-supplied `tableau_server_url`. The example does not require HTTPS, verify that the hostname belongs to an approved Tableau deployment, or explicitly prevent redirects. Authentication with Tableau is consistent with the Skill's BI-refresh functionality. However, allowing credentials to be sent to an unrestricted destination exceeds minimum privilege. A malicious or accidentally misconfigured URL could receive the supplied credentials. A plain HTTP URL could also expose them to network interception. The `requests` library verifies TLS certificates by default for HTTPS destinations, but that protection does not address a malicious yet valid HTTPS host, an explicitly supplied HTTP URL, or credential forwarding caused by unsafe destination configuration. ### Attack Path 1. An attacker influences the `tableau_server_url` configuration, deployment parameters, or copied implementation. 2. The victim supplies valid Tableau credentials and invokes `authenticate()`. 3. The code appends `/api/3.10/auth/signin` to the attacker-controlled URL. 4. `requests.post()` sends the username and passw ...[truncated 511 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require an `https://` URL and reject all other schemes. - Parse the URL and compare its normalized hostname and port against an explicit allowlist. - Prevent authentication requests from following redirects, or validate every redirect destination before forwarding sensitive data. - Prefer a narrowly scoped Tableau personal access token or equivalent service credential over an interactive account password. - Retrieve credentials from a managed secret store or protected environment variables. - Use a dedicated least-privileged service account restricted to the required data-source refresh operations. - Do not log request bodies, authentication headers, passwords, or returned tokens. - Add tests confirming that HTTP URLs, unapproved hosts, embedded URL credentials, and unexpected redirects are rejected. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
references/examples.md:690
Finding
Tableau password is hard-coded in an executable example<![CDATA[ ## Vulnerability Details **File Location**: `references/examples.md:690-693` **Vulnerability Type**: Hard-coded credential pattern **Risk Level**: Medium ### Vulnerable Code ```python bi_manager = BIRefreshManager( tableau_server_url="https://tableau.company.com", username="analytics_service", password="secure_password" ) ``` ### Technical Analysis The example embeds the password directly in source code. The displayed value appears to be a placeholder rather than a confirmed live credential, but the implementation pattern encourages users to replace it with a real password in an executable file. Source-embedded credentials can be exposed through version-control history, code review systems, backups, shared artifacts, or accidental logging. Removing a credential from the latest revision does not remove it from prior repository history. ### Attack Path 1. A user copies the example into an automation repository. 2. The user replaces `"secure_password"` with a functional Tableau password. 3. The file is committed, uploaded, logged, or shared. 4. An attacker with access to the repository or artifact extracts the password. 5. The attacker authenticates with the permissions assigned to `analytics_service`. ### Impact Assessment Exposure grants the privileges of the affected Tableau account. Depending on its role, this may permit viewing protected analytics, accessing data-source metadata, triggering refreshes, or changing Tableau resources. The scope increases if the account is overprivileged or the password is reused. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Replace the source-code password with retrieval from a secret manager or protected environment variable. - Fail closed if the required secret is absent; do not provide a functional default. - Clearly label all documentation values as nonfunctional placeholders. - Prefer a scoped, revocable Tableau personal access token. - Ensure secret values are masked in CI output and application logs. - Add secret scanning to pre-commit hooks and CI. - If a real credential has ever been committed, rotate it and purge it from repository history where appropriate. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
references/examples.md:866
Finding
Snowflake password is hard-coded in the monitoring example<![CDATA[ ## Vulnerability Details **File Location**: `references/examples.md:866-874` **Vulnerability Type**: Hard-coded credential pattern **Risk Level**: Medium ### Vulnerable Code ```python monitor = DataMonitor({ 'user': 'analytics_user', 'password': 'secure_password', 'account': 'company_account', 'warehouse': 'ANALYTICS_WH', 'database': 'ANALYTICS_DB', 'schema': 'MARTS' }) ``` ### Technical Analysis The Snowflake connection configuration includes a password directly in source code. Although `"secure_password"` appears to be illustrative, users may copy the example and substitute a real secret. This places warehouse credentials in a location commonly retained by source control and build systems. The monitoring operations only require narrowly scoped read access. A persistent password embedded in code provides unnecessary exposure compared with short-lived authentication and a dedicated read-only role. ### Attack Path 1. A user copies the monitoring example. 2. The placeholder is replaced with a valid Snowflake password. 3. The source file or its version history becomes accessible to an attacker. 4. The attacker extracts the account name, username, and password. 5. The attacker connects to Snowflake using the privileges assigned to `analytics_user`. ### Impact Assessment An attacker can exercise the Snowflake privileges granted to the monitoring account. Potential effects include unauthorized access to analytics data and metadata, consumption of warehouse resources, and—if the account is overprivileged—modification or deletion of warehouse objects and data. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Obtain the credential from a secret manager, workload identity, or protected environment variable. - Prefer key-pair, OAuth, or short-lived federated authentication over a static password. - Assign a dedicated read-only monitoring role restricted to the required database, schema, tables, and warehouse. - Avoid default credentials and fail if the secret has not been configured. - Mask Snowflake connection properties in logs and CI output. - Enable automated secret scanning and rotate any credentials previously committed to source control. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
references/examples.md:798
Finding
Unvalidated identifiers permit SQL injection in Snowflake monitoring queries<![CDATA[ ## Vulnerability Details **File Location**: `references/examples.md:798-839` **Vulnerability Type**: SQL injection through interpolated identifiers **Risk Level**: High ### Vulnerable Code ```python def check_data_freshness(self, table_name, timestamp_column, max_age_hours=2): """Check if data is fresh enough""" query = f""" SELECT MAX({timestamp_column}) as latest_timestamp, DATEDIFF('hour', MAX({timestamp_column}), CURRENT_TIMESTAMP()) as hours_old FROM {table_name} """ result = pd.read_sql(query, self.conn) hours_old = result['HOURS_OLD'].iloc[0] if hours_old > max_age_hours: self.send_alert( f"Data freshness alert for {table_name}", f"Data is {hours_old} hours old, exceeding threshold of {max_age_hours} hours" ) return False return True def check_row_count_anomaly(self, table_name, threshold_percent=20): """Check for unusual row count changes""" query = f""" WITH daily_counts AS ( SELECT DATE(created_at) as date, COUNT(*) as row_count FROM {table_name} WHERE created_at >= CURRENT_DATE - 7 GROUP BY DATE(created_at) ORDER BY date DESC ), count_comparison AS ( SELECT date, row_count, LAG(row_count) OVER (ORDER BY date) as prev_row_count, (row_count - LAG(row_count) OVER (ORDER BY date)) / LAG(row_count) OVER (ORDER BY date) * 100 as percent_change FROM daily_counts ) SELECT * FROM count_comparison WHERE date = CURRENT_DATE - 1 """ ``` ### Technical Analysis The `table_name` and `timestamp_column` arguments are inserted directly into SQL using f-strings. These are SQL identifiers, but they are neither restricted to known values nor safely validated and quoted. Ordinary value placeholders cannot always be used for identifiers. Consequently, safe identifier construction requires an expli ...[truncated 1364 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not accept arbitrary table or column names from untrusted sources. - Map external logical names to a fixed internal allowlist of fully qualified Snowflake identifiers. - Validate each identifier component with a conservative grammar and reject comments, whitespace, punctuation, and statement separators. - Quote identifiers through a connector-supported identifier-composition mechanism rather than manually concatenating input. - Use parameter binding for all values; reserve carefully controlled construction only for identifiers. - Run monitoring with a dedicated read-only role restricted to the exact schemas and tables being checked. - Configure statement timeouts and warehouse resource limits to reduce denial-of-service impact. - Add negative tests using malicious identifier payloads and verify that they are rejected before query execution. ]]>

T08 · Insecure Dependencies

Warning
Location
references/examples.md:730
Finding
CI installs unpinned third-party Python dependencies<![CDATA[ ## Vulnerability Details **File Location**: `references/examples.md:730-732` **Vulnerability Type**: Unpinned build dependencies **Risk Level**: Medium ### Vulnerable Code ```yaml pip install dbt-snowflake pip install sqlfluff pip install great-expectations ``` ### Technical Analysis The CI workflow installs packages without exact versions or integrity hashes. Each workflow run can therefore resolve to different package versions. An unexpected, compromised, or malicious upstream release could be installed and executed in the CI runner without a corresponding change to the audited repository. The package names shown are established projects, and there is no evidence that the example intentionally references a malicious or misspelled package. The risk arises from mutable dependency resolution rather than a confirmed malicious dependency. ### Attack Path 1. A compromised maintainer account or upstream package-distribution incident publishes a malicious release under one of the referenced package names. 2. The new release becomes the version selected by `pip`. 3. The CI workflow installs that release during a later run. 4. Malicious installation or runtime code executes in the runner. 5. The package accesses whatever repository data, credentials, tokens, network endpoints, or deployment permissions are available to that CI job. ### Impact Assessment Impact depends on the CI environment. A compromised dependency may read checked-out source code, alter generated artifacts, tamper with tests, access environment variables, and use available network connectivity. In a production deployment job, exposed credentials could permit changes to analytics infrastructure or data-warehouse resources. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Pin every direct and transitive dependency to a reviewed version through a lock file or constraints file. - Require package hashes, such as with `pip install --require-hashes`. - Build dependencies in a controlled update process and review lock-file changes. - Use a trusted internal package mirror or explicitly approved package index. - Run dependency vulnerability and provenance checks in CI. - Restrict CI tokens, secrets, filesystem access, and network access according to job requirements. - Separate untrusted test jobs from production deployment jobs and avoid exposing production credentials to dependency-installation steps. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

External Script Fetching

High
Category
Supply Chain
Content
dbt docs generate --target ci
        dbt docs serve --port 8080 &
        sleep 10
        curl http://localhost:8080
    
    - name: Data quality checks
      run: |
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The markdown presents code that authenticates to an external service and performs refresh operations using credentials, but the surrounding documentation does not warn that the example performs outbound authenticated requests. This omission is dangerous because users may treat the file as harmless reference material while it demonstrates high-trust patterns that can move data or trigger actions outside the local analytics environment.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The example adds active control over an external BI platform via authenticated HTTP API calls, which materially expands the skill from analytics modeling into remote service operation. Even as sample code, it normalizes credential handling and outbound actions that could be copied into agent behavior without clear scoping or user consent, increasing the risk of unauthorized external actions.

External Transmission

Medium
Category
Data Exfiltration
Content
}
        }
        
        response = requests.post(auth_url, json=payload)
        response.raise_for_status()
        
        auth_data = response.json()
Confidence
88% confidence
Finding
This line performs an outbound authenticated HTTP POST carrying credentials to an external service. In context, that is expected for Tableau automation, but it still represents external transmission of sensitive data and is risky when included in a general analytics skill because it can be copied into environments without proper disclosure, secret handling, or approval controls.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The code invokes local subprocesses to execute dbt commands, introducing command-execution capability beyond passive analytics guidance. In an agent-skill context, examples that run tools locally can be operationalized into unintended host actions, especially if later adapted to accept variable command arguments or targets.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The example combines direct database access with SMTP alerting but does not disclose those capabilities in the markdown narrative. In a skill repository, undocumented access and communication behaviors reduce operator awareness and make it easier for copied code to be deployed with sensitive connectivity and exfiltration-adjacent functionality.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The monitoring example includes SMTP email sending, which adds an external communications channel unrelated to the stated core analytics-engineering scope. In agent settings, outbound messaging can leak operational metadata, trigger spam/alert abuse, or be repurposed for unauthorized notification workflows if not explicitly disclosed and controlled.

Static analysis

No suspicious patterns detected.