Back to skill

Security audit

review simulate

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Chinese voice interview simulator, but users should understand that it sends interview audio and generated text to configured cloud AI services and saves reports locally.

Install only if you are comfortable sharing interview audio, transcripts, prompts, and summaries with the configured LLM/ASR/TTS providers. Use private output locations, avoid sensitive personal details in practice answers, consider --no-tts when voice playback is not needed, and prefer pinned dependency versions and deletion of saved reports/audio after use.

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
scripts/run_interview.py:305
Finding
Excessive Persistence of Raw ASR Provider Responses<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run_interview.py:305-313`, `scripts/run_interview.py:423-434`, and `scripts/run_interview.py:451-463` **Vulnerability Type**: Sensitive-data over-retention and insecure local storage **Risk Level**: Medium ### Vulnerable Code ```python payload = response.json() text = payload.get("text", "").strip() if not text and payload.get("segments"): text = " ".join(seg.get("text", "").strip() for seg in payload["segments"]).strip() if not text: raise ValueError("ASR 未返回可用文本") return {"text": text, "raw": payload} ``` ```python asr_result = transcribe_audio(audio_path, config["language"]) answer_text = asr_result["text"] asr_raw = asr_result["raw"] ``` ```python turn = { "round_id": round_id, "question_type": current_question_type, "interviewer_question": current_question, "interviewer_audio": tts_path, "asr_text": answer_text, "asr_raw": asr_raw, "evaluation": evaluation, "decision": decision, } turns.append(turn) ``` ```python payload = { "config": config, "closing_text": closing_text, "turns": turns, "final_report": final_report, "report_summary_tts_audio": report_tts, } if args.save_report: report_path = Path(args.save_report) else: OUTPUT_DIR.mkdir(parents=True, exist_ok=True) report_path = OUTPUT_DIR / "final_report.json" report_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") ``` ### Technical Analysis The ASR function returns both the normalized transcript and the complete response received from the external ASR provider. The complete response is subsequently assigned to `asr_raw`, inserted into every turn record, and written to the final JSON report. The interview workflow only requires the normalized transcript for evaluation and question generation. Retaining the complete provider response therefore exceeds the minimum data required for the declared functionality. Depending on the prov ...[truncated 1596 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Return and retain only the normalized transcript by default: ```python return {"text": text} ``` 2. Remove `asr_raw` from the persisted turn structure: ```python turn = { "round_id": round_id, "question_type": current_question_type, "interviewer_question": current_question, "interviewer_audio": tts_path, "asr_text": answer_text, "evaluation": evaluation, "decision": decision, } ``` 3. If raw ASR diagnostics are operationally necessary, make storage explicitly opt-in through a clearly documented debugging option. 4. Before storing an opted-in raw response, allowlist required fields rather than serializing the provider response wholesale. 5. Document what interview data is sent to third parties, what is written locally, and how long it should be retained. 6. Create reports with restrictive file permissions and recommend storage in a private, access-controlled directory. 7. Provide a cleanup or retention mechanism for generated reports and synthesized audio files. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run_interview.py:160
Finding
Prompt Injection Through Untrusted Candidate Answers and Interview History<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run_interview.py:160-180`, `scripts/run_interview.py:193-228`, `scripts/run_interview.py:237-281`, and `scripts/run_interview.py:339-350` **Vulnerability Type**: Prompt injection and insufficient validation of model-generated decisions **Risk Level**: Medium ### Vulnerable Code ```python def evaluate_turn(question: str, question_type: str, answer: str, history_summary: str) -> dict[str, Any]: user_prompt = f"""输入: - 当前问题:{question} - 问题类型:{question_type} - 用户回答:{answer} - 历史摘要:{history_summary} 请输出 JSON,字段包括: - relevance - clarity - specificity - persuasiveness - brief_comment - gap_summary 要求: - 四维评分使用 1-5 分整数 - brief_comment 控制在 1-2 句 - 评语具体,不要空泛夸奖 - gap_summary 只总结最需要追问或改进的点 """ return llm_json(EVALUATION_SYSTEM_PROMPT, user_prompt) ``` ```python def generate_next_question( decision: dict[str, Any], current_answer: str, history_summary: str, config: dict[str, Any], ) -> dict[str, Any]: user_prompt = f"""输入: - 决策结果:{json.dumps(decision, ensure_ascii=False)} - 当前用户回答:{current_answer} - 历史摘要:{history_summary} - 目标岗位:{config['target_role']} - 面试官风格:{config['interviewer_style']} 请输出 JSON,字段包括: - next_question - question_type 要求: - 如果 action=follow_up,围绕上一轮缺口深挖 - 如果 action=new_question,自然切换到未充分覆盖的维度 - 每次只生成一个问题 - 语言自然,像真实中文面试官 """ return llm_json(NEXT_QUESTION_SYSTEM_PROMPT, user_prompt) ``` ```python def generate_final_report(turns: list[dict[str, Any]], config: dict[str, Any]) -> dict[str, Any]: user_prompt = f"""输入: - 全部轮次记录:{json.dumps(turns, ensure_ascii=False)} - 会话配置:{json.dumps(config, ensure_ascii=False)} 请输出 JSON,字段包括: - overall_score - dimension_scores - strengths - weaknesses - round_summaries - improvement_suggestions - sample_better_answer - final_summary_text 要求: - 反馈要具体、可执行 - strengths 和 weaknesses 各给 2-4 条 - improvement_suggestions 给 2-4 条 - sample_better_answer 用中文给出一段更优表达示例 - final_summary_text 适合直接展示给用户,也可交给 TTS 朗读 """ return llm_json ...[truncated 3123 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Explicitly state in every relevant system prompt that candidate answers, transcripts, history, and prior model output are untrusted data and that instructions contained within them must never be followed. 2. Delimit untrusted values with clearly identified structured boundaries. Prefer structured message content or a strict JSON input object rather than embedding values into prose. 3. Separate application instructions from candidate data. For example, serialize candidate content into a dedicated field and describe its semantics in the system message. 4. Validate every model response against a strict schema: - Require all expected fields. - Reject unknown fields where practical. - Require integer scores from 1 through 5. - Restrict `action` to `follow_up`, `new_question`, or `end`. - Restrict question types to the documented allowlist. - Validate the final score range and dimension-score types. - Limit lengths for comments, questions, and summaries. 5. Recompute aggregate scores in deterministic application code rather than allowing the LLM to choose an unconstrained overall score. 6. Do not place raw candidate answers into decision prompts unless required. Pass only application-generated, validated summaries of relevant deficiencies. 7. Treat prior model output as untrusted before reinserting it into later prompts. Validate and normalize evaluations and decisions first. 8. Add adversarial tests using candidate answers that contain role changes, requests to ignore previous instructions, forged JSON, delimiter-breaking content, and requests for maximum scores. ]]>
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
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (21)

Tainted flow: 'ASR_URL' from os.environ.get (line 89, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
data["language"] = language.replace("-CN", "").lower()
    with audio_path.open("rb") as handle:
        files = {"file": (audio_path.name, handle)}
        response = requests.post(
            ASR_URL,
            headers=headers,
            data=data,
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'TTS_URL' from os.environ.get (line 95, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
"voice_setting": {"voice_id": TTS_VOICE_ID},
        "audio_setting": {"format": "mp3", "sample_rate": 32000},
    }
    response = requests.post(TTS_URL, headers=headers, json=payload, timeout=TTS_TIMEOUT)
    response.raise_for_status()
    result = response.json()
    if result.get("base_resp", {}).get("status_code") != 0:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Credential Access

High
Category
Privilege Escalation
Content
def load_env() -> None:
    if load_dotenv is None:
        return
    for path in (SKILL_DIR / ".env", Path.cwd() / ".env"):
        if path.exists():
            load_dotenv(path)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill describes executable behavior that uses environment variables, networked LLM/ASR/TTS services, and writes outputs to disk, but it does not declare any explicit tool scope or permissions. This creates a trust and containment gap: a host may expose broader capabilities than intended, and reviewers or runtime policy engines cannot clearly enforce least privilege for a skill that handles user audio, transcripts, and generated reports.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The description states the skill performs a Chinese voice interview simulation and later specifies a default language of `zh-CN`, while the scope explicitly excludes English interviews. This imposes a language constraint as a policy-relevant natural-language behavior without presenting it as a user choice or opt-in.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The prompt explicitly constrains the interaction language to Chinese using natural-language instructions, including a fixed `语言:中文` field. This appears to enforce a specific language/locale choice rather than offering user selection or documenting a region-specific justification, which matches the policy-violation criteria.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
The module instructions repeatedly specify that the simulated interview and generated content must be in Chinese, reinforcing a fixed language policy across the skill. Because the file does not indicate user opt-in or a justified locale restriction, this is a natural-language policy concern.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The example SessionConfig sets `"language": "zh-CN"`, which natural-language-wise implies the skill operates in a fixed locale. The file does not mention any user-selectable language option or justify that the skill is intentionally region-specific, so this can conflict with language/locale choice policy.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
Multiple system prompts explicitly instruct the model to act as a Chinese interviewer and produce Chinese interview content, which enforces a locale/language constraint at the skill level. Although a --language flag exists later, these prompts still hard-code Chinese behavior rather than offering a genuine user language choice.

External Transmission

Medium
Category
Data Exfiltration
Content
LLM_TIMEOUT = float(os.environ.get("INTERVIEW_LLM_TIMEOUT", "60"))
    LLM_TEMPERATURE = float(os.environ.get("INTERVIEW_LLM_TEMPERATURE", "0.3"))

    ASR_URL = os.environ.get("INTERVIEW_ASR_URL", "https://api.senseaudio.cn/v1/audio/transcriptions")
    ASR_MODEL = os.environ.get("INTERVIEW_ASR_MODEL", "sense-asr-pro")
    ASR_API_KEY = os.environ.get("INTERVIEW_ASR_API_KEY", os.environ.get("SENSEAUDIO_API_KEY", ""))
    ASR_TIMEOUT = float(os.environ.get("INTERVIEW_ASR_TIMEOUT", "300"))
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
LLM_TIMEOUT = float(os.environ.get("INTERVIEW_LLM_TIMEOUT", "60"))
    LLM_TEMPERATURE = float(os.environ.get("INTERVIEW_LLM_TEMPERATURE", "0.3"))

    ASR_URL = os.environ.get("INTERVIEW_ASR_URL", "https://api.senseaudio.cn/v1/audio/transcriptions")
    ASR_MODEL = os.environ.get("INTERVIEW_ASR_MODEL", "sense-asr-pro")
    ASR_API_KEY = os.environ.get("INTERVIEW_ASR_API_KEY", os.environ.get("SENSEAUDIO_API_KEY", ""))
    ASR_TIMEOUT = float(os.environ.get("INTERVIEW_ASR_TIMEOUT", "300"))
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"voice_setting": {"voice_id": TTS_VOICE_ID},
        "audio_setting": {"format": "mp3", "sample_rate": 32000},
    }
    response = requests.post(TTS_URL, headers=headers, json=payload, timeout=TTS_TIMEOUT)
    response.raise_for_status()
    result = response.json()
    if result.get("base_resp", {}).get("status_code") != 0:
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script sends generated interview questions and the final summary text to an external TTS service without clearly informing the user. While less sensitive than raw audio in many cases, these prompts and summaries can still expose personal context, performance assessments, and job-target information to a third party.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
When the user provides an audio file, the script uploads it to an external ASR service without an explicit consent or privacy notice at the decision point. Interview answers commonly contain personal information, employment history, and other sensitive content, so silent transmission to a third party creates a real privacy and compliance risk.

Tainted flow: 'payload' from os.environ.get (line 321, credential/environment) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
else:
        OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
        report_path = OUTPUT_DIR / "final_report.json"
    report_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"完整结果已保存:{report_path}")
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
The manifest sets the skill's user-facing display name entirely in Chinese ("语音面试模拟器") with no indication that users can choose another language or locale. This can conflict with language/locale policy requirements when the skill is presented to a broader audience without explicit opt-in or justification.

Unpinned Dependencies

Low
Category
Supply Chain
Content
openai>=1.30.0
requests>=2.31.0
python-dotenv>=1.0.0
Confidence
93% confidence
Finding
The dependency specifier for `openai` is unpinned (`>=1.30.0`), which makes builds non-reproducible and allows future releases with breaking changes or newly introduced vulnerabilities to be installed automatically. This is a real supply-chain hygiene issue, though the file alone does not show active exploitation or a known vulnerable version.

Unpinned Dependencies

Low
Category
Supply Chain
Content
openai>=1.30.0
requests>=2.31.0
python-dotenv>=1.0.0
Confidence
98% confidence
Finding
`requests>=2.31.0` is unpinned, so installations may resolve to different versions over time, including versions later found vulnerable or behaviorally incompatible. Because `requests` has a history of security advisories, leaving it unpinned increases uncertainty and supply-chain exposure.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
91% confidence
Finding
`requests` has known advisories, and because the manifest does not pin a version, there is no way to verify from this file whether the installed version is affected or patched. In this skill context, the package is commonly used for network calls, so unresolved dependency versioning could matter if vulnerable code paths are present elsewhere in the project.

Unpinned Dependencies

Low
Category
Supply Chain
Content
openai>=1.30.0
requests>=2.31.0
python-dotenv>=1.0.0
Confidence
97% confidence
Finding
`python-dotenv>=1.0.0` is also unpinned, which permits uncontrolled version drift and makes it impossible to guarantee which release is deployed. If the package later introduces or contains a vulnerable version in the allowed range, the environment could be exposed without any manifest change.

Unverifiable Dependency: python-dotenv has 2 known advisory(ies) (CVE-2026-28684 (python-dotenv: Symlink following in set_key allows arbitrary file overwrite via ); CVE-2026-28684 (python-dotenv reads key-value pairs from a .env file and can set them as environ)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
85% confidence
Finding
`python-dotenv` has known advisories, and without an exact version pin the project cannot demonstrate that it avoids affected releases. This is somewhat less dangerous in the current skill description because no direct `.env` file manipulation is shown here, but it still represents a real supply-chain verification gap.

Static analysis

No suspicious patterns detected.