T09 · Insecure Skill Coding Practices
- Location
scripts/ms.sh:8- Finding
Arbitrary Command Execution Through Executable .env Loading
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This MeterSphere skill has a legitimate disclosed purpose, but it needs Review because several write and credential-handling paths are too unsafe for automatic trust.
Install only if you can tightly control the skill directory and .env file, use HTTPS-only trusted MeterSphere URLs, use least-privilege and separate read/write API keys, set the default template/version/project IDs explicitly, avoid METERSPHERE_HEADERS_JSON unless necessary, do not pass untrusted OpenAPI URLs, and avoid sharing verification output that may contain credential prefixes.
scripts/ms.sh:8Arbitrary Command Execution Through Executable .env Loading
scripts/verify_installation.py:42MeterSphere Credential Material Disclosed in Verification Output
scripts/ms.py:167Authenticated MeterSphere Traffic Can Be Sent Over Plain HTTP
scripts/ms_generate.py:16Unrestricted OpenAPI URL Retrieval Enables Internal-Network Access
scripts/ms_batch.py:143Hardcoded Tenant-Specific IDs Can Misroute Created Test Assets
The script sends authenticated outbound requests to a fully environment-controlled BASE_URL, attaching accessKey and a derived signature header. In an agent/skill context, this can exfiltrate credentials or sensitive payloads to an attacker-controlled endpoint if the environment is poisoned or the skill is repointed unexpectedly.
data = None if body is None else json.dumps(body, ensure_ascii=False).encode('utf-8')
req = request.Request(url, data=data, headers=make_headers(), method=method.upper())
try:
with request.urlopen(req) as resp:
charset = resp.headers.get_content_charset() or 'utf-8'
print(resp.read().decode(charset, errors='replace'))
except error.HTTPError as e:
The script sends authenticated requests to a fully environment-controlled BASE_URL, including accessKey and derived signature headers. If an attacker can influence environment variables or execution context, they can redirect credentials and sensitive payload data to an arbitrary host, turning the tool into an exfiltration proxy.
def request_json(method, path, body=None):
data = None if body is None else json.dumps(body, ensure_ascii=False).encode('utf-8')
req = urllib.request.Request(BASE_URL + path, data=data, headers=headers(), method=method)
with urllib.request.urlopen(req, timeout=60) as r:
return json.loads(r.read().decode('utf-8', errors='replace'))
def request_multipart(method, path, fields=None, files=None):
The multipart helper likewise transmits signed credentials and uploaded content to an environment-controlled destination. In a skill context that processes test assets and potentially internal API definitions, this can leak sensitive organizational data if configuration is tampered with.
req_headers['Content-Length'] = str(len(body))
req = urllib.request.Request(BASE_URL + path, data=body, headers=req_headers, method=method)
with urllib.request.urlopen(req, timeout=60) as r:
return json.loads(r.read().decode('utf-8', errors='replace'))
The destination BASE_URL is taken from environment/.env and used directly for authenticated outbound requests, causing the script to send accessKey and signature headers plus case-related data to whatever host is configured. In an agent-skill context, that makes exfiltration easier if the skill directory or environment is tampered with, especially because there is no allowlist, scheme enforcement, or user confirmation.
headers=headers(),
method='POST',
)
with request.urlopen(req, timeout=60) as resp:
return json.loads(resp.read().decode('utf-8', errors='replace'))
This GET path has the same issue as the POST helper: the code trusts an environment-controlled BASE_URL and performs authenticated network access without constraining the remote endpoint. In a skill ecosystem, untrusted or modified configuration can redirect requests and leak credentials or sensitive case metadata to an attacker-controlled server.
def get_json(path: str):
req = request.Request(BASE_URL + path, headers=headers(), method='GET')
with request.urlopen(req, timeout=60) as resp:
return json.loads(resp.read().decode('utf-8', errors='replace'))
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
headers=headers(),
method='POST',
)
with request.urlopen(req, timeout=60) as resp:
return json.loads(resp.read().decode('utf-8', errors='replace'))
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
headers=headers(),
method='POST',
)
with request.urlopen(req, timeout=60) as resp:
return json.loads(resp.read().decode('utf-8', errors='replace'))
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
- [ ] 记录每个密钥的权限范围和有效期
- [ ] 在生产环境中使用前,在测试环境中验证权限
### 3. 检查和控制 .env 文件
**警告**: 脚本会从技能目录加载 .env 文件
**验证**:
- [ ] 确保 .env 文件仅包含预期的环境变量
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
- [ ] 记录每个密钥的权限范围和有效期
- [ ] 在生产环境中使用前,在测试环境中验证权限
### 3. 检查和控制 .env 文件
**警告**: 脚本会从技能目录加载 .env 文件
**验证**:
- [ ] 确保 .env 文件仅包含预期的环境变量
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
- [ ] 记录每个密钥的权限范围和有效期
- [ ] 在生产环境中使用前,在测试环境中验证权限
### 3. 检查和控制 .env 文件
**警告**: 脚本会从技能目录加载 .env 文件
**验证**:
- [ ] 确保 .env 文件仅包含预期的环境变量
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
- [ ] 记录每个密钥的权限范围和有效期
- [ ] 在生产环境中使用前,在测试环境中验证权限
### 3. 检查和控制 .env 文件
**警告**: 脚本会从技能目录加载 .env 文件
**验证**:
- [ ] 确保 .env 文件仅包含预期的环境变量
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
- [ ] 记录每个密钥的权限范围和有效期
- [ ] 在生产环境中使用前,在测试环境中验证权限
### 3. 检查和控制 .env 文件
**警告**: 脚本会从技能目录加载 .env 文件
**验证**:
- [ ] 确保 .env 文件仅包含预期的环境变量
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
- [ ] 记录每个密钥的权限范围和有效期
- [ ] 在生产环境中使用前,在测试环境中验证权限
### 3. 检查和控制 .env 文件
**警告**: 脚本会从技能目录加载 .env 文件
**验证**:
- [ ] 确保 .env 文件仅包含预期的环境变量
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
- [ ] 记录每个密钥的权限范围和有效期
- [ ] 在生产环境中使用前,在测试环境中验证权限
### 3. 检查和控制 .env 文件
**警告**: 脚本会从技能目录加载 .env 文件
**验证**:
- [ ] 确保 .env 文件仅包含预期的环境变量
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
- [ ] 记录每个密钥的权限范围和有效期
- [ ] 在生产环境中使用前,在测试环境中验证权限
### 3. 检查和控制 .env 文件
**警告**: 脚本会从技能目录加载 .env 文件
**验证**:
- [ ] 确保 .env 文件仅包含预期的环境变量
The declared purpose focuses on MeterSphere test-asset management, but the skill also performs installation verification, reads local configuration files, checks file permissions, probes network connectivity, and scans for hardcoded IDs. This mismatch is security-relevant because operators may grant trust based on the high-level description without realizing the skill inspects local files and performs broader environment/network validation actions.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
SCRIPT_DIR = Path(__file__).resolve().parent
SKILL_DIR = SCRIPT_DIR.parent
ENV_FILE = SKILL_DIR / '.env'
if ENV_FILE.exists():
load_dotenv(ENV_FILE)
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
SCRIPT_DIR = Path(__file__).resolve().parent
SKILL_DIR = SCRIPT_DIR.parent
ENV_FILE = SKILL_DIR / '.env'
if ENV_FILE.exists():
load_dotenv(ENV_FILE)
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
ENV_FILE="${SKILL_DIR}/.env"
if [[ -f "$ENV_FILE" ]]; then
set -a
When required IDs are absent, the script silently substitutes hardcoded project-specific templateId and versionId values, which can cause test assets to be created in the wrong project or tenant context. In a test-management integration, that can corrupt another project's data, leak organizational information across boundaries, or trigger unauthorized changes with valid credentials.
The code is explicitly designed to read secrets from a local .env file in the skill directory, which is a credential-handling weakness because secrets may be stored in plaintext near code and become accessible to other local processes, backups, or accidental commits. In a reusable agent skill, this pattern broadens exposure of MeterSphere credentials beyond a dedicated secret-management boundary.
SCRIPT_DIR = Path(__file__).resolve().parent
SKILL_DIR = SCRIPT_DIR.parent
ENV_FILE = SKILL_DIR / '.env'
if ENV_FILE.exists():
for line in ENV_FILE.read_text(encoding='utf-8').splitlines():
The script automatically reads credentials from a repository-adjacent .env file, which encourages plaintext secret storage near code and increases the chance of accidental disclosure through source control, backups, or permissive filesystem access. In an agent skill context, this is more dangerous because skills are often shared, packaged, or inspected by automation, widening the exposure path for API secrets.
SCRIPT_DIR = Path(__file__).resolve().parent
SKILL_DIR = SCRIPT_DIR.parent
ENV_FILE = SKILL_DIR / '.env'
if ENV_FILE.exists():
for line in ENV_FILE.read_text(encoding='utf-8').splitlines():
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
- [ ] 确保 .env 文件仅包含预期的环境变量
- [ ] 检查 .env 文件中没有额外的敏感信息
- [ ] 使用 `.env.example` 作为模板创建安全的 .env 文件
- [ ] 设置适当的文件权限 (chmod 600 .env)
### 4. 警惕 HEADERS_JSON
**风险**: METERSPHERE_HEADERS_JSON 可以注入任意 HTTP 头
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
- [ ] 确保 .env 文件仅包含预期的环境变量
- [ ] 检查 .env 文件中没有额外的敏感信息
- [ ] 使用 `.env.example` 作为模板创建安全的 .env 文件
- [ ] 设置适当的文件权限 (chmod 600 .env)
### 4. 警惕 HEADERS_JSON
**风险**: METERSPHERE_HEADERS_JSON 可以注入任意 HTTP 头
The entire skill documentation is written in Chinese, including headings, instructions, warnings, and troubleshooting, with no indication that the skill is region-specific or that users may choose another language. Under the policy rule for language/locale, this is a natural-language constraint imposed without user opt-in or justification.