Back to skill

Security audit

Claw Apply

Security checks for vulnerabilities and agentic risk

Overview

This is not clearly malicious, but it can automatically submit real job applications and share or retain sensitive candidate data with weak approval boundaries.

Install only if you are comfortable with an agent using your LinkedIn/Wellfound sessions to submit applications, sending candidate data to Anthropic and Telegram, and storing reusable answers locally. Start with preview mode, set a low max_applications_per_run, review answers.json regularly, disable crons until tested, and protect .env and data logs as sensitive files.

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)

other

Error
Location
lib/form_filler.mjs:232
Finding
Indirect prompt injection through job application fields can disclose stored candidate answers<![CDATA[ ## Vulnerability Details **File Location**: `lib/form_filler.mjs:232-276, 573-575, 594-596, 619-620, 697-700`; `lib/apply/easy_apply.mjs:208-220, 278-297` **Vulnerability Type**: Indirect prompt injection with automatic external submission **Risk Level**: High ### Vulnerable Code ```js async aiAnswerFor(label, opts = {}) { if (!this.apiKey) return null; const savedAnswers = this.answers.map(a => `Q: "${a.pattern}" -> A: "${a.answer}"`).join('\n'); const optionsHint = opts.options?.length ? `\nAvailable options: ${opts.options.join(', ')}` : ''; const systemPrompt = `You are helping a job candidate fill out application forms. You have access to their profile and previously answered questions. Rules: - If this question is a variation of a previously answered question, return the SAME answer - For yes/no or multiple choice, return ONLY the exact option text - For short-answer fields, be brief and direct (1 line) - Use first person - Never make up facts - Just the answer text — no preamble, no explanation, no quotes`; const userPrompt = `Candidate: ${this.profile.name?.first} ${this.profile.name?.last} Location: ${this.profile.location?.city}, ${this.profile.location?.state} Years experience: ${this.profile.years_experience || 7} Applying for: ${this.jobContext.title || 'a role'} at ${this.jobContext.company || 'a company'} Previously answered questions: ${savedAnswers || '(none yet)'} New question: "${label}"${optionsHint} Answer:`; try { const res = await fetch(ANTHROPIC_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey, 'anthropic-version': '2023-06-01', }, body: JSON.stringify({ model: 'claude-sonnet-4-6', max_tokens: 256, system: systemPrompt, messages: [{ role: 'user', content: userPrompt }], }), }); if (!res.ok) return null; const data = await res.json(); const answer = data. ...[truncated 3727 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not place the entire `answers.json` collection into every model prompt. Retrieve only one or a small number of answers that were locally matched as relevant to the current field. 2. Treat labels, placeholders, job titles, company names, and options as untrusted data. Clearly delimit them and tell the model that content inside the delimiters is data, not instructions. 3. Reject or escalate fields containing instruction-like language, requests to reveal context, encoded content, URLs, secrets, previous answers, system prompts, or unrelated data. 4. Require explicit user approval before submitting any AI-generated free-text response. Automatic submission should be limited to deterministic profile mappings and previously approved exact answers. 5. Use structured model output with a strict schema, maximum length, and field-type constraints. 6. For multiple-choice questions, only accept exact values from the page’s option allowlist. Do not use substring matching for security-sensitive questions. 7. Keep sensitive answer categories out of model context unless strictly required for the current field. 8. Mark model-generated answers separately and do not persist them as trusted reusable answers until the user confirms them. 9. Add adversarial tests covering labels that request previous answers, candidate profile data, model instructions, hidden context, or unrelated output. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
lib/filter.mjs:21
Finding
AI features transmit candidate data beyond the minimum required scope<![CDATA[ ## Vulnerability Details **File Location**: `lib/filter.mjs:21-25, 73-112`; `job_filter.mjs:208-242, 287-294`; `lib/keywords.mjs:8-56`; `lib/ai_answer.mjs:18-80` **Vulnerability Type**: Excessive disclosure of personally identifiable and employment-related information **Risk Level**: Medium ### Vulnerable Code The filtering system serializes the complete candidate profile into the Anthropic system prompt: ```js function buildSystemPrompt(jobProfile, candidateProfile) { return `You are a job relevance scorer. Score each job listing 0-10 based on how well it matches the candidate profile below. ## Candidate Profile ${JSON.stringify(candidateProfile, null, 2)} ## Target Job Profile ${JSON.stringify(jobProfile, null, 2)} ## Instructions - Use the candidate profile and target job profile above as your only criteria - Score based on title fit, industry fit, experience match, salary range, location/remote requirements, and any exclude_keywords - 10 = perfect match, 0 = completely irrelevant - If salary is unknown, do not penalize - If a posting is from a staffing agency but the role itself matches, score the role — not the agency Return ONLY a JSON object: {"score": <0-10>, "reason": "<one concise line>"}`; } ``` That complete object is loaded from the user’s profile configuration and passed to the batch API: ```js const candidateProfile = loadConfig(resolve(__dir, 'config/profile.json')); const submitted = await submitBatches( filterable, jobProfilesByTrack, candidateProfile, model, apiKey ); ``` Keyword generation also sends identifying data that is not required to produce search terms: ```js const prompt = `You are an expert job search strategist helping a candidate find the right roles on LinkedIn and Wellfound. ## Candidate Profile - Name: ${profile.name.first} ${profile.name.last} - Location: ${profile.location.city}, ${profile.location.state} (remote only) - Years experience: ${profile.years_experience} - Desired salary: $${pro ...[truncated 4694 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace whole-object serialization with an explicit per-feature allowlist. 2. For job scoring, construct a dedicated object containing only: - relevant experience and skills; - target titles and industries; - generalized location or remote preference; - compensation constraints; - required work-authorization characteristics. 3. Exclude names, email addresses, telephone numbers, local paths, social-profile URLs, and unrelated application answers from filtering requests. 4. Remove the candidate’s name from keyword-generation prompts and generalize precise location where exact location is unnecessary. 5. Do not include resume content by default. Make resume-assisted generation a separate, explicit opt-in setting. 6. When resume assistance is enabled, extract only locally selected facts relevant to the current question rather than sending the first 4,000 characters. 7. Present users with a clear field-level disclosure of what each AI feature transmits before enabling it. 8. Add tests that inspect outbound request bodies and fail if prohibited profile keys appear. 9. Apply retention and logging controls so prompts containing candidate information are not written to local logs or external observability systems. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (70)

Ae1

High
Category
analysis-evasion
Content
ls for job scoring (`lib/filter.mjs`), answer generation (`lib/ai_answer.mjs`, `lib/form_filler.mjs`), and keyword generation (`lib/keywords.mjs`). Those files
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
/**
 * env.mjs — Load .env file if present
 * Reads key=value pairs from .env and sets process.env for any missing vars.
 * Never overwrites vars already set in the environment.
 */
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
/**
 * env.mjs — Load .env file if present
 * Reads key=value pairs from .env and sets process.env for any missing vars.
 * Never overwrites vars already set in the environment.
 */
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
/**
 * env.mjs — Load .env file if present
 * Reads key=value pairs from .env and sets process.env for any missing vars.
 * Never overwrites vars already set in the environment.
 */
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
/**
 * env.mjs — Load .env file if present
 * Reads key=value pairs from .env and sets process.env for any missing vars.
 * Never overwrites vars already set in the environment.
 */
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
/**
 * env.mjs — Load .env file if present
 * Reads key=value pairs from .env and sets process.env for any missing vars.
 * Never overwrites vars already set in the environment.
 */
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
/**
 * env.mjs — Load .env file if present
 * Reads key=value pairs from .env and sets process.env for any missing vars.
 * Never overwrites vars already set in the environment.
 */
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
/**
 * env.mjs — Load .env file if present
 * Reads key=value pairs from .env and sets process.env for any missing vars.
 * Never overwrites vars already set in the environment.
 */
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
/**
 * env.mjs — Load .env file if present
 * Reads key=value pairs from .env and sets process.env for any missing vars.
 * Never overwrites vars already set in the environment.
 */
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
/**
 * env.mjs — Load .env file if present
 * Reads key=value pairs from .env and sets process.env for any missing vars.
 * Never overwrites vars already set in the environment.
 */
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
/**
 * env.mjs — Load .env file if present
 * Reads key=value pairs from .env and sets process.env for any missing vars.
 * Never overwrites vars already set in the environment.
 */
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
/**
 * env.mjs — Load .env file if present
 * Reads key=value pairs from .env and sets process.env for any missing vars.
 * Never overwrites vars already set in the environment.
 */
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
import { fileURLToPath } from 'url';

const __dir = dirname(fileURLToPath(import.meta.url));
const envPath = resolve(__dir, '../.env');

export function loadEnv() {
  if (!existsSync(envPath)) return;
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
console.log('\n✅ Data directory ready');

  // Write .env file with API keys (gitignored — never embedded in cron payloads)
  const envPath = resolve(__dir, '.env');
  const envLines = [];
  if (process.env.KERNEL_API_KEY) envLines.push(`KERNEL_API_KEY=${process.env.KERNEL_API_KEY}`);
  if (process.env.ANTHROPIC_API_KEY) envLines.push(`ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}`);
Confidence
80% confidence
Finding
The script copies sensitive API keys from process environment variables into a plaintext .env file on disk. Even with benign intent and a comment noting gitignore usage, storing long-lived credentials in a local file increases exposure through local compromise, backups, accidental inclusion, or misconfigured permissions in other environments.

Credential Access

High
Category
Privilege Escalation
Content
if (process.env.ANTHROPIC_API_KEY) envLines.push(`ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}`);
  if (envLines.length) {
    writeFileSync(envPath, envLines.join('\n') + '\n', { mode: 0o600 });
    console.log('✅ API keys written to .env (mode 600, gitignored)');
  }

  // Test Telegram
Confidence
86% confidence
Finding
This line writes collected API keys into a plaintext .env file. Although mode 600 reduces risk, secrets are still persisted on disk where they may be read by local malware, exposed in backups, or mishandled operationally, making this a real but moderate credential-handling weakness rather than overt malicious behavior.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README promotes fully automated job applications and reuse of learned answers without prominently warning that the tool may submit inaccurate, stale, or overly broad responses on the user's behalf. In this context, the skill directly automates high-stakes submissions to third-party employers, so omission of safety warnings materially increases the chance of misrepresentation, unintended disclosure, or reputational harm.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The README states that unknown application questions and AI suggestions are sent through Telegram and Claude, but it does not clearly disclose that profile, resume, job application, and potentially sensitive employment data may be transmitted to third-party services. Because this tool is explicitly designed to process applicant data at scale, the missing privacy warning makes inadvertent exposure more likely and more dangerous than in a generic chatbot integration.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill clearly automates submission of real job applications and stores learned answers in persistent files, but the opening description does not prominently warn users about these consequential actions. This can mislead users into running the skill without fully understanding that it will take irreversible external actions and retain sensitive personal/application data.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
Although the skill notes it makes Claude API calls and uses Telegram, it does not present a clear privacy warning that job listings, application responses, and potentially sensitive personal data may be transmitted to Anthropic and Telegram. Users may therefore expose resumes, contact details, compensation targets, or application answers to third-party services without informed consent.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The manifest explicitly advertises automated job applications via stealth browsers, but provides no user-facing warning or consent boundary for actions that can submit applications, interact with third-party accounts, and potentially violate platform rules. Because these are impactful external actions performed automatically, the missing disclosure materially increases the risk of unintended submissions, account misuse, and reputational harm.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill requests network, browser, filesystem, and messaging permissions and references external credentials, yet the manifest does not explain what data may be read, transmitted, or sent through third-party services. In the context of job applications, this can include resumes, personal identifiers, account session activity, and notification data, making the lack of disclosure and scope limitation a meaningful security and privacy issue.

Ssd 3

Medium
Confidence
96% confidence
Finding
The script monkey-patches stdout and stderr to tee all console output into a persistent local log file. Since later code logs unknown application questions, answer options, AI-generated answers, stack traces, job titles, and companies, this creates durable storage of sensitive application data that could be exposed to other local users, backups, or incidentally shared files.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The code sends job title, company, free-form application questions, available answer options, and AI-generated answers to Telegram. That exposes potentially sensitive employment activity and profile-derived content to a third-party messaging service and any party with access to the chat, and there is no consent gate, redaction, or minimization in this file.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script overrides stdout/stderr and persists all runtime output to data/searcher.log, creating an undisclosed retention channel for potentially sensitive data such as job titles, search terms, account state, errors, and possibly secrets echoed by dependencies. Because the logging is automatic and unconditional, sensitive information can accumulate on disk and be exposed to other local users, backups, or later compromise.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
On shutdown, the script sends a partial run summary to Telegram, which transmits operational data to a third-party messaging service. Even if the summary is brief, it can disclose job-search activity, platform usage, and timing metadata outside the local environment without any consent or minimization controls visible in this file.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
debug_webflow.mjs:29

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
test_apply.mjs:37