Back to skill

Security audit

Doppel

Security checks for vulnerabilities and agentic risk

Overview

The skill largely matches its stated writing-voice purpose, but its install and privacy safeguards are under-scoped for private writing data.

Review before installing. Prefer cloning or copying the reviewed repository instead of the unpinned `npx` command, and add/verify `.gitignore` rules before using real writing samples. Use only consented subject-authored sources, keep manifests and context packs outside Git, and remember that any agent/model that reads the context pack may process those excerpts.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T08 · Insecure Dependencies

Warning
Location
README.md:62
Finding

Unpinned third-party package execution through npx

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/validate_package.py:12
Finding

Missing Git ignore rules can expose private voice data

Content
View full analysis
bool: """Return whether the basename is covered by the shipped Git ignore rules.""" return LOCAL_OUTPUT_RE.fullmatch(path.name) is not None ``` The workflow instructs users to rely on an ignored filename: ```markdown 2. Copy `examples/voice-manifest.example.json` to an ignored local filename such as `voice-manifest.local.json`. Add subject-authored sources and their SHA-256 hashes. ``` However, the audited project root does not contain the required `.gitignore` file. ### Technical Analysis The Skill handles privacy-sensitive writing samples, manifests, provenance metadata, and generated context packs containing verbatim excerpts. Its documentation and output-name validation represent these artifacts as protected by shipped Git ignore rules. Because `.gitignore` is absent, matching a filename such as `voice-context.local.md` provides no actual protection against Git staging or commits. The builder checks only the output basename and does not verify that an applicable ignore rule exists. Although `validate_package.py` reports the missing file when users run it, the builder remains independently executable, and the documented privacy assumption can therefore fail open. This is primari ...[truncated 1380 chars]
Remediation
View remediation
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (6)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is about constructing and using a local writing-voice digital twin for drafting and revision. The actual code does not build a voice model, process subject-authored writing, generate or revise text, or implement consent/provenance workflows directly. Instead, it performs repository/package validation and safety/compliance checks for the Doppel package. While these checks may support the broader project, this code chunk’s primary purpose is materially different from the declared end-user functionality, so this is a clear description-behavior mismatch.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding

The README instructs users to run npx skills add AntreasAntoniou/doppel without pinning a specific package version. Because npx resolves and executes code from the registry at install time, a future compromised, malicious, or simply breaking release of the referenced package/tooling could be executed on a user's machine, creating a supply-chain risk.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/validate_package.py (reported line 29)May include surrounding context.

python
def tracked_files(root: Path = ROOT) -> list[Path]:
    result = subprocess.run(
        ["git", "ls-files"], cwd=root, check=False, capture_output=True, text=True
    )
    if result.returncode == 0:

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

The manifest describes building a consent-first, provenance-aware local writing-voice context pack from subject-authored material. The separate --hash mode lets a caller compute the SHA-256 of any local file path, which is a general-purpose file utility not clearly justified by drafting or revising text with a voice Doppel.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/build_voice_context.py (reported line 202)May include surrounding context.

python
raise SystemExit("--hash cannot be combined with context-building arguments")
        print(sha256_file(args.hash.resolve()))
        return 0
    missing = [name for name in ("manifest", "task", "audience", "output") if getattr(args, name) is None]
    if missing:
        raise SystemExit(f"Missing required arguments: {', '.join('--' + item for item in missing)}")
    if not safe_local_output_name(args.output):

Static analysis

No suspicious patterns detected.