Back to skill

Security audit

HubSpot by altf1be

Security checks for vulnerabilities and agentic risk

Overview

This is a legitimate HubSpot CLI, but it deserves review because it can change sensitive CRM/business data and caches OAuth access tokens locally without explicit file-permission hardening.

Install only if you are comfortable giving this skill HubSpot access to the specific objects you need. Prefer a least-privilege Private App token, avoid broad write scopes unless required, and be cautious with OAuth mode because a temporary access token is cached locally in plaintext. Review and remove ~/.cache/openclaw/hubspot-token.json when no longer needed.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/hubspot.mjs:106
Finding

OAuth Access Token Cached Without Restrictive File Permissions

Content
View full analysis

Vulnerability Details

File Location: scripts/hubspot.mjs, lines 106–113
Vulnerability Type: Plaintext sensitive credential stored with umask-dependent permissions
Risk Level: Medium

Vulnerable Code

js
// Cache to disk
try {
  const cacheDir = dirname(OAUTH_TOKEN_PATH);
  if (!existsSync(cacheDir)) mkdirSync(cacheDir, { recursive: true });
  writeFileSync(OAUTH_TOKEN_PATH, JSON.stringify({
    access_token: _cachedAccessToken,
    expires_at: _tokenExpiresAt,
  }), 'utf8');
} catch {

The destination is defined as:

js
const OAUTH_TOKEN_PATH = resolve(
  homedir(),
  '.cache',
  'openclaw',
  'hubspot-token.json'
);

Technical Analysis

The refreshed OAuth bearer token is stored in plaintext without specifying a restrictive file mode. Consequently, the file's permissions depend on the process umask. On a POSIX system with a permissive or commonly used umask, the resulting file may be readable by other local users or processes.

The cache directory is also created without an explicit 0700 mode. The code does not verify the cache file's ownership, permissions, or file type before subsequently reading it. This weakens local credential isolation and may also expose the cache operation to symlink-based file redirection in environments where an attacker can manipulate the cache path.

Caching an access token is consistent with the declared OAuth auto-refresh functionality, but storing it without explicit access controls exceeds the minimum local exposure necessary for that functionality.

Attack Path

  1. A user configures OAuth mode and invokes a HubSpot command.
  2. The CLI submits the client ID, client secret, and refresh token to HubSpot's official OAuth endpoint.
  3. HubSpot returns a temporary access token.
  4. The CLI writes that token to ~/.cache/openclaw/hubspot-token.json without explicitly setting mode 0600.
  5. Another local account or compromised process reads the cache if effective filesystem permissions allo ...[truncated 934 chars]
Remediation
View remediation

Remediation Suggestions

  1. Create the private cache directory with owner-only permissions:
js
mkdirSync(cacheDir, { recursive: true, mode: 0o700 });
  1. Write the token file with explicit owner-only permissions:
js
writeFileSync(
  OAUTH_TOKEN_PATH,
  JSON.stringify({
    access_token: _cachedAccessToken,
    expires_at: _tokenExpiresAt,
  }),
  { encoding: 'utf8', mode: 0o600 }
);
  1. Explicitly apply chmodSync(OAUTH_TOKEN_PATH, 0o600) when replacing an existing file, because the mode option does not necessarily correct permissions on an already existing file.

  2. Use an atomic write process:

    • Create a temporary file in the same private directory using exclusive creation.
    • Set mode 0600.
    • Write and flush the content.
    • Rename it atomically to the final cache path.
  3. Before reading an existing cache, use lstatSync to reject symbolic links and non-regular files. Verify that the file is owned by the current effective user and is not accessible by group or other users.

  4. Prefer an operating-system credential store or keychain when available. If plaintext caching remains necessary, document its location, permissions, expiration behavior, and secure deletion procedure.

  5. Ensure refreshed tokens are revoked or invalidated where supported after suspected local compromise.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (12)

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 · README.md (reported line 51)May include surrounding context.

md
npm install

# 3. Configure
cp .env.example .env
# Edit .env with your HubSpot Private App token

# 4. Use

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 · README.md (reported line 63)May include surrounding context.

md
## Setup

1. Go to HubSpot Settings > Integrations > Private Apps
2. Create a Private App and copy the access token
3. Copy `.env.example` to `.env` and fill in:
   - `HUBSPOT_ACCESS_TOKEN` — your Private App token
4. Run `npm install`

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 · README.md (reported line 52)May include surrounding context.

md
import { config } from 'dotenv';
import { Command } from 'commander';

// ── Load .env ────────────────────────────────────────────────────────────────

config();

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/hubspot.mjs (reported line 24)May include surrounding context.

js
import { config } from 'dotenv';
import { Command } from 'commander';

// ── Load .env ────────────────────────────────────────────────────────────────

config();

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill advertises and operationally requires access to environment variables and networked HubSpot APIs, but it does not declare any tool scope such as permissions or allowed-tools. That omission weakens user awareness and policy enforcement around a skill that can read and modify sensitive CRM, conversations, marketing, and CMS data, increasing the chance of overbroad or unintended execution.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill description presents broad HubSpot functionality without warning that it can access highly sensitive CRM records, conversations, emails, tickets, and other business data. In practice, this can mislead users into invoking the skill without appreciating the privacy and compliance implications of exposing customer and internal communications data to the agent runtime.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
87% confidence
Finding

The skill explicitly states that OAuth tokens are cached in ~/.cache/openclaw/hubspot-token.json, which introduces session persistence for credentials beyond a single run. Persisted refresh/access tokens can be recovered by other local processes or users if file permissions, shared environments, backups, or workstation hygiene are weak, enabling continued unauthorized API access.

Content

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

md
# Read contact details
node {baseDir}/scripts/hubspot.mjs contacts read --id 123

# Create a contact
node {baseDir}/scripts/hubspot.mjs contacts create --email "jane@example.com" --firstname Jane --lastname Doe

# Update a contact

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · scripts/hubspot.mjs (reported line 84)May include surrounding context.

js
const clientSecret = env('HUBSPOT_CLIENT_SECRET');
  const refreshToken = env('HUBSPOT_REFRESH_TOKEN');

  const resp = await fetch('https://api.hubapi.com/oauth/v1/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% 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 · scripts/hubspot.mjs (reported line 84)May include surrounding context.

js
const clientSecret = env('HUBSPOT_CLIENT_SECRET');
  const refreshToken = env('HUBSPOT_REFRESH_TOKEN');

  const resp = await fetch('https://api.hubapi.com/oauth/v1/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

This markdown checklist includes a natural-language requirement that the README author line include specific country flag symbols. That imposes a locale/cultural presentation choice rather than offering flexibility or documenting why it is required, which matches the policy concern about forcing a specific language/locale style without opt-in.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 33)May include surrounding context.

json
"url": "https://github.com/ALT-F1-OpenClaw/openclaw-skill-hubspot-by-altf1be.git"
  },
  "dependencies": {
    "commander": "^12.0.0",
    "dotenv": "^16.0.0"
  },
  "engines": {

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 34)May include surrounding context.

json
},
  "dependencies": {
    "commander": "^12.0.0",
    "dotenv": "^16.0.0"
  },
  "engines": {
    "node": ">=18.0.0"