T09 · Insecure Skill Coding Practices
- Location
scripts/init_repo.py:45- Finding
Gitea access token transmitted over unencrypted HTTP
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill appears to do the advertised Gitea repository setup, but it handles powerful repository credentials and shared workflow state in ways that need review before installation.
Review this before installing, especially for production or shared Gitea organizations. Require HTTPS, use a minimally scoped and rotated bot token, mask token entry, avoid system-wide dependency installation, validate usernames and helper paths, and replace the shared temp state with per-user or per-session protected state.
scripts/init_repo.py:45Gitea access token transmitted over unencrypted HTTP
requirements.txt:1Unpinned dependencies are installed into the system Python environment
main.js:19Predictable shared temporary state enables cross-session interference and symlink attacks
setup.sh:51Personal Access Token is collected with terminal echo enabled
main.js:125Unvalidated usernames are interpolated into API and repository paths
GITEA_URL is loaded from environment or .env and used directly as the destination for authenticated HTTP requests. Because the Authorization token is sent in headers, a malicious or misconfigured GITEA_URL can exfiltrate the Gitea token and all repository metadata to an attacker-controlled server; the default also uses plain HTTP, making interception easier.
}
def api_get(path):
return requests.get(f'{GITEA_URL}/api/v1{path}', headers=_headers(), timeout=15)
def api_post(path, data):
return requests.post(f'{GITEA_URL}/api/v1{path}', json=data, headers=_headers(), timeout=15)
The script posts repository creation, file contents, and issue data to a base URL fully controlled by environment configuration. If GITEA_URL is altered, sensitive data including the Gitea token, repository names, descriptions, usernames, and generated file contents can be transmitted to an attacker-controlled endpoint.
return requests.get(f'{GITEA_URL}/api/v1{path}', headers=_headers(), timeout=15)
def api_post(path, data):
return requests.post(f'{GITEA_URL}/api/v1{path}', json=data, headers=_headers(), timeout=15)
def api_put(path, data):
return requests.put(f'{GITEA_URL}/api/v1{path}', json=data, headers=_headers(), timeout=15)
PUT requests for collaborator management are sent to a URL built from untrusted environment configuration while carrying the same Authorization token. This can leak credentials and enable unauthorized privilege-management requests against unintended services if the endpoint is malicious or compromised.
return requests.post(f'{GITEA_URL}/api/v1{path}', json=data, headers=_headers(), timeout=15)
def api_put(path, data):
return requests.put(f'{GITEA_URL}/api/v1{path}', json=data, headers=_headers(), timeout=15)
# ── 邮件发送(调用 imap-smtp-email Skill)────────────────────
A second, broader description-behavior mismatch exists: the skill promises a full interactive workflow and multiple external side effects, but the available file does not implement or prove any of them, while static analysis indicates other setup or dependency actions may occur instead. This creates a supply-chain style trust problem where the manifest can socially engineer approval for behavior that is absent, incomplete, or materially different in the real code path.
A second, broader description-behavior mismatch exists: the skill promises a full interactive workflow and multiple external side effects, but the available file does not implement or prove any of them, while static analysis indicates other setup or dependency actions may occur instead. This creates a supply-chain style trust problem where the manifest can socially engineer approval for behavior that is absent, incomplete, or materially different in the real code path.
The skill description does not prominently warn that it will create repositories, modify access control, generate files, create issues, and send emails. Lack of clear disclosure undermines informed consent and increases the risk that a user triggers irreversible or organization-wide changes without realizing the consequences.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
| 变量 | 说明 |
|------|------|
| `GITEA_URL` | Gitea 服务器地址 |
| `GITEA_TOKEN` | AIFusionBot 的 Personal Access Token |
| `GITEA_ORG` | 操作的 Gitea 组织名 |
| `EMAIL_SKILL_PATH` | imap-smtp-email Skill 的绝对路径 |
| `EMAIL_ACCOUNT` | 发件邮箱账号名称(留空使用默认账号) |
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# =============================================
# project-init Skill 环境变量配置示例
# 复制本文件内容到 ~/.config/project-init/.env
# =============================================
# Gitea 服务器地址
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# =============================================
# project-init Skill 环境变量配置示例
# 复制本文件内容到 ~/.config/project-init/.env
# =============================================
# Gitea 服务器地址
The example explicitly instructs operators to provision a long-lived Gitea Personal Access Token for automation, which creates a sensitive credential dependency for a repository-management skill. In this context, the skill can create repositories, assign admin/read roles, open issues, and trigger notifications, so compromise of the token could enable broad unauthorized actions across the organization.
# Gitea 服务器地址
GITEA_URL=http://43.156.243.152:3000
# AIFusionBot 的 Personal Access Token
# 获取方式:登录 Gitea → 用户设置 → 应用 → 生成令牌
GITEA_TOKEN=your_token_here
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# ── 加载配置 ──────────────────────────────────────────────────
SKILL_DIR = Path(__file__).parent.parent
CONFIG_PATH = Path.home() / '.config' / 'project-init' / '.env'
FALLBACK_ENV = SKILL_DIR / '.env'
if CONFIG_PATH.exists():
Falling back to a .env file inside the skill directory increases the chance that secrets are bundled with the skill, exposed to other users, or accidentally committed to source control. In the context of an agent skill distributed as files, this makes credential exposure materially more dangerous than a private per-user config path.
# ── 加载配置 ──────────────────────────────────────────────────
SKILL_DIR = Path(__file__).parent.parent
CONFIG_PATH = Path.home() / '.config' / 'project-init' / '.env'
FALLBACK_ENV = SKILL_DIR / '.env'
if CONFIG_PATH.exists():
load_dotenv(CONFIG_PATH)
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_DIR="$HOME/.config/project-init"
ENV_FILE="$CONFIG_DIR/.env"
echo "========================================"
echo " project-init Skill 初始化配置"
The script requests a Gitea Personal Access Token directly from the user without masking input, so the token may be visible on-screen or captured in terminal recordings and shoulder-surfing scenarios. Because the token can grant repository and organization access, disclosure could enable unauthorized repo creation, modification, or data access within the configured Gitea environment.
read -p "Gitea 服务器地址 (默认: http://43.156.243.152:3000): " GITEA_URL
GITEA_URL="${GITEA_URL:-http://43.156.243.152:3000}"
read -p "AIFusionBot Personal Access Token: " GITEA_TOKEN
if [ -z "$GITEA_TOKEN" ]; then
echo "❌ Token 不能为空"
exit 1
The skill advertises actions that inherently require powerful capabilities (reading environment variables, filesystem access, network access, and likely shell execution) but does not declare any explicit tool scope or permission boundaries. This increases the chance of over-privileged execution and makes it harder to audit what the skill is allowed to do, especially because it can create repositories, assign roles, and send notifications.
The trigger phrase is broad enough to match ordinary conversation, which can cause accidental activation of a skill that performs high-impact actions like creating repositories and assigning permissions. In this context, unintended invocation is more dangerous than usual because the skill affects shared infrastructure and may send notifications to other users.
The natural-language description is entirely in Chinese and presents the skill as operating in that language context without any indication that users can choose another language or locale. The audit rule flags language or locale policy violations when a skill appears to force a specific language without user opt-in.
The comments and setup instructions are written entirely in Chinese, which imposes a specific language on users. Under the policy, language constraints should either be optional for the user or clearly justified as region-specific; this file provides neither.
The skill's user-facing prompts, confirmations, and status messages are written exclusively in Chinese, and the debug interface also expects Chinese interaction. There is no indication that users can opt into another language or locale, which is a natural-language policy concern when no language constraint is documented.
Line L226 tells the user that an error-details email has been sent, but in this file the failure path only returns a formatted error message after the Python script exits unsuccessfully. There is no code here that sends an email or checks script output for successful error-notification delivery, so the user-facing text actively overstates what the code can substantiate.
This Python file's top-level docstring, CLI description, status/error messages, and email content are written exclusively in Chinese, which imposes a language choice on users without opt-in. Under the policy, language constraints should either be optional or clearly justified as region-specific; neither is stated here.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
return requests.get(f'{GITEA_URL}/api/v1{path}', headers=_headers(), timeout=15)
def api_post(path, data):
return requests.post(f'{GITEA_URL}/api/v1{path}', json=data, headers=_headers(), timeout=15)
def api_put(path, data):
return requests.put(f'{GITEA_URL}/api/v1{path}', json=data, headers=_headers(), timeout=15)
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
return requests.post(f'{GITEA_URL}/api/v1{path}', json=data, headers=_headers(), timeout=15)
def api_put(path, data):
return requests.put(f'{GITEA_URL}/api/v1{path}', json=data, headers=_headers(), timeout=15)
# ── 邮件发送(调用 imap-smtp-email Skill)────────────────────
The script executes an external Node.js program whose path and optional account argument are derived from environment-controlled configuration. Although subprocess.run is used without shell=True, this still trusts EMAIL_SKILL_PATH and EMAIL_ACCOUNT from .env files, so a tampered configuration can redirect execution to attacker-controlled code and cause arbitrary code execution in the context of the skill.
cmd = ['node', str(smtp_script), '--account', EMAIL_ACCOUNT,
'send', '--to', to, '--subject', subject, '--body', body]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode != 0:
print(f'[WARN] 邮件发送失败: {result.stderr}', file=sys.stderr)
return result.returncode == 0
The command arguments passed to subprocess.run include EMAIL_ACCOUNT from environment-controlled configuration and the script path derived from EMAIL_SKILL_PATH. Even without shell expansion, this still creates a code-execution trust boundary: an attacker who can modify configuration can influence which program runs or how the helper script behaves, which is significant in an automation skill that may run with service credentials.
cmd = ['node', str(smtp_script), '--account', EMAIL_ACCOUNT,
'send', '--to', to, '--subject', subject, '--body', body]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode != 0:
print(f'[WARN] 邮件发送失败: {result.stderr}', file=sys.stderr)
return result.returncode == 0
Detected: suspicious.dangerous_exec