Back to skill

Security audit

Hostex

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Hostex management helper, but it has confirmed safety-control and credential-destination weaknesses that warrant Review before installation.

Install only if you are comfortable reviewing and controlling Hostex API access yourself. Use a read-only, narrowly scoped PAT where possible, avoid HOSTEX_BASE_URL except with non-production tokens, do not rely on --dry-run when --confirm is also present, and treat reservation, guest, message, lock-code, pricing, and availability output as sensitive.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/hostex-client.mjs:22
Finding
Arbitrary API Base URL Can Exfiltrate the Hostex Access Token and Private Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/hostex-client.mjs:22-29`, `scripts/hostex-client.mjs:44-70` **Vulnerability Type**: Unrestricted credential-bearing network destination **Risk Level**: High ### Vulnerable Code ```js export function buildUrl(path, query = {}) { const base = getEnv('HOSTEX_BASE_URL', DEFAULT_BASE_URL); // Important: preserve base path segments (e.g. https://api.hostex.io/v3) // new URL('/room_types', 'https://api.hostex.io/v3') would drop /v3. const baseUrl = new URL(base.endsWith('/') ? base : `${base}/`); const rel = path.startsWith('/') ? path.slice(1) : path; const u = new URL(rel, baseUrl); ``` ```js export async function hostexRequest({ method, path, query, json, headers, timeoutMs = 30000, retries = 2, }) { const token = getEnv('HOSTEX_ACCESS_TOKEN'); if (!token) throw new Error('Missing HOSTEX_ACCESS_TOKEN'); const url = buildUrl(path, query); const reqHeaders = { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Hostex-Access-Token': token, ...headers, }; const attempt = async (n) => { const controller = new AbortController(); const t = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs); try { let res; try { res = await fetch(url, { method, headers: reqHeaders, body: json ? JSON.stringify(json) : undefined, signal: controller.signal, }); ``` ### Technical Analysis The API client obtains its destination from the unrestricted `HOSTEX_BASE_URL` environment variable. It does not validate the URL scheme or require the destination hostname to be `api.hostex.io`. Every request attaches `HOSTEX_ACCESS_TOKEN` as the `Hostex-Access-Token` header. Write requests can also carry guest names, email addresses, telephone numbers, messages, reservation details, property identifiers, availability information, and pricing data in the request body. Send ...[truncated 1796 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `HOSTEX_BASE_URL` from production operation and always use the fixed official endpoint: ```js const DEFAULT_BASE_URL = 'https://api.hostex.io/v3'; ``` 2. If an override is necessary for testing, require a separate explicit development-only option and validate it before loading the token: - Require the `https:` protocol. - Allowlist exact trusted hostnames. - Reject embedded credentials, unexpected ports, and ambiguous hostname forms. - Keep localhost or test endpoints behind an explicit test mode that uses a non-production token. 3. Validate the final URL immediately before every credential-bearing request: ```js function validateApiUrl(url) { if (url.protocol !== 'https:' || url.hostname !== 'api.hostex.io') { throw new Error('Refusing to send Hostex credentials to an untrusted destination'); } } ``` 4. Disable automatic cross-origin redirects for authenticated requests, or validate every redirect destination before resending credentials. For example, use `redirect: 'error'` if redirects are not required. 5. Continue recommending read-only, narrowly scoped PATs. Use separate credentials for test and production environments and rotate any PAT that may have been sent to an untrusted endpoint. 6. Add automated tests proving that credential-bearing requests are rejected for: - Plain HTTP URLs - Unapproved domains - Lookalike or subdomain-confusion hostnames - Unexpected ports - Cross-origin redirects ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/hostex-write.mjs:47
Finding
Dry-Run Mode Executes Real Write Operations When Combined with Confirmation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/hostex-write.mjs:47-56`, `scripts/hostex-write.mjs:113-125`, `scripts/hostex-write.mjs:167-175`, `scripts/hostex-write.mjs:232-244` **Vulnerability Type**: Broken write-operation safety control **Risk Level**: High ### Vulnerable Code The defect appears in every supported write command. #### Send message ```js const plan = { conversation_id, text }; if (!confirm || dryRun) { console.log(JSON.stringify({ action: cmd, plan, confirmRequired: !confirm }, null, 2)); if (!confirm) return; } const { data } = await hostexRequest({ method: 'POST', path: `/conversations/${conversation_id}`, json: { text } }); console.log(JSON.stringify(data, null, 2)); return; ``` #### Update listing prices ```js const plan = { channel_type, listing_id, prices }; if (!confirm || dryRun) { console.log(JSON.stringify({ action: cmd, plan, confirmRequired: !confirm }, null, 2)); if (!confirm) return; } const { data } = await hostexRequest({ method: 'POST', path: '/listings/prices', json: plan, }); console.log(JSON.stringify(data, null, 2)); return; ``` #### Create reservation ```js if (!confirm || dryRun) { console.log(JSON.stringify({ action: cmd, plan, confirmRequired: !confirm }, null, 2)); if (!confirm) return; } const { data } = await hostexRequest({ method: 'POST', path: '/reservations', json: plan }); console.log(JSON.stringify(data, null, 2)); return; ``` #### Update availability ```js if (!confirm || dryRun) { console.log(JSON.stringify({ action: cmd, plan, confirmRequired: !confirm }, null, 2)); if (!confirm) return; } const { data } = await hostexRequest({ method: 'POST', path: '/availabilities', json: plan }); console.log(JSON.stringify(data, null, 2)); return; ``` ### Technical Analysis The preview condition is entered when either confirmation is ...[truncated 2590 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make dry-run mode unconditionally non-mutating in every write branch: ```js if (dryRun || !confirm) { console.log(JSON.stringify({ action: cmd, plan, confirmRequired: !confirm }, null, 2)); return; } ``` 2. Prefer a centralized helper so all write commands use identical safety behavior: ```js function previewOrAbort({ cmd, plan, confirm, dryRun }) { if (dryRun || !confirm) { console.log(JSON.stringify({ action: cmd, plan, dryRun, confirmRequired: !confirm }, null, 2)); return true; } return false; } ``` 3. Explicitly reject conflicting flags if the intended CLI contract does not allow both: ```js if (dryRun && confirm) { throw new Error('--dry-run cannot be combined with --confirm'); } ``` 4. Add regression tests for each write command covering: - No `--confirm`: no network request - `--dry-run` without `--confirm`: no network request - `--dry-run --confirm`: no network request - `--confirm` without `--dry-run`: exactly one expected network request 5. Mock `hostexRequest()` in tests and assert that it is never called when dry-run mode is enabled. 6. Include an explicit `"executed": false` field in preview output so automation can reliably distinguish a preview from a completed operation. 7. Preserve the separate `HOSTEX_ALLOW_WRITES=true` gate and continue requiring explicit confirmation for all actual mutations. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (16)

Credential Access

High
Category
Privilege Escalation
Content
"/oauth/authorizations": {
      "post": {
        "summary": "Obtain/Refresh Tokens",
        "description": "This endpoint is used to obtain a new access token using various OAuth 2.0 grant types or refresh an existing token.",
        "operationId": "obtain-token",
        "tags": [
          "OAuth"
Confidence
95% confidence
Finding
The token issuance endpoint allows obtaining new access tokens and therefore enables credential material to be created or refreshed through the skill. In this context, that is dangerous because the skill otherwise centers on PAT-backed API use, so exposing token minting materially increases abuse potential and credential-handling risk.

Credential Access

High
Category
Privilege Escalation
Content
},
                  "refresh_token": {
                    "type": "string",
                    "description": "The refresh token used to obtain a new access token. Required if 'grant_type' is 'refresh_token'."
                  }
                }
              }
Confidence
94% confidence
Finding
The schema explicitly accepts a refresh_token to obtain a new access token, meaning the skill can process long-lived credential material capable of renewing access. That makes compromise or misuse more severe because a leaked refresh token may extend access well beyond a single session.

Credential Access

High
Category
Privilege Escalation
Content
"properties": {
                            "access_token": {
                              "type": "string",
                              "description": "The newly issued access token to be used for authorized API requests."
                            },
                            "refresh_token": {
                              "type": "string",
Confidence
93% confidence
Finding
Returning an access_token in the response means the skill can surface live bearer credentials to the calling environment. In an agent ecosystem, this is sensitive because tokens may be logged, cached, summarized, or inadvertently disclosed to users or other tools.

Credential Access

High
Category
Privilege Escalation
Content
},
                            "refresh_token": {
                              "type": "string",
                              "description": "A token that can be used to obtain a new access token once the current one expires."
                            },
                            "expires_in": {
                              "type": "integer",
Confidence
93% confidence
Finding
Returning a refresh_token is especially sensitive because it can be used to continuously obtain new access tokens after expiry. Exposure in a skill response greatly increases persistence of compromise compared with a short-lived token alone.

Credential Access

High
Category
Privilege Escalation
Content
},
                            "expires_in": {
                              "type": "integer",
                              "description": "The duration in seconds until the access token expires."
                            }
                          },
                          "required": [
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
"securitySchemes": {
      "HostexAccessToken": {
        "type": "apiKey",
        "description": "Access token to authenticate the request.",
        "name": "Hostex-Access-Token",
        "in": "header"
      }
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill documents and enables network-capable operations against the Hostex API but does not declare an explicit tool scope such as allowed-tools or permissions. That creates a governance gap: a host agent may expose outbound network access more broadly than intended, making it easier for the skill to perform API calls without clear policy boundaries or review. The write guardrails in the text help operationally, but they do not replace explicit capability scoping.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The reservation and conversation endpoints expose sensitive guest data including names, phone numbers, email addresses, ID details, lock codes, check-in guide URLs, and message content, yet the skill description does not foreground privacy or sensitivity constraints. In an agent context, this increases the chance of over-collection, indiscriminate display, or disclosure of guest PII and access data to an unauthorized requester.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The skill is described as PAT-based property and reservation management with optional writes, but the OpenAPI spec also exposes OAuth token issuance and revocation endpoints that handle client credentials and tokens. This expands the skill's authority beyond its stated purpose and could enable unnecessary credential handling or account-linking actions if surfaced to an agent or user without strong scoping.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
Including OAuth flows that require client_id and client_secret is unjustified for a skill positioned around PAT-based API access. Client-secret handling introduces a materially more sensitive credential class than a user PAT and creates risk of secret collection, storage, exfiltration, or misuse by downstream tooling.

External Transmission

Medium
Category
Data Exfiltration
Content
import process from 'node:process';

const DEFAULT_BASE_URL = 'https://api.hostex.io/v3';

export function getEnv(name, fallback = undefined) {
  const v = process.env[name];
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
import process from 'node:process';

const DEFAULT_BASE_URL = 'https://api.hostex.io/v3';

export function getEnv(name, fallback = undefined) {
  const v = process.env[name];
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
import process from 'node:process';

const DEFAULT_BASE_URL = 'https://api.hostex.io/v3';

export function getEnv(name, fallback = undefined) {
  const v = process.env[name];
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
export function buildUrl(path, query = {}) {
  const base = getEnv('HOSTEX_BASE_URL', DEFAULT_BASE_URL);

  // Important: preserve base path segments (e.g. https://api.hostex.io/v3)
  // new URL('/room_types', 'https://api.hostex.io/v3') would drop /v3.
  const baseUrl = new URL(base.endsWith('/') ? base : `${base}/`);
  const rel = path.startsWith('/') ? path.slice(1) : path;
Confidence
83% confidence
Finding
The client allows HOSTEX_BASE_URL to override the default API endpoint with no validation, and hostexRequest always attaches the Hostex access token as a header. If an attacker can influence environment variables or deployment configuration, they can redirect requests and exfiltrate the API token and request data to an arbitrary server.

External Transmission

Medium
Category
Data Exfiltration
Content
const base = getEnv('HOSTEX_BASE_URL', DEFAULT_BASE_URL);

  // Important: preserve base path segments (e.g. https://api.hostex.io/v3)
  // new URL('/room_types', 'https://api.hostex.io/v3') would drop /v3.
  const baseUrl = new URL(base.endsWith('/') ? base : `${base}/`);
  const rel = path.startsWith('/') ? path.slice(1) : path;
  const u = new URL(rel, baseUrl);
Confidence
83% confidence
Finding
Creating the request URL from an unvalidated base URL enables server redirection under attacker-controlled configuration, while authenticated requests include the Hostex-Access-Token header. This turns a normal API client into a credential exfiltration primitive if the base URL is changed to a malicious endpoint.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The CLI dumps full API responses for reservations, conversations, and reviews directly to stdout, which can expose guest PII, booking details, and message contents to terminals, shell history capture, logs, or downstream tooling. In this skill context, those endpoints are expected to contain sensitive hospitality data, so unrestricted full-output behavior increases the chance of accidental disclosure even though it is not overtly malicious.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/hostex-client.mjs:6