Back to skill

Security audit

meeting-to-text

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches a local meeting transcription purpose, but it can unexpectedly download a remote speaker model and load it unsafely despite claiming to be fully local.

Review before installing. This skill will process local recordings and write transcripts, but despite advertising a fully local workflow it may contact ModelScope to fetch a speaker model and then load that checkpoint in a way that can execute unsafe serialized content if the model source or cache is compromised. Prefer a version that vendors or preinstalls verified model files, disables runtime downloads, and uses safer checkpoint loading.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Findings (1)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/meeting_to_text.py:175
Finding
Remote Model Download Followed by Unsafe PyTorch Deserialization<![CDATA[ ## Vulnerability Details **File Location**: `scripts/meeting_to_text.py:175-186` and `scripts/meeting_to_text.py:215-224` **Vulnerability Type**: Remote payload retrieval, insecure dependency handling, and unsafe model deserialization **Risk Level**: High ### Complete Vulnerable Code ```python def ensure_speaker_model_cached() -> Path: target_dir = THREE_D_SPEAKER_CACHE / SPEAKER_MODEL_ID if (target_dir / "configuration.json").exists(): return target_dir THREE_D_SPEAKER_CACHE.mkdir(parents=True, exist_ok=True) from modelscope.hub.snapshot_download import snapshot_download downloaded = snapshot_download( SPEAKER_MODEL_ID, revision=SPEAKER_MODEL_REVISION, cache_dir=str(THREE_D_SPEAKER_CACHE), ) return Path(downloaded) ``` The downloaded checkpoint is subsequently loaded as follows: ```python model_dir = ensure_speaker_model_cached() checkpoint_path = model_dir / SPEAKER_MODEL_CKPT if not checkpoint_path.exists(): raise DiarizationError(f"Missing speaker model checkpoint: {checkpoint_path}") config = Config( { "feature_extractor": { "obj": "speakerlab.process.processor.FBank", "args": { "n_mels": 80, "sample_rate": SAMPLE_RATE, "mean_nor": True, }, }, "embedding_model": { "obj": "speakerlab.models.campplus.DTDNN.CAMPPlus", "args": { "feat_dim": 80, "embedding_size": 192, }, }, } ) feature_extractor = build("feature_extractor", config) embedding_model = build("embedding_model", config) state_dict = torch.load(str(checkpoint_path), map_location="cpu") embedding_model.load_state_dict(state_dict) embedding_model.eval() ``` ### Technical Analysis When the speaker model is not already cac ...[truncated 2893 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Fail closed when the local model is missing** - Package or provision the approved speaker model separately. - Remove automatic runtime downloading from the transcription path. - Return a clear validation error if the required model is unavailable. 2. **Use a non-executable model format** - Prefer SafeTensors or another format that stores tensors without pickle-based object deserialization. - Convert and validate the expected model weights during a controlled build or provisioning process. 3. **Restrict PyTorch loading** - Where supported, use: ```python state_dict = torch.load( str(checkpoint_path), map_location="cpu", weights_only=True, ) ``` - Confirm that the returned object is a plain state dictionary containing only expected tensor keys and value types. - Reject unexpected objects, missing keys, extra keys, or incompatible tensor shapes. 4. **Verify artifact integrity** - Pin an approved model version and expected SHA-256 digest in trusted project configuration. - Calculate the checkpoint digest before loading it and reject any mismatch. - Prefer publisher signatures or a trusted internal artifact repository in addition to checksum verification. 5. **Protect the local cache** - Store models in a directory writable only by trusted administrators or the dedicated application account. - Reject symlinks and verify that the resolved checkpoint remains under the approved model directory. - Avoid sharing a writable model cache between mutually untrusted users. 6. **Disclose network behavior** - If remote download remains necessary, update `SKILL.md` to state that a cache miss causes network access. - Require explicit user authorization before downloading. - Restrict outbound access to the approved endpoint and apply download size and timeout limits. 7. **Separate acquisition from execution** - Implement a controlled inst ...[truncated 171 chars]
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (14)

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
Always treat the last non-empty stdout line as the JSON result object.

Interpret results this way:
- Exit code `0` with `status: success`: transcript file was created with no warnings.
- Exit code `0` with `status: warning`: transcript file was created, but you must report the warnings and any skipped segments.
- Non-zero exit code or `status: error`: do not claim success; surface the warning list and the intended output path.
Confidence
80% confidence
Finding
Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Hidden Instructions

High
Category
Prompt Injection
Content
# Troubleshooting

## Validation Errors
Confidence
60% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
# Troubleshooting

## Validation Errors
Confidence
60% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The skill is described as fully local, but it silently downloads a speaker model at runtime when the cache is missing. This creates an undisclosed network egress path, may transmit metadata to a third-party service, and expands the trust boundary to remote content that can influence later model loading behavior.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
For a local transcription skill, runtime network download capability is unnecessary and dangerous because it permits external dependency retrieval during execution. In this context, that is more serious than usual: the code handles local meeting recordings, which may be sensitive, so unexpected online behavior undermines user expectations and increases supply-chain and privacy risk.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill instructs the agent to read local path references, invoke a Python interpreter, and write transcript output, but it declares no explicit tool/permission scope. That creates an authorization ambiguity where a host agent may execute shell and file operations more broadly than intended, increasing the chance of unsafe local file access or command execution through user-controlled paths.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The description says the skill should use default speaker labels like `说话人1, 说话人2` and treats related Chinese phrases as triggers. This imposes a specific language/locale choice in user-facing output without offering an alternative or opt-in, which violates the language/locale policy criteria.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The behavior guarantee states the script will write timestamps and `说话人N`, making the output language fixed to Chinese. There is no documented user choice or justification for this locale-specific constraint.

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
The eval content requires output using Chinese text such as '带说话人标签', '说话人1', and Chinese warning wording, which implies a fixed locale/language expectation. In a JSON eval/config file, there is no accompanying justification or user opt-in showing that Chinese-only output is intentional or region-specific.

Natural-Language Policy Violations

Medium
Confidence
84% confidence
Finding
This prompt specifies Chinese-language behavior for an English-source MP4 ('en_source.mp4') but provides no documented opt-in or rationale for forcing that locale. That creates a natural-language policy concern because the skill behavior appears constrained to a specific language without user choice.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"pcm_s16le",
        str(output_wav_path),
    ]
    completed = subprocess.run(command, capture_output=True, text=True, encoding="utf-8", errors="replace")
    if completed.returncode != 0 or not output_wav_path.exists():
        message = completed.stderr.strip() or completed.stdout.strip() or "ffmpeg failed to normalize media"
        raise MediaError(message)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'command' from os.getenv (line 133, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
"pcm_s16le",
        str(output_wav_path),
    ]
    completed = subprocess.run(command, capture_output=True, text=True, encoding="utf-8", errors="replace")
    if completed.returncode != 0 or not output_wav_path.exists():
        message = completed.stderr.strip() or completed.stdout.strip() or "ffmpeg failed to normalize media"
        raise MediaError(message)
Confidence
87% confidence
Finding
The executable path comes from MEETING_TO_TEXT_FFMPEG, an environment variable, and is then executed directly via subprocess.run(). While this is not shell injection, it is still arbitrary program execution if an attacker can influence the environment or deployment configuration, causing the skill to run a malicious binary instead of ffmpeg.

Insecure deserialization: torch.load() without weights_only=True

Medium
Category
Dangerous Code Execution
Content
feature_extractor = build("feature_extractor", config)
    embedding_model = build("embedding_model", config)
    state_dict = torch.load(str(checkpoint_path), map_location="cpu")
    embedding_model.load_state_dict(state_dict)
    embedding_model.eval()
    return feature_extractor, embedding_model, circle_pad
Confidence
97% confidence
Finding
torch.load() can deserialize pickle-based model files and may execute attacker-controlled code during loading. Because the checkpoint path is influenced by cache contents and can also be redirected via environment-controlled model locations, a malicious checkpoint could yield arbitrary code execution under the skill's privileges.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
This code forces transcript speaker labels to use the Chinese string "说话人" regardless of the user's preferred language or locale. The file contains no user option, configuration, or documented justification for restricting output labels to Chinese, which creates a natural-language locale policy violation.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/meeting_to_text.py:225