Back to skill

Security audit

Blueair Expert

Security checks for vulnerabilities and agentic risk

Overview

This skill is built to manage Blueair purifiers as advertised, but it handles account passwords and device-control commands unsafely enough that users should review it before installing.

Install only if you are comfortable giving the skill Blueair account access and letting it control purifier settings. Use a dedicated low-privilege Blueair account if possible, avoid password reuse, protect ~/.blueair/config.json with strict local permissions, and review or patch the login and set_state code before relying on it.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/api/gigyaClient.ts:19
Finding

Blueair Account Password Transmitted in the URL Query String

Content
View full analysis
{ const params = new URLSearchParams({ apiKey: this.api_key, loginID: this.username, password: this.password, targetEnv: 'mobile', }); const response = await this.apiCall('/accounts.login', params.toString()); ``` ```ts private async apiCall(url: string, data: string, retries = 3): Promise { const maxRetries = retries; try { const response = await fetch(`${this.gigyaApiUrl}${url}?${data}`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: '*/*', }, }); ``` Equivalent generated code is also present in: - `scripts/api/gigyaClient.js` - `dist/api/gigyaClient.js` ### Technical Analysis The login request includes the user's email address and plaintext password in a `URLSearchParams` value and appends that value to the request URL. Although the request uses HTTPS, TLS only protects data while it is in transit. It does not prevent the complete URL from being captured by: - Reverse-proxy or endpoint access logs - HTTP tracing and application-performance monitoring systems - Debugging instrumentation - Error telemetry - Network security products that terminate TLS - Request-history or diagnostic tooling The request declares `application/x-www-form-urlencoded`, but the form data is not placed in the POST body. As a result, the password is unnecessarily exposed in the URL. Sending credentials to Gigya is necessary for the declared Blueair authentication functionality. Sending the password as a URL query parameter, however, exceeds the minimum safe exposure required for that functionality. ...[truncated 1193 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
README.md:36
Finding

Blueair Password Stored in Plaintext Without File-Permission Enforcement

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/set_state.ts:58
Finding

Device-Control CLI Accepts Unrestricted Attributes and Values

Content
View full analysis
"); process.exit(1); } const uuid = args[0]; const attribute = args[1]; let value: any = args[2]; // Ensure types matches what the API expects (number or boolean) if (value === "true") value = true; else if (value === "false") value = false; else if (!isNaN(Number(value))) value = Number(value); try { const client = await getApi(); await client.setDeviceStatus(uuid, attribute, value); ``` The unchecked values are used to construct an authenticated API request: ```ts async setDeviceStatus(uuid: string, state: string, value: number | boolean): Promise { await this.checkTokenExpiration(); const body: any = { n: state }; if (typeof value === 'number') body.v = value; else if (typeof value === 'boolean') body.vb = value; return await this.apiCall(`/${uuid}/a/${state}`, body); } ``` ### Technical Analysis The standalone CLI instructed by `SKILL.md` does not enforce the supported attribute list. It accepts arbitrary device identifiers and state names and inserts them into the request path. It also permits unrestricted numeric values and leaves unrecognized values as strings. The separate MCP tool definition contains an enum for known attributes, but the CLI implementation used by the Skill instructions does not apply equivalent validation. The MCP schema also does not establish model-specific fan-speed ranges. This is not operating-system command injection because the arguments are not passed to a shell. It is an authenticated API input-validatio ...[truncated 1501 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
scripts/api/blueairAwsClient.ts:132
Finding

Retry Logic Can Deadlock While Holding a Non-Reentrant Mutex

Content
View full analysis
{ const release = await this.mutex.acquire(); try { const response = await fetch(`${this.blueairApiUrl}${url}`, { method, headers: { Authorization: `Bearer ${this.accessToken}`, idtoken: this.accessToken, 'Content-Type': 'application/json', ...headers, }, body: data ? JSON.stringify(data) : undefined, }); const json = await (response as any).json(); if (response.status !== 200) throw new Error(`API error ${response.status}`); return json; } catch (error) { if (retries > 0) return this.apiCall(url, data, method, headers, retries - 1); throw error; } finally { release(); } } ``` Equivalent generated behavior is present in `scripts/api/blueairAwsClient.js` and `dist/api/blueairAwsClient.js`. ### Technical Analysis `apiCall()` acquires an `async-mutex` lock before issuing the request. If the request fails, the catch block recursively invokes `apiCall()` before control reaches the outer `finally` block. The recursive invocation attempts to acquire the same non-reentrant mutex. The original invocation still owns that mutex and cannot release it until the recursive invocation returns. The recursive invocation therefore waits for the original invocation, while the original invocation waits for the recursive invocation, creating a deadlock. Failures that can trigger this condition include: - Network rejection - A non-200 HTTP response - Invalid or non-JSON response content - TLS or DNS failure The configured `BLUEAIR_API_TIMEOUT` constant is not applied to these ...[truncated 1224 chars]
Remediation
View remediation
{ let lastError: unknown; for (let attempt = 0; attempt <= retries; attempt++) { try { return await this.performRequest(url, data, method, headers); } catch (error) { lastError = error; if (attempt === retries) break; await delay(Math.min(1000 * 2 ** attempt, 8000)); } } throw lastError; }); } ``` 6. Add tests that simulate HTTP errors, malformed JSON, rejected promises, and timeouts to verify that retries terminate and the mutex is released. 7. Regenerate all JavaScript artifacts after correcting the TypeScript implementation. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (71)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Reading local credentials, authenticating to a cloud account, retrieving account identifiers, and enumerating devices are all sensitive operational behaviors absent from the simple expert framing. Even if intended for legitimate management, undisclosed credential and account handling increases privacy and security risk for users and reviewers.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

Reading local credentials, authenticating to a cloud account, retrieving account identifiers, and enumerating devices are all sensitive operational behaviors absent from the simple expert framing. Even if intended for legitimate management, undisclosed credential and account handling increases privacy and security risk for users and reviewers.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

Reading local credentials, authenticating to a cloud account, retrieving account identifiers, and enumerating devices are all sensitive operational behaviors absent from the simple expert framing. Even if intended for legitimate management, undisclosed credential and account handling increases privacy and security risk for users and reviewers.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Reading local credentials, authenticating to a cloud account, retrieving account identifiers, and enumerating devices are all sensitive operational behaviors absent from the simple expert framing. Even if intended for legitimate management, undisclosed credential and account handling increases privacy and security risk for users and reviewers.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Reading local credentials, authenticating to a cloud account, retrieving account identifiers, and enumerating devices are all sensitive operational behaviors absent from the simple expert framing. Even if intended for legitimate management, undisclosed credential and account handling increases privacy and security risk for users and reviewers.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

Reading local credentials, authenticating to a cloud account, retrieving account identifiers, and enumerating devices are all sensitive operational behaviors absent from the simple expert framing. Even if intended for legitimate management, undisclosed credential and account handling increases privacy and security risk for users and reviewers.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

Reading local credentials, authenticating to a cloud account, retrieving account identifiers, and enumerating devices are all sensitive operational behaviors absent from the simple expert framing. Even if intended for legitimate management, undisclosed credential and account handling increases privacy and security risk for users and reviewers.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

Reading local credentials, authenticating to a cloud account, retrieving account identifiers, and enumerating devices are all sensitive operational behaviors absent from the simple expert framing. Even if intended for legitimate management, undisclosed credential and account handling increases privacy and security risk for users and reviewers.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

This file implements Gigya authentication and token acquisition, which is unrelated to an indoor air quality expert skill. The mismatch between the declared skill purpose and the actual capability is a strong indicator of covert credential-handling or account-access behavior, especially because it silently logs in and retrieves session/JWT material from a third-party identity provider.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The code acquires a user session token, session secret, and JWT from Gigya using supplied credentials, which is an unjustified capability for a Blueair air-quality domain expert. In this context, token acquisition could enable unauthorized account access or reuse of identity artifacts outside the user's expectations, making the skill substantially more dangerous because the behavior is concealed behind an unrelated wellness/air-quality description.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: fast-uri==3.1.0 — 7 advisory(ies): CVE-2026-13676 (fast-uri vulnerable to host confusion via failed IDN canonicalization); CVE-2026-18446 (fast-uri vulnerable to host confusion via backslash authority introducer); CVE-2026-75975 (fast-uri vulnerable to server-side request forgery via malformed IPv6 normalizat) +4 more

High
Category
Supply Chain
Confidence
88% confidence
Finding

fast-uri 3.1.0 is flagged for multiple URI parsing and canonicalization issues, including host confusion and malformed IPv6 handling that can enable SSRF or origin validation bypass. In an agent skill that likely makes outbound requests to Blueair or related APIs, incorrect URI parsing can be especially dangerous if any user-controlled URL, host, or callback data is ever processed.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: hono==4.12.8 — 16 advisory(ies): CVE-2026-56762 (Hono missing validation of cookie name on write path in setCookie()); CVE-2026-47676 (Hono: app.mount() strips mount prefix using undecoded path, causing incorrect ro); CVE-2026-47675 (Hono: Cookie helper does not sanitize sameSite and priority, allowing Set-Cookie) +13 more

High
Category
Supply Chain
Confidence
92% confidence
Finding

hono 4.12.8 has numerous published advisories affecting cookies, routing, and request handling. Because this skill appears to rely on the MCP SDK’s embedded web/server stack, weaknesses in Hono can materially affect authentication boundaries, route matching, and response security if the server is exposed beyond a tightly controlled local context.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: ip-address==10.1.0 — 2 advisory(ies): CVE-2026-69192 (ip-address: Address4 decodes leading-zero octets as decimal while resolvers deco); CVE-2026-42338 (ip-address has XSS in Address6 HTML-emitting methods)

High
Category
Supply Chain
Confidence
80% confidence
Finding

ip-address 10.1.0 is associated with parsing inconsistencies and an XSS issue in HTML-emitting methods. The HTML-emission issue may be irrelevant if the package is never used for rendering, but the address parsing discrepancy can still matter for IP-based access controls or rate limiting in transitive server components.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: path-to-regexp==8.3.0 — 2 advisory(ies): CVE-2026-4923 (path-to-regexp vulnerable to Regular Expression Denial of Service via multiple w); CVE-2026-4926 (path-to-regexp vulnerable to Denial of Service via sequential optional groups)

High
Category
Supply Chain
Confidence
89% confidence
Finding

path-to-regexp 8.3.0 is reported vulnerable to ReDoS/DoS conditions from crafted route patterns or path inputs. Since it underpins routing behavior in common Node web frameworks, a reachable HTTP interface could allow attackers to consume CPU and degrade availability with malicious requests.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · dist/api/blueairAwsClient.js (reported line 125)May include surrounding context.

js
idtoken: jwt,
        });
        if (!response.access_token)
            throw new Error('AWS access token error');
        return { accessToken: response.access_token };
    }
    async apiCall(url, data, method = 'POST', headers, retries = 3) {

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/api/blueairAwsClient.js (reported line 125)May include surrounding context.

js
idtoken: jwt,
        });
        if (!response.access_token)
            throw new Error('AWS access token error');
        return { accessToken: response.access_token };
    }
    async apiCall(url, data, method = 'POST', headers, retries = 3) {

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/api/blueairAwsClient.ts (reported line 128)May include surrounding context.

ts
idtoken: jwt,
        });
        if (!response.access_token)
            throw new Error('AWS access token error');
        return { accessToken: response.access_token };
    }
    async apiCall(url, data, method = 'POST', headers, retries = 3) {

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

This file implements a Gigya authentication client that performs account login, session acquisition, and JWT issuance, which is materially unrelated to a stated indoor air quality expert skill. That mismatch is a strong indicator of hidden capability or covert credential-handling behavior, especially because it enables external identity-provider access rather than air-quality functionality.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The code logs into an identity provider and exchanges the resulting session for a JWT, creating a reusable authentication capability with access-token semantics. In the context of an air-quality domain expert skill, this is unjustified and dangerous because it can be used to impersonate users, access linked accounts, or support unauthorized downstream API calls.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

This file implements a full Gigya authentication flow, including login with username/password and JWT retrieval, which is unrelated to the stated indoor air quality expert purpose. That mismatch is dangerous because it introduces hidden identity-handling capability into a skill whose declared function would not reasonably require account authentication, increasing the likelihood of covert credential collection or unauthorized account access.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code contains standalone account-authentication functionality that can exchange user credentials for session tokens and JWTs without any evident connection to Blueair air-quality expertise. In the context of this skill, such capability is unjustified and creates a high-risk primitive for account takeover, secret harvesting, or reuse of credentials outside the user's expected interaction.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
95% confidence
Finding

The README instructs users to store a Blueair username and password in a plaintext file under their home directory, which creates persistent local credential exposure. If the host is multi-user, compromised, backed up insecurely, or if file permissions are weak, those credentials can be recovered and reused to access the user's Blueair account and device controls.

Content

Scanner excerpt · README.md (reported line 38)May include surrounding context.

2. Configure Credentials

Create a ~/.blueair/config.json file on your system with your Blueair account details:

json
{

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding

The skill directs the agent to run local Node.js scripts and the static analysis indicates those scripts use environment and network capabilities, yet the manifest declares no explicit tool scope or allowed-tools restrictions. This creates a least-privilege failure: a user invoking what appears to be a simple domain-expert skill may unknowingly trigger code with broader execution and outbound network access than the manifest discloses.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The trigger list includes broad everyday terms like 'filter' and 'stuffy', which can cause accidental invocation in unrelated contexts. Because this skill can access credentials, run local scripts, and potentially control devices, unintended activation raises the chance of unnecessary data access or unexpected actions.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
89% confidence
Finding

The install step instructs creation of ~/.blueair/config.json, implying persistent local storage of authentication material or account configuration. Persistent secrets in a predictable path increase the risk of credential theft by other local processes, accidental inclusion in backups, or exposure through weak file permissions.

Content

Scanner excerpt · SKILL.md (reported line 10)May include surrounding context.

md
credentials_required: true
  install_steps: 
    - "npm install"
    - "Create ~/.blueair/config.json"
---

# Blueair Expert (Indoor Air Quality Specialist)

Static analysis

No suspicious patterns detected.