Back to skill

Security audit

元钥 yotta-secret

Security checks for vulnerabilities and agentic risk

Overview

The skill is a legitimate local secret scanner, but its default reports can expose the secrets it says are masked and can report a clean scan when requested targets were not actually scanned.

Install only if you are comfortable with a Review-level issue: use it only on authorized repositories, avoid sharing its text/JSON/CSV reports until you manually confirm snippets contain no secrets, do not rely on exit code 0 unless you know all targets were readable and scanned, and pin the npm package version instead of running an unversioned npx command.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/yotta_secret.py:401
Finding
Default Scan Reports Can Disclose Plaintext Secrets Through Snippet Fields<![CDATA[ ## Vulnerability Details **File Location**: `scripts/yotta_secret.py`, lines 401–403, 430–433, and 532–543 **Vulnerability Type**: Sensitive information exposure caused by incomplete output redaction **Risk Level**: High ### Vulnerable Code Normal file and standard-input scanning: ```python display = value if opts.show_secret else mask_secret(value) snippet = line.strip() snippet = snippet[: span[0]] + display + snippet[span[1]:] findings.append({ "rule_id": rule.id, "rule_name": rule.name, "category": rule.category, "severity": rule.severity, "file": fname, "line": lineno, "secret": display, "length": len(value), "entropy": round(shannon_entropy(value), 3), "snippet": snippet[:200], "commit": "", "path_in_commit": "", }) ``` Private-key scanning: ```python display = "[PRIVATE KEY REDACTED]" if opts.show_secret: display = value[:12] + "...(%d chars)" % len(value) snippet = " ".join(value.split())[:200] findings.append({ "rule_id": rule.id, "rule_name": rule.name, "category": rule.category, "severity": rule.severity, "file": fname, "line": lineno, "secret": display, "length": len(value), "entropy": round(shannon_entropy(value), 3), "snippet": snippet, "commit": "", "path_in_commit": "", }) ``` Git-history scanning: ```python display = value if opts.show_secret else mask_secret(value) findings.append({ "rule_id": rule.id, "rule_name": rule.name, "category": rule.category, "severity": rule.severity, "file": cur_path, "line": 0, "secret": display, "length": len(value), "entropy": round(shannon_entropy(value), 3), "snippet": content.strip()[:200], "commit": commit, "path_in_commit": cur_path, }) ``` ### Technical Analysis The scanner claims to mask secrets unless `--show-secret` is explicitly supplied. Although the `secret` field is masked by default, the associated `snippet` field is not consistently ...[truncated 2268 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Redact every output field derived from source content, not only the `secret` field. 2. Apply match offsets before stripping or otherwise transforming the source line: ```python snippet = line[:span[0]] + display + line[span[1]:] snippet = snippet.strip()[:200] ``` 3. Prefer applying a centralized redaction function to the final snippet: ```python snippet = redact_text(line).strip()[:200] ``` 4. For private-key findings, never include content from the key block in default snippets. Use a fixed value such as: ```python snippet = "[PRIVATE KEY REDACTED]" ``` 5. In git-history mode, replace the matched span with `display`, or pass the complete line through `redact_text()` before adding it to a finding. 6. Ensure that redaction handles multiple secrets on the same line rather than replacing only the current match. 7. Add regression tests that serialize complete text, JSON, and CSV reports and assert that the original secret does not appear anywhere when `--show-secret` is absent. 8. Add dedicated tests for: - indented credential assignments; - private-key blocks; - git-history findings; - multiple secrets on one line; - secrets near the 200-character truncation boundary. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/yotta_secret.py:313
Finding
Unreadable or Missing Scan Targets Can Produce a False Clean Result<![CDATA[ ## Vulnerability Details **File Location**: `scripts/yotta_secret.py`, lines 313–315, 468–470, and 666–669 **Vulnerability Type**: Fail-open error handling in a security scanner **Risk Level**: High ### Vulnerable Code Read failures are converted into an indistinguishable `None` result: ```python try: with open(path, "rb") as f: head = f.read(8192) if b"\x00" in head: return None f.seek(0) data = f.read() except OSError: return None ``` Missing or invalid targets are only reported as warnings: ```python if not os.path.isdir(p): sys.stderr.write("[warn] 路径不存在: %s\n" % p) continue ``` Files that return `None` are silently skipped: ```python text = read_text(full, max_bytes) if text is None: continue sources.append(full) findings.extend(scan_text(text, full, args)) ``` After these skips, the command returns a clean status if no findings remain: ```python return EXIT_FOUND if findings else EXIT_CLEAN ``` ### Technical Analysis `read_text()` uses the same `None` result for several materially different conditions: - intentional binary-file exclusion; - oversized-file exclusion; - permission denial; - file disappearance during traversal; - filesystem errors; - other `OSError` conditions. The caller silently skips every `None` result. Similarly, a nonexistent explicitly requested path only produces a warning. No error state is retained, so the command eventually returns `EXIT_CLEAN` when no findings were collected. This behavior contradicts the documented contract that read and usage errors return exit code `4`. It is particularly unsafe when the scanner is used as a commit, build, or release gate: exit code `0` can mean either “all intended inputs were scanned and no secrets were found” or “some or all intended inputs could not be scanned.” ### Attack Path 1. A CI job or user configures the scanner to examine a credential-bearing file or directory. 2. The target path is mistyped, ...[truncated 1319 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Distinguish intentional skips from read failures. Return structured outcomes such as: ```python {"status": "ok", "text": text} {"status": "binary"} {"status": "too_large"} {"status": "error", "error": str(err)} ``` 2. Track all scan errors in `cmd_scan()`. 3. Return `EXIT_ERROR` when an explicitly requested path does not exist or cannot be read. 4. Treat permission failures, unexpected disappearance, and other filesystem errors as scan failures rather than clean skips. 5. Clearly report intentionally skipped oversized or binary files. Consider a strict mode that fails if any file is not scanned. 6. Preserve a machine-readable summary containing: - files discovered; - files successfully scanned; - binary files skipped; - oversized files skipped; - read failures; - missing requested targets. 7. In CI-oriented usage, fail closed whenever scan coverage is incomplete. 8. Add regression tests requiring exit code `4` for: - nonexistent explicit paths; - permission-denied files and directories; - files removed during scanning; - other simulated read errors. ]]>

T08 · Insecure Dependencies

Warning
Location
README.md:74
Finding
Recommended Unpinned npx Installation Executes a Mutable Remote Package<![CDATA[ ## Vulnerability Details **File Location**: `README.md`, lines 74–76; `README.zh-CN.md`, lines 74–76 **Vulnerability Type**: Unpinned package execution and supply-chain exposure **Risk Level**: Medium ### Vulnerable Documentation ```text # Optional China mirror: npm config set registry https://registry.npmmirror.com npx -y @yottameta/yotta-secret --agent <agent-name> # install to the agent's default user-level skills dir npx -y @yottameta/yotta-secret --dir <your-skills-dir> # point to the skills dir itself (e.g. ~/.codex/skills) ``` The equivalent commands are also recommended in `README.zh-CN.md`. ### Technical Analysis The recommended command invokes `npx -y` without an exact package version. `npx` retrieves the currently resolved package from the configured npm registry and executes its declared binary with the invoking user's permissions. The audited package currently maps the command to `bin/install.js`, and no malicious behavior was identified in that reviewed installer. However, the documented command does not guarantee that users will execute the reviewed version. A later package publication, compromised publisher account, registry compromise, or compromised mirror could replace the effective installer after this audit. The `-y` option suppresses the normal confirmation prompt, reducing the opportunity for users to review the resolved package before execution. The installer can write into agent skill directories under the user's home directory, and a malicious future package would not be limited to those legitimate operations. This is a supply-chain weakness rather than evidence that the currently audited package retrieves a hidden payload at runtime. ### Attack Path 1. An attacker compromises the npm publisher account, the configured mirror, or a future package release process. 2. The attacker publishes a modified version of `@yottameta/yotta-secret` containing a malicious executable. 3. A user follows the recommended unvers ...[truncated 1018 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin the exact reviewed package version: ```text npx -y @yottameta/yotta-secret@0.2.0 --agent <agent-name> npx -y @yottameta/yotta-secret@0.2.0 --dir <your-skills-dir> ``` 2. Publish and document package integrity hashes for release artifacts. 3. Enable npm provenance and signed release workflows where available. 4. Protect publisher accounts with phishing-resistant multi-factor authentication and tightly scoped automation tokens. 5. Avoid recommending a mutable third-party mirror as the default trust source unless its integrity and synchronization model are documented. 6. For security-sensitive environments, recommend downloading the fixed artifact, verifying its digest or signature, reviewing it, and then running the local installer. 7. Keep package versions synchronized between documentation, `package.json`, `SKILL.md`, release tags, and integrity documentation. ]]>
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
Findings (1)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documentation describes capabilities to read local files, scan .env and git history, write reports, and invoke shell commands, but no explicit permissions are declared. That mismatch increases the chance an agent will execute broader local access than users or the platform expect, which is risky because the skill is specifically designed to handle secrets and credentials. In context, the functionality is legitimate, but the undeclared capability surface makes misuse or overreach more dangerous.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.exposed_secret_literal

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
test/install.test.js:12

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
references/rules.md:65

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/test_yotta_secret.py:256