Back to skill

Security audit

project-assistant

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent project-analysis assistant, but it has risky file-access and credential-handling behavior that should be reviewed before installation.

Install only if you are comfortable with a project assistant that writes .claude state, scans project configuration and environment files, and stores Feishu tokens in a local JSON file. Avoid using it on untrusted repositories or projects containing secrets until path containment, secret redaction, and opt-in secret scanning are fixed.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/qa_doc_manager.py:348
Finding
Arbitrary File Read Through the Q&amp;A Document Retrieval Command<![CDATA[ ## Vulnerability Details **File Location**: `scripts/qa_doc_manager.py:348-358` and `scripts/qa_doc_manager.py:476-484` **Vulnerability Type**: Path traversal and unrestricted absolute-path file read **Risk Level**: High ### Vulnerable Code ```python def get_qa_doc_content(project_dir: str, doc_path: str) -> Optional[str]: """Read Q&A document content.""" full_path = os.path.join(project_dir, ".claude", doc_path) if os.path.exists(full_path): try: with open(full_path, 'r', encoding='utf-8') as f: return f.read() except: pass return None ``` The function is directly reachable through the command-line interface: ```python elif command == "get": if len(sys.argv) < 4: print("Usage: qa_doc_manager.py <projectDir> get <doc_path>") sys.exit(1) content = get_qa_doc_content(project_dir, sys.argv[3]) if content: print(content) else: print(json.dumps({"error": "Document does not exist"})) ``` ### Technical Analysis The `doc_path` argument comes directly from `sys.argv[3]` and is passed to `os.path.join()` without validation or canonical containment checking. An attacker can use either of the following path behaviors: 1. A path containing `../` components can escape the intended `<project>/.claude` directory. 2. On supported platforms, an absolute `doc_path` causes `os.path.join()` to discard the preceding project path components. The code only verifies that the resulting path exists. It does not verify that the canonical path remains within the intended Q&A document directory. It then prints the complete file contents to standard output. ### Attack Path 1. An attacker or untrusted instruction causes the agent to invoke the Q&A `get` command with a crafted path. 2. The attacker supplies an absolute path or a traversal path, for example: ```bash python3 scripts/qa_doc_manager.py /target/project get /etc/passwd ``` or: ```bash python ...[truncated 995 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject absolute paths supplied as document paths. 2. Resolve both the trusted root and requested path before opening the file. 3. Require the requested path to remain under `.claude/docs/qa`, not merely under the broader `.claude` directory. 4. Reject symlinks or verify containment after resolving symlinks. 5. Return a controlled error instead of suppressing all exceptions. 6. Avoid printing sensitive file content unless the document was obtained from a trusted index entry. Example hardening: ```python def get_qa_doc_content(project_dir: str, doc_path: str) -> Optional[str]: qa_root = ( Path(project_dir).resolve() / ".claude" / "docs" / "qa" ).resolve() supplied = Path(doc_path) if supplied.is_absolute(): return None candidate = (Path(project_dir).resolve() / ".claude" / supplied).resolve() try: candidate.relative_to(qa_root) except ValueError: return None if not candidate.is_file() or candidate.is_symlink(): return None return candidate.read_text(encoding="utf-8") ``` ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/qa_doc_manager.py:371
Finding
Arbitrary File Deletion Through a Poisoned Q&amp;A Index Entry<![CDATA[ ## Vulnerability Details **File Location**: `scripts/qa_doc_manager.py:163-171` and `scripts/qa_doc_manager.py:371-385` **Vulnerability Type**: Path traversal and unsafe trust in repository-controlled persistent state **Risk Level**: High ### Vulnerable Code The index is loaded without schema or path validation: ```python def load_index(project_dir: str) -> Dict[str, Any]: """Load the index.""" index_path = get_index_path(project_dir) if os.path.exists(index_path): try: with open(index_path, 'r', encoding='utf-8') as f: return json.load(f) except: pass return DEFAULT_INDEX.copy() ``` The index-controlled path is later used for deletion: ```python def delete_qa_doc(project_dir: str, entry_id: str) -> Dict[str, Any]: """Delete a Q&A document.""" index = load_index(project_dir) for i, entry in enumerate(index["entries"]): if entry["id"] == entry_id: doc_path = os.path.join(project_dir, ".claude", entry["doc_path"]) if os.path.exists(doc_path): os.remove(doc_path) index["entries"].pop(i) save_index(project_dir, index) return {"success": True, "message": "Deleted"} return {"success": False, "error": "Not found"} ``` ### Technical Analysis The code treats `.claude/index/qa_index.json` as trusted state even though it may be present in an untrusted project or modified by another local process. The `doc_path` field is passed to `os.path.join()` and then to `os.remove()` without canonicalization or containment validation. A malicious index can specify: - An absolute path, which can replace the intended project prefix - A relative path containing `../`, which can escape the `.claude` directory - A path traversing a symlink to a location outside the project The deletion operation is destructive and executes with the full filesystem privileges of the agent process. ### Attack Path ...[truncated 1245 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat every index field as untrusted input. 2. Validate the complete JSON structure and required field types before use. 3. Reject absolute `doc_path` values and traversal components. 4. Resolve the deletion target and require it to remain under `.claude/docs/qa`. 5. Reject symlinks and non-regular files. 6. Consider deriving the document path from a validated identifier instead of storing arbitrary paths. 7. Require explicit confirmation before destructive operations. 8. Create and update the index atomically to reduce tampering and corruption risks. Example containment check: ```python qa_root = ( Path(project_dir).resolve() / ".claude" / "docs" / "qa" ).resolve() stored_path = Path(entry["doc_path"]) if stored_path.is_absolute(): return {"success": False, "error": "Invalid document path"} candidate = (Path(project_dir).resolve() / ".claude" / stored_path).resolve() try: candidate.relative_to(qa_root) except ValueError: return {"success": False, "error": "Document path escapes Q&A directory"} if not candidate.is_file() or candidate.is_symlink(): return {"success": False, "error": "Invalid document target"} candidate.unlink() ``` ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/config_manager.py:96
Finding
Feishu Access Tokens Are Stored and Returned in Plaintext<![CDATA[ ## Vulnerability Details **File Location**: `scripts/config_manager.py:96-125` and `scripts/config_manager.py:138-154`; documented usage in `references/guides/config.md:22-23` and `references/guides/config.md:42-46` **Vulnerability Type**: Insecure secret storage and secret disclosure through command output **Risk Level**: High ### Vulnerable Code Configuration, including arbitrary custom values and documented Feishu tokens, is written directly to a JSON file: ```python def save_config(base_dir: str, config: Dict[str, Any]) -> bool: """Save the configuration file.""" config_path = get_config_path(base_dir) try: config["updated_at"] = datetime.now().isoformat() if not config.get("created_at"): config["created_at"] = config["updated_at"] with open(config_path, 'w', encoding='utf-8') as f: json.dump(config, f, indent=2, ensure_ascii=False) return True except IOError as e: print(f"[Error] Failed to save configuration: {e}", file=sys.stderr) return False ``` Individual values are returned without redaction: ```python def get_value(base_dir: str, key: str) -> Dict[str, Any]: """Get an individual configuration value.""" config = load_config(base_dir) keys = key.split('.') value = config for k in keys: if isinstance(value, dict) and k in value: value = value[k] else: return {"success": False, "key": key, "error": f"Configuration does not exist: {key}"} return {"success": True, "key": key, "value": value} ``` All custom configuration is also returned by the display operation: ```python def show_all(base_dir: str) -> Dict[str, Any]: """Display all configuration.""" config = load_config(base_dir) result = {"config": {}} for key in ["workdir", "project_name", "build_command", "run_command", "test_command"]: result["config"][key] = config.get(key) if config.get("preferences") ...[truncated 2249 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not store service tokens directly in the general JSON configuration. 2. Store secrets in an operating-system keyring, dedicated secret manager, or environment variable. 3. Store only a secret reference or keyring identifier in `config.json`. 4. If a local fallback is unavoidable, create the file atomically with owner-only permissions such as `0600`. 5. Detect sensitive key names such as `token`, `secret`, `password`, `api_key`, and `credential`. 6. Redact sensitive values from all `set`, `get`, `show`, error, and logging output. 7. Avoid passing secrets as command-line arguments; read them from protected standard input. 8. Document credential rotation and deletion procedures. 9. Add `config.json` to ignore rules and verify that it is not committed. 10. Rotate any token previously exposed through command output or repository history. A redacted response should resemble: ```json { "success": true, "key": "feishu.doc_token", "value": "***REDACTED***" } ``` ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/analyzers/env_scanner.py:125
Finding
Default Secret Reconnaissance Scans Sensitive Project Files Without Explicit Opt-In<![CDATA[ ## Vulnerability Details **File Location**: `scripts/analyzers/env_scanner.py:125-140`, `scripts/analyzers/env_scanner.py:145-181`, `scripts/analyzers/env_scanner.py:224-258`, and `scripts/analyzers/env_scanner.py:331-345` **Vulnerability Type**: Excessive sensitive-resource access and insufficiently disclosed secret scanning **Risk Level**: Medium ### Vulnerable Code Secret scanning is enabled by default: ```python def __init__(self, project_dir: str, scan_secrets: bool = True): self.project_dir = Path(project_dir).resolve() self.scan_secrets = scan_secrets self.env_vars: Dict[str, EnvVariable] = {} self.secrets_found: List[SecretFinding] = [] self.env_files: List[str] = [] def scan(self) -> Dict[str, Any]: """Execute the scan.""" logger.info(f"Starting environment-variable scan: {self.project_dir}") self._scan_env_files() self._scan_source_files() if self.scan_secrets: self._scan_for_secrets() return self._generate_report() ``` Environment files are read and parsed: ```python def _scan_env_files(self) -> None: """Scan environment files.""" for pattern in self.ENV_FILE_PATTERNS: for env_file in self.project_dir.glob(pattern): self._parse_env_file(env_file) self.env_files.append(str(env_file.relative_to(self.project_dir))) def _parse_env_file(self, file_path: Path) -> None: """Parse an environment file.""" try: with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: lines = f.readlines() rel_path = str(file_path.relative_to(self.project_dir)) for line_num, line in enumerate(lines, 1): line = line.strip() if not line or line.startswith('#'): continue match = re.match(r'^([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$', line) if match: name = match.group(1) value = match.group(2).strip().strip('"').strip("'") ...[truncated 3396 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable secret scanning by default. 2. Require an explicit `--scan-secrets` option rather than an opt-out `--no-secrets` option. 3. Clearly disclose which file types and credential patterns will be inspected. 4. Obtain explicit user confirmation before reading real `.env` files. 5. Prefer scanning `.env.example` and configuration templates during ordinary project analysis. 6. Add file-size, file-count, and execution-time limits. 7. Do not follow symlinks, and verify that every resolved file remains under the selected project root. 8. Allow users to define excluded files and directories. 9. Return aggregate counts by default; reveal exact secret locations only when explicitly requested. 10. Ensure no secret snippets, values, or credential-derived material are written to logs, caches, generated documentation, or external integrations. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (138)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The manifest presents the skill as '智能分析' and project Q&A, but the documented workflow for historical Q&A search/create suggests a more constrained retrieval and persistence mechanism than the headline implies. Overstating capability is dangerous because it can lead users to trust incomplete or low-fidelity answers as if they were broad, current, and deeply reasoned project analysis.

Missing User Warnings

High
Confidence
97% confidence
Finding
Feishu integration is presented as a normal feature but lacks any privacy warning that project-derived content, metadata, or recommendations may leave the local environment or be prepared for external sharing. In a project-analysis skill, this is especially risky because analyzed files, Git history, and synthesized documentation can contain proprietary code, secrets, or internal architecture details.

Credential Access

High
Category
Privilege Escalation
Content
'smtp_password', 'mail_password',
    ]

    # .env 文件模式
    ENV_FILE_PATTERNS = [
        '.env',
        '.env.local',
Confidence
90% confidence
Finding
This line begins a set of patterns explicitly targeting .env files, which commonly store API keys, passwords, and tokens. In the context of a non-security-oriented assistant skill, adding logic to discover and parse these files creates credential-access capability that can expose sensitive configuration data.

Credential Access

High
Category
Privilege Escalation
Content
# .env 文件模式
    ENV_FILE_PATTERNS = [
        '.env',
        '.env.local',
        '.env.development',
        '.env.production',
Confidence
90% confidence
Finding
Including '.env' as a direct scan target enables the tool to inspect a file type that often contains plaintext credentials. That creates a real credential-access surface, especially risky because the skill's declared purpose does not require reading secrets.

Credential Access

High
Category
Privilege Escalation
Content
# .env 文件模式
    ENV_FILE_PATTERNS = [
        '.env',
        '.env.local',
        '.env.development',
        '.env.production',
        '.env.test',
Confidence
89% confidence
Finding
Targeting '.env.local' extends the same credential-access behavior to a common local override file that frequently contains developer-specific secrets. This increases the likelihood of exposing private credentials during ordinary assistant operations.

Credential Access

High
Category
Privilege Escalation
Content
ENV_FILE_PATTERNS = [
        '.env',
        '.env.local',
        '.env.development',
        '.env.production',
        '.env.test',
        '.env.staging',
Confidence
89% confidence
Finding
Scanning '.env.development' gives the skill access to development secrets such as test database credentials and API tokens. Even non-production secrets can enable lateral movement, source-code service access, or pivoting into higher-value environments.

Credential Access

High
Category
Privilege Escalation
Content
'.env',
        '.env.local',
        '.env.development',
        '.env.production',
        '.env.test',
        '.env.staging',
        '.env.*',
Confidence
89% confidence
Finding
Including '.env.production' is particularly sensitive because production environment files may contain live credentials. In a skill not declared as a security scanner, this creates disproportionate risk of unauthorized access to operational secrets.

Credential Access

High
Category
Privilege Escalation
Content
# 配置文件模式
    CONFIG_FILE_PATTERNS = [
        '*.env',
        'config.*',
        'settings.*',
        '*.config.*',
Confidence
86% confidence
Finding
The '*.env' config-file pattern broadens discovery of environment files across the repository, increasing the chance of reading sensitive configuration material. The danger is amplified by the mismatch between capability and stated skill purpose.

Credential Access

High
Category
Privilege Escalation
Content
return self._generate_report()

    def _scan_env_files(self) -> None:
        """扫描 .env 文件"""
        for pattern in self.ENV_FILE_PATTERNS:
            for env_file in self.project_dir.glob(pattern):
                self._parse_env_file(env_file)
Confidence
92% confidence
Finding
This function actively scans for .env files and passes them for parsing, which is direct credential-access behavior. Because environment files commonly contain plaintext secrets, this materially increases confidentiality risk if the skill is invoked in routine workflows.

Credential Access

High
Category
Privilege Escalation
Content
self.env_files.append(str(env_file.relative_to(self.project_dir)))

    def _parse_env_file(self, file_path: Path) -> None:
        """解析 .env 文件"""
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                lines = f.readlines()
Confidence
94% confidence
Finding
Parsing .env files means the code reads lines that may contain secrets and stores associated metadata, masking only some values later. Reading sensitive files at all is credential-access capability, and partial redaction does not remove the underlying exposure risk.

Credential Access

High
Category
Privilege Escalation
Content
for f in files:
                ext = Path(f).suffix.lower()
                if ext in {'.py', '.js', '.ts', '.jsx', '.tsx', '.java', '.go', '.json', '.yaml', '.yml', '.env'}:
                    file_path = Path(root) / f
                    self._find_secrets_in_file(file_path)
Confidence
91% confidence
Finding
This line includes '.env' in the set of file types scanned for sensitive values, meaning the scanner inspects secret-bearing files directly for credentials and tokens. In a general project-assistant skill, that is an unnecessary and risky credential-discovery capability.

Credential Access

High
Category
Privilege Escalation
Content
"""生成安全建议"""
        recommendations = []

        # 检查是否有 .env 文件
        if self.env_files:
            recommendations.append("确保 .env 文件已添加到 .gitignore")
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Static analysis

No suspicious patterns detected.