Back to skill

Security audit

sn-ppt-creative

Security checks for vulnerabilities and agentic risk

Overview

This PPT-generation skill is mostly coherent, but it has under-scoped file handling that could expose local reference-image files to a VLM service and write PPTX output outside the intended deck folder.

Install only if you trust the upstream entry skill and deck metadata source. Before use, constrain reference images to an approved asset folder and require PPTX output paths to resolve inside deck_dir; otherwise a manipulated task pack could read unintended local image files or write files outside the deck folder.

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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
SKILL.md:126
Finding

Unrestricted Reference Image Paths Permit Local File Disclosure

Content
View full analysis
') ip = json.loads((deck / 'info_pack.json').read_text()) tp = json.loads((deck / 'task_pack.json').read_text()) refs = [p for p in (ip.get('user_assets') or {}).get('reference_images', []) if pathlib.Path(p).exists()] sys_prompt = open('$SKILL_DIR/prompts/style_from_image.md').read() user_prompt = f'PPT 主题/参数: {json.dumps(tp["params"], ensure_ascii=False)}\nuser_query: {ip.get("user_query") or ""}' md = vlm(sys_prompt, user_prompt, images=refs) ``` ### Technical Analysis The reference-image paths are loaded from `info_pack.json` and validated only with `Path.exists()`. The code does not require a path to be located beneath the deck's approved asset directory, does not reject absolute or traversal paths, and does not ensure that the resolved target is a regular image file. As a result, a party able to influence `user_assets.reference_images` can direct the process to any local path readable by the account running the Skill. The selected files are then passed to `vlm(..., images=refs)`, which may read and transmit their contents to the configured VLM service. This violates least privilege because the Skill only needs access to uploaded reference assets, not arbitrary files accessible to the host process. ### Attack Path 1. An attacker gains control over, or supplies data used to create, `/info_pack.json`. 2. The attacker places an absolute path or traversal path in `user_assets.reference_images`, such as a path to a sensitive local image outside the deck directory. 3. `Path(p).exists()` returns true because the target exists. 4. The untrusted path is retained in `refs`. 5. The Skill passes the path to `vlm()` through the `images` argument. 6. The model client reads the file and may transmit its cont ...[truncated 698 chars]
Remediation
View remediation
/assets/reference_images`. 2. Resolve both the trusted root and every supplied path before use. 3. Reject any resolved path that is not contained beneath the trusted root. 4. Require each target to be a regular file and validate its extension and actual media type. 5. Address symlink traversal by resolving the final target before performing the containment check. 6. Reject the entire request, rather than silently accepting unsafe entries, when an out-of-scope path is supplied. 7. Configure the model client to accept uploaded byte streams or validated asset identifiers instead of arbitrary filesystem paths where possible. Example hardening: ```python asset_root = (deck / "assets" / "reference_images").resolve() refs = [] for raw_path in (ip.get("user_assets") or {}).get("reference_images", []): candidate = pathlib.Path(raw_path).expanduser().resolve() if not candidate.is_relative_to(asset_root): raise ValueError(f"Reference image is outside the approved asset directory: {raw_path}") if not candidate.is_file(): raise ValueError(f"Reference image is not a regular file: {raw_path}") if candidate.suffix.lower() not in {".png", ".jpg", ".jpeg", ".webp"}: raise ValueError(f"Unsupported reference image type: {raw_path}") refs.append(str(candidate)) ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/build_pptx.py:84
Finding

PPTX Output Path Is Not Restricted to the Deck Directory

Content
View full analysis
dict: tp = json.loads((deck / "task_pack.json").read_text(encoding="utf-8")) deck_id = tp.get("deck_id") or deck.name page_count = int(tp["params"]["page_count"]) prs = Presentation() prs.slide_width = SLIDE_W prs.slide_height = SLIDE_H blank_layout = prs.slide_layouts[6] included: list[int] = [] missing: list[int] = [] for n, png in resolve_page_order(deck, page_count): slide = prs.slides.add_slide(blank_layout) if png.exists(): slide.shapes.add_picture(str(png), 0, 0, width=SLIDE_W, height=SLIDE_H) included.append(n) else: missing.append(n) print(f"[build_pptx] page_{n:03d}.png missing, inserting blank slide", file=sys.stderr) output.parent.mkdir(parents=True, exist_ok=True) prs.save(str(output)) ``` ```python def main(argv: list[str] | None = None) -> int: p = argparse.ArgumentParser() p.add_argument("--deck-dir", type=Path, required=True) p.add_argument("--output", type=Path, default=None, help="Output .pptx path. Default: /.pptx") args = p.parse_args(argv) deck = args.deck_dir.expanduser().resolve() if not (deck / "task_pack.json").exists(): print(json.dumps({"error": f"task_pack.json missing in {deck}"}, ensure_ascii=False)) return 2 if args.output is None: tp = json.loads((deck / "task_pack.json").read_text(encoding="utf-8")) deck_id = tp.get("deck_id") or deck.name output = deck / f"{deck_id}.pptx" else: output = args.output.expanduser().resolve() result = build(deck, output) ``` ### Technical Analysis The default output filename incorpo ...[truncated 2192 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (9)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description presents a broader creative-slide generation pipeline involving model calls, image generation, and fallback web search. The supplied code does none of that. It reads local metadata and pre-existing PNGs, creates a PowerPoint deck, inserts images or blank slides for missing pages, and saves a .pptx. This is a materially different primary purpose from the declared generation pipeline. While the one-image-per-slide 16:9 aspect aligns partially with the description, the core capabilities described are absent, and the code instead implements a downstream assembly step.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill instructs extensive file reads and writes across a user-supplied <deck_dir> and uses multiple external scripts, but it declares no explicit tool scope or permission boundaries. Without a declared allowlist, an agent/runtime may grant broader filesystem capabilities than necessary, increasing the risk of unintended file access or overwrite if deck_dir or related inputs are manipulated.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

Earlier, Stage 5 requires producing a PPTX via scripts/build_pptx.py and even discusses the case where python-pptx is missing, implying that script may legitimately depend on it. But L295 says "Do NOT use python-pptx ... scripts/build_pptx.py is the ONLY way to produce a PPTX," which conflicts with the documented build path if that script itself uses python-pptx.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

Line L035 hard-codes page_number_label as 第1页,共N页, which is Chinese text regardless of the requested language. Although L110 later mentions an English alternative, the schema/example itself sets a Chinese default and may cause the model to emit Chinese even for English outputs.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Line L29 states that if language is zh, the text must be Chinese, and if en it must be English. This imposes a language policy in the prompt instructions without indicating any user choice, opt-in, or justification for restricting output to those languages.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The prompt is written as a mandatory Chinese-language instruction set and does not indicate that the user may choose another language. Under the policy rule for natural-language violations, forcing a specific language without opt-in is a locale-policy issue.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file’s core instructions are written to require operation in Chinese and position the assistant as a Chinese-language PPT design expert. Although the input schema includes a language field later, the prompt does not explicitly let the user choose the response language or state that output should follow that field, which creates a locale-policy issue.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
99% confidence
Finding

The table at L027-L029 explicitly says T2I must go through sn-image-base/scripts/sn_agent_runner.py, but L031 says "Never mix — LLM / VLM through sn-image-base, or T2I through model_client," which reverses the second half of the policy. This is an active documentation contradiction that could misdirect implementers about which backend is permitted for image generation.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency is specified with only a lower bound, which allows future versions of python-pptx to be installed without review. This can introduce supply-chain risk through breaking changes, newly introduced vulnerable transitive dependencies, or a compromised upstream release, especially in an automated build or deployment environment.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
python-pptx>=0.6.21

Static analysis

No suspicious patterns detected.