Back to skill

Security audit

Keyapi Threads

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but its helper scripts persist an API token in shell startup files and can upload arbitrary local files to KeyAPI if invoked that way.

Install only if you are comfortable with a local helper modifying shell profile files and storing a KeyAPI token there. Prefer setting KEYAPI_TOKEN only for the current shell/session, avoid passing tokens on the command line, and do not let the skill use --image-file, --body-file, or --query-file on sensitive local files unless you intentionally want that content sent to KeyAPI.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/configure-keyapi-auth.mjs:119
Finding

KeyAPI Bearer Token Persisted in Plaintext Shell Profiles

Content
View full analysis

Vulnerability Details

File Location: scripts/configure-keyapi-auth.mjs:119-135, 192-222
Vulnerability Type: Plaintext credential storage and unsafe credential input
Risk Level: Medium

Vulnerable Code

js
function buildManagedBlock(values, profileKind) {
  if (profileKind === "powershell") {
    return [
      managedBlockStart,
      `$env:KEYAPI_TOKEN = ${powershellQuote(values.token)}`,
      managedBlockEnd,
      ""
    ].join("\n");
  }

  return [
    managedBlockStart,
    `export KEYAPI_TOKEN=${shellQuote(values.token)}`,
    managedBlockEnd,
    ""
  ].join("\n");
}
js
async function collectCredentials(args) {
  const token = args.token ? requireRealValue("KEYAPI_TOKEN", args.token) : undefined;

  if (token) {
    return { token };
  }

  const rl = createInterface({ input, output });

  try {
    output.write("KeyAPI setup will save KEYAPI_TOKEN into your shell environment variables.\n");
    const enteredToken = await rl.question("Paste KEYAPI_TOKEN: ");

    return {
      token: requireRealValue("KEYAPI_TOKEN", enteredToken)
    };
  } finally {
    rl.close();
  }
}

function writeProfile(profilePath, credentials) {
  ensureParentDir(profilePath);
  const profileKind = detectProfileKind(profilePath);
  const existing = fs.existsSync(profilePath) ? fs.readFileSync(profilePath, "utf8") : "";
  const cleaned = stripManagedBlock(existing);
  const block = buildManagedBlock(credentials, profileKind);
  const next = cleaned ? `${cleaned}\n\n${block}` : block;
  fs.writeFileSync(profilePath, next);
  return profileKind;
}

The associated documentation also recommends passing the token through a command-line argument:

bash
node scripts/configure-keyapi-auth.mjs --token "your_keyapi_token"

Technical Analysis

The setup helper permanently writes the KeyAPI bearer token into a standard shell startup ...[truncated 1954 chars]

Remediation
View remediation

Remediation Suggestions

  1. Store the token in an operating-system credential manager, such as Windows Credential Manager, macOS Keychain, or a Linux Secret Service implementation.
  2. If a file-based fallback is required, use a dedicated credentials file rather than a shell startup file. Create it with owner-only permissions and verify the resulting mode before writing the token.
  3. Replace readline.question with a hidden-input implementation that disables terminal echo while the token is entered.
  4. Remove or deprecate --token. Accept non-interactive credentials through protected standard input or a documented secret-manager integration.
  5. If command-line token input remains supported, display a clear warning about process-list and shell-history exposure.
  6. Add an explicit removal command that deletes the managed credential block without affecting unrelated profile content.
  7. Document token rotation procedures and advise users to rotate any token entered into chat, exposed in history, or committed to a repository.
  8. Preserve the existing rule that tokens must never be printed or included in API response output.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/keyapi-api.mjs:318
Finding

Unrestricted Local File Read and Transmission Through Image Upload Option

Content
View full analysis

Vulnerability Details

File Location: scripts/keyapi-api.mjs:318-344, 456-475
Vulnerability Type: Excessive local file access and unintended data disclosure
Risk Level: Medium

Vulnerable Code

js
async function loadBody(args) {
  if (args.body && args.bodyFile) {
    throw new Error("Use either --body or --body-file, not both.");
  }

  let body;
  if (args.bodyFile) {
    body = parseJsonFlag("--body-file", await readFile(args.bodyFile, "utf8"));
  } else {
    body = parseJsonFlag("--body", args.body);
  }

  if (args.imageFile) {
    const imageField = args.imageField || "image_base64";
    if (body === undefined) {
      body = {};
    }
    if (!body || typeof body !== "object" || Array.isArray(body)) {
      throw new Error("--image-file can only be merged into a JSON object body.");
    }
    body[imageField] = (await readFile(args.imageFile)).toString("base64");
  }

  const imageField = args.imageField || "image_base64";
  if (body && typeof body === "object" && !Array.isArray(body) && typeof body[imageField] === "string") {
    body[imageField] = stripDataUrlPrefix(body[imageField]);
  }

  return body;
}
js
const headers = {
  Accept: "application/json",
  Authorization: authHeader
};

const options = {
  method: args.method,
  headers,
  signal: args.timeoutMs > 0 ? AbortSignal.timeout(args.timeoutMs) : undefined
};

if (body !== undefined && args.method !== "GET") {
  headers["Content-Type"] = "application/json";
  options.body = JSON.stringify(body);
}

const response = await fetch(url, options);

Technical Analysis

The --image-file option accepts any filesystem path readable by the Agent process. The helper does not verify that the selected file is actually an image, belongs to the current workspace, was explicitly selected by the user, or is within an approved size limit. It reads the entire file, encodes ...[truncated 2030 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove --image-file from this Threads Skill unless a verified Threads endpoint requires local image input.
  2. Require explicit, per-file user confirmation before reading and transmitting any local file.
  3. Restrict uploads to files inside an approved workspace or user-selected upload directory.
  4. Resolve canonical paths and reject path traversal, symbolic links, device files, shell profiles, credential directories, SSH directories, and other known-sensitive locations.
  5. Validate content using file signatures rather than relying only on extensions. Permit only documented image formats.
  6. Enforce conservative file-size limits before reading the file into memory.
  7. Display the canonical source path, destination origin, API route, and payload purpose before transmission.
  8. Apply equivalent sensitivity checks to --body-file and --query-file, especially when their content will be transmitted.
  9. Introduce an endpoint allowlist identifying which verified Threads routes may accept uploaded binary content.
  10. Add tests demonstrating rejection of non-image files, paths outside the approved directory, symlinks, oversized inputs, and sensitive system or credential files.
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (23)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
2. Check auth with `node scripts/configure-keyapi-auth.mjs --status` when script execution is available and setup state is unknown; treat `authStatus: "availabl

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
2. Check auth with `node scripts/configure-keyapi-auth.mjs --status` when script execution is available and setup state is unknown; treat `authStatus: "availabl

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
2. Check auth with `node scripts/configure-keyapi-auth.mjs --status` when script execution is available and setup state is unknown; treat `authStatus: "availabl

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
2. Check auth with `node scripts/configure-keyapi-auth.mjs --status` when script execution is available and setup state is unknown; treat `authStatus: "availabl

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
2. Check auth with `node scripts/configure-keyapi-auth.mjs --status` when script execution is available and setup state is unknown; treat `authStatus: "availabl

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
9. Resolve current docs with `node scripts/search-keyapi-docs.mjs --query "<semantic entity action>" --resolve` when the helper script is available; otherwise o

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
9. Resolve current docs with `node scripts/search-keyapi-docs.mjs --query "<semantic entity action>" --resolve` when the helper script is available; otherwise o

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
9. Resolve current docs with `node scripts/search-keyapi-docs.mjs --query "<semantic entity action>" --resolve` when the helper script is available; otherwise o

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
12. Prefer `node scripts/keyapi-api.mjs` for live REST calls when script execution is available. Use the host's HTTP client only when scripts are unavailable or

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
12. Prefer `node scripts/keyapi-api.mjs` for live REST calls when script execution is available. Use the host's HTTP client only when scripts are unavailable or

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
12. Prefer `node scripts/keyapi-api.mjs` for live REST calls when script execution is available. Use the host's HTTP client only when scripts are unavailable or

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
12. Prefer `node scripts/keyapi-api.mjs` for live REST calls when script execution is available. Use the host's HTTP client only when scripts are unavailable or

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
12. Prefer `node scripts/keyapi-api.mjs` for live REST calls when script execution is available. Use the host's HTTP client only when scripts are unavailable or

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
12. Prefer `node scripts/keyapi-api.mjs` for live REST calls when script execution is available. Use the host's HTTP client only when scripts are unavailable or

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
12. Prefer `node scripts/keyapi-api.mjs` for live REST calls when script execution is available. Use the host's HTTP client only when scripts are unavailable or

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
12. Prefer `node scripts/keyapi-api.mjs` for live REST calls when script execution is available. Use the host's HTTP client only when scripts are unavailable or

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
12. Prefer `node scripts/keyapi-api.mjs` for live REST calls when script execution is available. Use the host's HTTP client only when scripts are unavailable or

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
12. Prefer `node scripts/keyapi-api.mjs` for live REST calls when script execution is available. Use the host's HTTP client only when scripts are unavailable or

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
12. Prefer `node scripts/keyapi-api.mjs` for live REST calls when script execution is available. Use the host's HTTP client only when scripts are unavailable or

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
a different current working directory, resolve `scripts/...` relative to this `SKILL.md`. For large JSON bodies, especially base64 image payloads, prefer `--bo

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code detects and targets user shell startup files such as .zshrc, .bashrc, .profile, or PowerShell profile files, giving the skill the ability to alter future shell behavior and persist data. In the context of a data-discovery skill, modifying startup files is over-privileged and increases risk because any bug or future change could inject unintended commands or expose secrets on every shell launch.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script persistently writes KEYAPI_TOKEN into the user's shell startup/profile file and instructs the user to reload it, extending the skill's reach beyond transient API use into persistent host configuration. Even if intended for convenience, this creates lasting secret exposure in plaintext and modifies execution environment state in a way not required for a Threads analysis skill.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script reads KEYAPI_TOKEN not only from the current process environment but also by inspecting the user's shell profile files and extracting a managed block. That expands the skill's access beyond its stated API-calling purpose into local credential discovery, which increases the blast radius if the skill is misused, modified, or invoked in an unexpected context. In an agent setting, reading persisted secrets from profile files is especially sensitive because it bypasses the narrower expectation that only explicitly provided runtime credentials are used.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.env_credential_access, suspicious.secret_argv_exposure

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/keyapi-api.mjs:127

Instructions pass high-value credentials through process argv.

Critical
Code
suspicious.secret_argv_exposure
Location
references/setup-and-auth.md:42