Back to skill

Security audit

Basecamp CLI

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a legitimate Basecamp CLI, but it handles account tokens and live Basecamp data in ways that should be reviewed before installation.

Review this before installing if the Basecamp account contains sensitive business or personal data. Use a non-production Basecamp account first, keep BASECAMP_CLIENT_SECRET and tokens out of shared terminals and logs, revoke tokens if exposed, and be aware that commands can archive projects, create messages, send chat lines, and print people/profile data.

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

Warning
Location
src/lib/auth.ts:169
Finding

OAuth Credentials and Tokens Transmitted in URL Query Parameters

Content
View full analysis

Vulnerability Details

File Location: src/lib/auth.ts:169-178 and src/lib/auth.ts:202-209
Vulnerability Type: Sensitive information exposed through request URLs
Risk Level: Medium

Vulnerable Code

ts
const response = await got.post(`${OAUTH_BASE}/authorization/token`, {
  searchParams: {
    type: 'web_server',
    client_id: clientId,
    client_secret: clientSecret,
    redirect_uri: redirectUri,
    code,
    code_verifier: codeVerifier
  }
}).json<{ access_token: string; refresh_token: string; expires_in: number }>();
ts
const response = await got.post(`${OAUTH_BASE}/authorization/token`, {
  searchParams: {
    type: 'refresh',
    client_id: clientId,
    client_secret: clientSecret,
    refresh_token: currentTokens.refresh_token
  }
}).json<{ access_token: string; expires_in: number }>();

Technical Analysis

The OAuth token exchange and refresh requests use Got's searchParams option. This places the OAuth client secret, authorization code, PKCE verifier, and refresh token in the request URL query string rather than in the POST body.

HTTPS encrypts the request in transit, and the destination is the expected official endpoint at https://launchpad.37signals.com. Therefore, this behavior is not evidence of malicious credential exfiltration and is functionally required for authentication. However, placing secrets in URLs unnecessarily increases their exposure because complete URLs may be captured by:

  • Forward or reverse proxies
  • HTTP diagnostic and tracing systems
  • Application performance monitoring tools
  • Exception and debug logs
  • Network security appliances
  • Request-history or telemetry systems

OAuth credentials should be submitted in an application/x-www-form-urlencoded request body or through the authorization mechanism required by the OAuth provider. Sensitive parameters should not appear in request URLs.

Att

...[truncated 1515 chars]

Remediation
View remediation

Remediation Suggestions

  1. Replace searchParams with Got's form option so OAuth parameters are encoded in the POST body:

    ts
    const response = await got.post(`${OAUTH_BASE}/authorization/token`, {
      form: {
        type: 'web_server',
        client_id: clientId,
        client_secret: clientSecret,
        redirect_uri: redirectUri,
        code,
        code_verifier: codeVerifier
      }
    }).json<TokenResponse>();
    
  2. Apply the same change to the refresh-token request:

    ts
    const response = await got.post(`${OAUTH_BASE}/authorization/token`, {
      form: {
        type: 'refresh',
        client_id: clientId,
        client_secret: clientSecret,
        refresh_token: currentTokens.refresh_token
      }
    }).json<RefreshResponse>();
    
  3. If supported by the OAuth provider, authenticate the client through an appropriate authorization header instead of transmitting the client secret as a request parameter.

  4. Review HTTP error handling and diagnostic logging to ensure request URLs, bodies, and headers containing OAuth credentials are redacted.

  5. Rotate the OAuth client secret and revoke existing refresh tokens if URLs containing these values may already have been retained in logs.

T09 · Insecure Skill Coding Practices

Warning
Location
src/lib/config.ts:13
Finding

Token Encryption Relies on a Predictable Machine-Derived Key and Unauthenticated AES-CBC

Content
View full analysis

Vulnerability Details

File Location: src/lib/config.ts:13-52
Vulnerability Type: Inadequate protection of locally stored authentication tokens
Risk Level: Medium

Vulnerable Code

ts
function getEncryptionKey(): Buffer {
  const machineId = `${os.hostname()}-${os.userInfo().username}-basecamp-cli-tokens`;
  return crypto.createHash('sha256').update(machineId).digest();
}
ts
function encrypt(text: string): string {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv('aes-256-cbc', getEncryptionKey(), iv);
  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  return iv.toString('hex') + ':' + encrypted;
}
ts
function decrypt(text: string): string {
  try {
    const [ivHex, encrypted] = text.split(':');
    if (!ivHex || !encrypted) {
      throw new Error('Invalid encrypted format');
    }
    const iv = Buffer.from(ivHex, 'hex');
    const decipher = crypto.createDecipheriv('aes-256-cbc', getEncryptionKey(), iv);
    let decrypted = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
  } catch {
    // If decryption fails, the data might be in plain text (legacy)
    // Return as-is to allow migration
    return text;
  }
}

Technical Analysis

The encryption key is deterministically derived from the hostname, operating-system username, and a public constant. These values are identifiers, not secrets. An attacker who obtains the configuration file and can determine or guess the hostname and username can reproduce the AES key and decrypt the stored access and refresh tokens.

AES-256-CBC provides confidentiality but no built-in integrity or authenticity. The implementation does not calculate a message authentication code, so it cannot reliably detect ciphertext modification.

In addition, the decryption function treats ...[truncated 1849 chars]

Remediation
View remediation

Remediation Suggestions

  1. Store access and refresh tokens in the operating system's credential manager, such as macOS Keychain, Windows Credential Manager, or a Linux Secret Service implementation.

  2. If encrypted file storage is unavoidable:

    • Generate a cryptographically random encryption key.
    • Protect that key separately through an operating-system credential store or a user-provided secret.
    • Do not derive it solely from public machine attributes.
    • Enforce restrictive owner-only permissions on both token and key files.
  3. Replace AES-CBC with an authenticated encryption mode such as AES-256-GCM or ChaCha20-Poly1305. Store the nonce, authentication tag, ciphertext, and a format version.

  4. Fail closed when authenticated decryption fails. Do not automatically return malformed or undecryptable encrypted data as plaintext.

  5. Implement an explicit, one-time legacy migration path:

    • Detect a clearly versioned legacy format.
    • Read and immediately re-encrypt legacy plaintext tokens.
    • Remove plaintext compatibility after the migration period.
  6. On deployment of the corrected storage mechanism, revoke or rotate existing refresh tokens if token files may have been copied or exposed.

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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (20)

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 188)May include surrounding context.

md
| `BASECAMP_CLIENT_ID` | OAuth Client ID |
| `BASECAMP_CLIENT_SECRET` | OAuth Client Secret |
| `BASECAMP_REDIRECT_URI` | OAuth Redirect URI (default: `http://localhost:9292/callback`) |
| `BASECAMP_ACCESS_TOKEN` | Access token (alternative to OAuth flow) |

## License

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
80% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: picomatch==4.0.3 — 2 advisory(ies): CVE-2026-33672 (Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Mat); CVE-2026-33671 (Picomatch has a ReDoS vulnerability via extglob quantifiers)

High
Category
Supply Chain
Confidence
80% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: rollup==4.56.0 — 1 advisory(ies): CVE-2026-27606 (Rollup 4 has Arbitrary File Write via Path Traversal)

High
Category
Supply Chain
Confidence
80% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

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 · src/lib/config.ts (reported line 13)May include surrounding context.

ts
/**
 * Generate a machine-specific encryption key
 * This provides better security than plain text while not requiring external dependencies
 * Note: For maximum security, consider using system keychain (macOS Keychain, Windows Credential Manager)
 */
function getEncryptionKey(): Buffer {
  const machineId = `${os.hostname()}-${os.userInfo().username}-basecamp-cli-tokens`;

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 · src/lib/config.ts (reported line 13)May include surrounding context.

ts
/**
 * Generate a machine-specific encryption key
 * This provides better security than plain text while not requiring external dependencies
 * Note: For maximum security, consider using system keychain (macOS Keychain, Windows Credential Manager)
 */
function getEncryptionKey(): Buffer {
  const machineId = `${os.hostname()}-${os.userInfo().username}-basecamp-cli-tokens`;

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The README instructs users to configure OAuth client credentials and use commands that can create, update, archive, and send content in live Basecamp accounts, but it does not warn about secret handling, shell history exposure, or the risk of modifying production data. In an agent skill context, documentation is often used as operational guidance, so missing safety guidance can lead to credential leakage or unintended changes to real projects.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill references use of an environment variable for a sensitive secret (BASECAMP_CLIENT_SECRET) but does not declare any explicit tool scope such as permissions or allowed-tools. That creates an ambiguity gap: an agent or runtime may expose broader environment access than intended, increasing the risk of secret access or leakage during skill execution. In a skill that performs OAuth setup and handles API credentials, missing scope declarations are more concerning because secrets are central to normal operation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This code performs authenticated API calls to retrieve people records and later prints personally identifiable information such as names, email addresses, bios, locations, and time zones. While the command descriptions explain functionality, they do not warn that sensitive profile data will be fetched and displayed, including in machine-readable JSON output.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The manifest description limits the skill to managing Basecamp projects, to-dos, messages, and campfires, but this entrypoint also registers auth, accounts/account, people, and me commands. Those capabilities extend the operational scope beyond what the manifest says the skill is for.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: ajv==8.17.1 — 1 advisory(ies): CVE-2025-69873 (ajv has ReDoS when using `$data` option)

Low
Category
Supply Chain
Confidence
60% confidence
Finding

Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

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 36)May include surrounding context.

json
"url": "git+https://github.com/emredoganer/basecamp-cli.git"
  },
  "dependencies": {
    "chalk": "^5.3.0",
    "cli-table3": "^0.6.5",
    "commander": "^12.1.0",
    "conf": "^13.0.1",

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 37)May include surrounding context.

json
},
  "dependencies": {
    "chalk": "^5.3.0",
    "cli-table3": "^0.6.5",
    "commander": "^12.1.0",
    "conf": "^13.0.1",
    "got": "^14.4.5",

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 38)May include surrounding context.

json
"dependencies": {
    "chalk": "^5.3.0",
    "cli-table3": "^0.6.5",
    "commander": "^12.1.0",
    "conf": "^13.0.1",
    "got": "^14.4.5",
    "open": "^10.1.0"

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 39)May include surrounding context.

json
"chalk": "^5.3.0",
    "cli-table3": "^0.6.5",
    "commander": "^12.1.0",
    "conf": "^13.0.1",
    "got": "^14.4.5",
    "open": "^10.1.0"
  },

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 40)May include surrounding context.

json
"cli-table3": "^0.6.5",
    "commander": "^12.1.0",
    "conf": "^13.0.1",
    "got": "^14.4.5",
    "open": "^10.1.0"
  },
  "devDependencies": {

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 41)May include surrounding context.

json
"commander": "^12.1.0",
    "conf": "^13.0.1",
    "got": "^14.4.5",
    "open": "^10.1.0"
  },
  "devDependencies": {
    "@types/node": "^22.10.5",

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 44)May include surrounding context.

json
"open": "^10.1.0"
  },
  "devDependencies": {
    "@types/node": "^22.10.5",
    "tsup": "^8.3.5",
    "typescript": "^5.7.2"
  },

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 45)May include surrounding context.

json
},
  "devDependencies": {
    "@types/node": "^22.10.5",
    "tsup": "^8.3.5",
    "typescript": "^5.7.2"
  },
  "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 46)May include surrounding context.

json
"devDependencies": {
    "@types/node": "^22.10.5",
    "tsup": "^8.3.5",
    "typescript": "^5.7.2"
  },
  "engines": {
    "node": ">=18.0.0"

Static analysis

No suspicious patterns detected.