Back to skill

Security audit

Recruiter Assistant (Shenzhen)

Security checks for vulnerabilities and agentic risk

Overview

This recruiter skill has a coherent hiring purpose, but it handles sensitive resume data with unsafe shell commands, broad local file access, raw logging, and outbound HR messaging without clear confirmation safeguards.

Review this skill before installing. Use it only on trusted files in a contained workspace, do not process attacker-supplied filenames or paths, and require manual approval before saving reports or messaging HR. The scripts should be fixed to avoid shell command construction, use private temporary files, redact or minimize resume output, and isolate document content from agent instructions.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/batch_screen.js:43
Finding

Shell Command Injection in Batch Resume Processing

Content
View full analysis

Vulnerability Details

File Location: scripts/batch_screen.js:43-49
Vulnerability Type: OS command injection through shell-based command construction
Risk Level: High

Vulnerable Code

js
if (file.toLowerCase().endsWith('.pdf')) {
  const tempTxt = path.join('/tmp', file + '.txt');
  execSync('pdftotext "' + filePath + '" "' + tempTxt + '"');
  processPath = tempTxt;
  isTemp = true;
}

const output = execSync('node ' + path.join(__dirname, 'screen_resume.js') + ' "' + processPath + '" --lang "' + lang + '" --yoe "' + yoe + '"', { encoding: 'utf8' });

Technical Analysis

The script constructs shell commands by concatenating untrusted values into strings passed to child_process.execSync. The affected values include:

  • Resume filenames obtained from the user-selected directory.
  • The --lang command-line argument.
  • The --yoe command-line argument.
  • Paths derived from these inputs.

Wrapping values in double quotes does not make shell construction safe. Command substitutions such as $(command) remain active inside double quotes, and embedded quotes can terminate an argument and introduce additional shell syntax.

Because execSync invokes a shell, these values can alter the intended command rather than being treated exclusively as arguments.

Attack Path

  1. An attacker supplies a crafted option such as a --lang value containing shell substitution, or places a PDF with a shell-bearing filename in the scanned directory.
  2. batch_screen.js reads the value without validation or escaping.
  3. The value is concatenated into the pdftotext or node command string.
  4. execSync passes the constructed string to the operating-system shell.
  5. The shell evaluates the injected syntax and executes the attacker's command.

Impact Assessment

Successful exploitation permits arbitrary command execution with the same operating-system privileges as the Node.js process. ...[truncated 320 chars]

Remediation
View remediation

Remediation Suggestions

  • Replace execSync with execFileSync or spawnSync and pass arguments as an array without enabling a shell.

  • Invoke PDF conversion as follows:

    js
    execFileSync('pdftotext', [filePath, tempTxt], {
      shell: false,
      stdio: 'pipe'
    });
    
  • Invoke the Node.js script using process.execPath and an argument array:

    js
    execFileSync(process.execPath, [
      path.join(__dirname, 'screen_resume.js'),
      processPath,
      '--lang', lang,
      '--yoe', yoe
    ], {
      encoding: 'utf8',
      shell: false
    });
    
  • Validate lang and yoe against explicit formats, lengths, and accepted values.

  • Reject filenames containing control characters and enforce file-type validation based on content rather than extension alone.

  • Run document conversion in a sandboxed, low-privilege process with resource limits.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/process_incoming.js:6
Finding

Path Traversal, Command Injection, and Attacker-Controlled File Deletion

Content
View full analysis

Vulnerability Details

File Location: scripts/process_incoming.js:6-37
Vulnerability Type: Path traversal combined with shell command injection and unsafe cleanup
Risk Level: High

Vulnerable Code

js
const fileKey = process.argv[2];
const fileName = process.argv[3];
const docToken = process.argv[4];

if (!fileKey || !fileName) {
    console.error("Missing fileKey or fileName");
    process.exit(1);
}

const localPath = path.join('/tmp', fileName);
const txtPath = localPath + '.txt';

console.log(`Processing: ${fileName}`);

// In a real environment with direct API access, we'd download here.
// For now, we simulate the logic assuming the file is accessible or 
// will be provided to the screening script.

try {
    // If file exists (manually placed or downloaded by middleware)
    if (fs.existsSync(localPath)) {
        execSync(`pdftotext "${localPath}" "${txtPath}"`);
        const content = fs.readFileSync(txtPath, 'utf8');
        
        // We use the batch screen logic or direct screen
        const output = execSync(`node ${path.join(__dirname, 'screen_resume.js')} "${txtPath}" --lang "PHP Engineer" --yoe "10"`, { encoding: 'utf8' });
        
        console.log("--- EVALUATION RESULT ---");
        console.log(output);
        
        // Cleanup
        fs.unlinkSync(txtPath);

Technical Analysis

fileName is accepted directly from the command line and passed to path.join('/tmp', fileName) without restricting it to a basename or verifying that the resolved path remains under /tmp. Relative traversal sequences can therefore cause the script to access paths outside the intended directory.

The derived paths are also interpolated into template strings passed to execSync. Consequently, shell metacharacters or command substitutions in fileName can result in arbitrary command execution.

Finally, fs.unlinkSync(txtPath) deletes the deriv ...[truncated 1599 chars]

Remediation
View remediation

Remediation Suggestions

  • Require a basename rather than an arbitrary path:

    js
    if (fileName !== path.basename(fileName)) {
      throw new Error('Invalid filename');
    }
    
  • Resolve the input against a dedicated directory and verify containment:

    js
    const root = path.resolve('/tmp/recruiter-incoming');
    const localPath = path.resolve(root, fileName);
    
    if (!localPath.startsWith(root + path.sep)) {
      throw new Error('Path escapes processing directory');
    }
    
  • Replace both execSync calls with execFileSync and argument arrays, with shell: false.

  • Use process.execPath instead of the string node.

  • Maintain an explicit record of files created by the current execution and delete only those files.

  • Use lstatSync and file-descriptor-based operations to reject symbolic links and reduce time-of-check/time-of-use races.

  • Place cleanup in a finally block while preserving the rule that only invocation-owned files may be removed.

  • Remove the unused docToken, or implement explicit token validation in trusted middleware if authorization is required.

  • Run conversion in a dedicated low-privilege sandbox with strict filesystem access.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/batch_screen.js:41
Finding

Predictable Temporary Files Permit Symlink and File-Clobbering Attacks

Content
View full analysis

Vulnerability Details

File Location: scripts/batch_screen.js:41-46
Vulnerability Type: Insecure temporary-file creation in a shared directory
Risk Level: Medium

Vulnerable Code

js
// Handle PDF conversion
if (file.toLowerCase().endsWith('.pdf')) {
  const tempTxt = path.join('/tmp', file + '.txt');
  execSync('pdftotext "' + filePath + '" "' + tempTxt + '"');
  processPath = tempTxt;
  isTemp = true;
}

A corresponding predictable construction also appears at scripts/process_incoming.js:14-15:

js
const localPath = path.join('/tmp', fileName);
const txtPath = localPath + '.txt';

Technical Analysis

Temporary output names are deterministically derived from attacker-influenced filenames and placed directly in the globally shared /tmp directory. The code does not:

  • Create a private temporary directory.
  • Randomize the output filename.
  • Use exclusive file creation.
  • Reject pre-existing symbolic links.
  • Verify ownership or file type before reading or deleting the result.

A local attacker who can predict the filename can pre-create a regular file or symbolic link at the output location. The external converter may then overwrite or follow that path. Subsequent reads and cleanup operate on the same attacker-influenced location.

Attack Path

  1. A local attacker learns or predicts the name of a PDF that will be processed.
  2. The attacker creates /tmp/<filename>.txt before processing, potentially as a symbolic link to another accessible file.
  3. The Skill invokes pdftotext using that predictable path as its output.
  4. The conversion process writes to or replaces the pre-positioned target, depending on platform and tool behavior.
  5. The Skill reads the resulting path and may expose its content through the screening output.
  6. Cleanup may remove the temporary path without verifying that it belongs to the current invocation.

Impact Assess

...[truncated 433 chars]

Remediation
View remediation

Remediation Suggestions

  • Create a private, randomized temporary directory for every invocation:

    js
    const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'recruiter-'));
    const tempTxt = path.join(tempDir, 'resume.txt');
    
  • Set restrictive directory and file permissions appropriate to the platform.

  • Do not derive temporary output names directly from uploaded filenames.

  • Reject symbolic links by checking with lstatSync, while recognizing that descriptor-based or sandboxed handling is preferable to path-based checks.

  • Ensure temporary output is newly created and owned by the current invocation.

  • Remove the private directory recursively in a finally block after all child processes have terminated.

  • Where possible, pass document content through controlled streams rather than shared filesystem paths.

T01 · Skill Instruction Hijacking

Warning
Location
scripts/screen_resume.js:28
Finding

Untrusted Resume and Interview Content Can Hijack Agent Instructions

Content
View full analysis

Vulnerability Details

File Location: scripts/screen_resume.js:28-40
Vulnerability Type: Indirect prompt injection through untrusted document content
Risk Level: Medium

Vulnerable Code

js
const content = fs.readFileSync(resumePath, 'utf8');
console.log('--- SYSTEM CONFIGURATION ---');
console.log(`Target Language: ${lang}`);
console.log(`Experience Requirement: ${yoe} years`);
console.log('----------------------------\n');
console.log('--- RESUME CONTENT START ---');
console.log(content);
console.log('--- RESUME CONTENT END ---');
console.log(`\n[AGENT INSTRUCTION]: Evaluate this resume specifically for a ${lang} position with ${yoe} years of experience. 

CRITICAL: You must also evaluate the candidate's **AI Proficiency** (usage of Cursor, Copilot, LLM integration, etc.) as defined in references/hiring-criteria.md. 

Provide a score (0-100), pros/cons, and a Hire/No-Hire recommendation.`);

The same trust-boundary weakness appears in scripts/generate_questions.js:18-22:

js
const content = fs.readFileSync(inputPath, 'utf8');
console.log('--- SCREENING RESULT START ---');
console.log(content);
console.log('--- SCREENING RESULT END ---');
console.log('\n[AGENT INSTRUCTION]: Based on the screening result above, generate 5-8 deep-dive technical interview questions. Focus on the candidate\'s experience with Go/PHP and specific projects mentioned.');

It also appears in scripts/summarize_interview.js:18-22:

js
const content = fs.readFileSync(notesPath, 'utf8');
console.log('--- INTERVIEW NOTES START ---');
console.log(content);
console.log('--- INTERVIEW NOTES END ---');
console.log('\n[AGENT INSTRUCTION]: Summarize these interview notes into the format defined in assets/report-template.md. Be objective and concise.');

Technical Analysis

Attacker-controlled resume text, screening results, and interview notes are printed into the same textual ...[truncated 1641 chars]

Remediation
View remediation

Remediation Suggestions

  • Pass document text as a structured, explicitly untrusted data field rather than concatenating it with Agent instructions.
  • Place trusted instructions in a higher-priority message role and state that instructions found inside resumes or notes must never be followed.
  • Use a fixed schema for model output and validate the result before further processing.
  • Escape or reject forged delimiter sequences if a text-only interface is unavoidable.
  • Require explicit human confirmation before sending candidate information through the message tool.
  • Restrict downstream tools so document-derived text cannot select recipients, invoke commands, access files, or alter system instructions.
  • Apply data minimization and redact unnecessary personal information before supplying documents to an Agent.
  • Treat generated screening results as untrusted when they are reused by generate_questions.js.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description presents a recruitment assistant that evaluates candidates and performs salary benchmarking. The supplied code chunk, however, is an operational batch runner: it accepts a folder path, scans local files, converts PDFs, and shells out to another script for screening. Its primary observable behavior is filesystem access and subprocess execution, which are undeclared capabilities. While batch resume screening is related to the general recruitment domain, this chunk does not itself implement the advertised critical evaluation or Shenzhen salary benchmarking. Because the code materially emphasizes undeclared local file processing and command execution, and the advertised core capabilities are not present in the chunk, this is a description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description presents a comprehensive recruitment assessment tool for screening, technical evaluation, and salary benchmarking. The supplied code does not implement any of those core functions. Its actual behavior is limited to reading a provided JSON file and emitting a prompt for downstream interview-question generation, specifically emphasizing Go/PHP experience and projects. That is a materially different primary purpose from resume evaluation and salary benchmarking, so this is a clear description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description presents a broad professional recruitment assistant with dynamic candidate evaluation and Shenzhen-specific salary benchmarking. However, this code chunk only handles document processing: it looks for a local file in /tmp, converts a PDF to text, and passes the text to another script with fixed screening arguments ('PHP Engineer', '10' years). There is no implementation here for dynamic requirements, AI proficiency assessment, critical pros/cons analysis, or salary benchmarking. The primary observable behavior is file conversion and orchestration of a screening subprocess, which is narrower and materially different from the declared end-user capability set.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description presents a full recruitment workflow assistant with resume evaluation and Shenzhen-specific salary benchmarking. The code only reads a resume from disk, echoes configuration and content, and prints an agent instruction asking for an evaluation. It does not itself analyze the resume, compute salary benchmarks, or perform market-aligned compensation assessment. The primary implemented behavior is prompt construction for downstream evaluation, which is materially narrower than the declared purpose. Therefore this is a description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description presents a sophisticated recruitment assessment and salary benchmarking assistant, but the supplied code only performs simple file reading and outputs interview notes with a summarization prompt. It does not evaluate resumes, assess candidate skills, benchmark salaries, or perform the described screening functions. The actual primary purpose is interview note summarization preparation, which is materially different from the declared recruitment evaluation and compensation analysis capabilities.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
78% confidence
Finding

The description fixes salary benchmarking to "Shenzhen-specific market salary benchmarks" and "Boss Zhipin 2026 standards" as a default behavior. Under SQP-3, forcing a specific locale without user choice can be a natural-language policy violation unless the regional constraint is clearly justified and documented as intentionally region-specific.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill directs sending high-scoring candidate summaries to HR via a messaging tool without requiring consent, review, or privacy safeguards. Because candidate summaries may include personal information and evaluative judgments, automatic transmission can leak sensitive data to unintended recipients or create compliance issues around hiring records and internal sharing.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill hard-codes Shenzhen-specific salary benchmarks and requires every evaluation to use that regional standard, without any user opt-in, jurisdiction check, or mechanism to adapt to the candidate’s actual market. In a recruitment workflow, this can systematically bias compensation guidance and hiring decisions by anchoring outputs to an irrelevant location, which may produce unfair or misleading assessments.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The script builds shell command strings with untrusted values such as file paths, language, and years-of-experience arguments, then passes them to execSync. Because shell metacharacters in folder contents or CLI parameters can break out of the intended quoting, this can lead to command injection and arbitrary code execution on the host running the recruitment workflow.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The script writes extracted resume text to /tmp using a predictable filename derived from the uploaded PDF name, which can expose sensitive candidate data to other local users or processes and create symlink/race-condition risks. In a recruitment context this is more dangerous because resumes commonly contain PII, employment history, contact details, and compensation data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
80% confidence
Finding

This code executes the external pdftotext command on user-supplied files, which is a safety-relevant subprocess operation. While the script logs that it is processing files, it does not explicitly disclose that an external command will be run or document this behavior in comments/docstrings at the call site.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script emits the entire screening result JSON to stdout, which can expose sensitive candidate information such as personal details, employment history, compensation data, or evaluator notes to terminal logs, CI logs, shell history capture tools, or downstream processes. In a recruitment assistant context, this is more dangerous because the input is likely to contain PII and confidential hiring assessments, and the output is explicitly framed for reuse by another agent step.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
80% confidence
Finding

The manifest describes a recruitment workflow assistant focused on evaluating resumes and salary benchmarking, but this file implements that by spawning OS-level subprocesses via pdftotext and another Node script. While resume parsing is expected, direct shelling out is a broader capability that is not justified by the stated business purpose itself and increases operational power beyond what a recruiter assistant would semantically imply.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code constructs a local file path, converts the file to text, reads its contents, and passes the extracted text to another screening script. Although it logs that processing is happening, it does not warn the user that document contents will be accessed and analyzed, which is relevant for potentially sensitive resume data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The script reads an arbitrary resume file and prints its full contents to stdout, which can expose personally identifiable information such as contact details, employment history, and other sensitive candidate data in terminals, logs, CI output, or agent transcripts. In the context of a recruitment assistant, this is more dangerous because resumes routinely contain confidential personal data, and the script emits it by default without redaction, minimization, or an explicit warning/consent step.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The skill instructs the agent to save each candidate evaluation to its own document, but it does not require user confirmation, path constraints, or disclosure that new files containing sensitive personal data will be created. In a recruitment context, resumes and evaluations often contain personal and potentially confidential employment information, so silent persistence increases privacy and data-retention risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The script invokes pdftotext and another Node.js script via execSync, which are subprocess operations covered by the missing-warning criteria for code files. While the skill appears intended to process incoming documents, the file itself does not provide an explicit disclosure that external commands will be run on the user's file.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/batch_screen.js:44

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/process_incoming.js:27