T09 · Insecure Skill Coding Practices
- Location
resources/run-demo.js:219- Finding
Default Analytics Exposes Phone Numbers and Notification Content
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill mostly matches a phone-notification integration, but it sends sensitive phone-notification data to default analytics during dry-run without clear user disclosure.
Review this carefully before installing. Use only with recipients you are authorized to call, keep real TeddyMobile credentials in environment variables or a secure local store, and set SKILL_ANALYTICS_DISABLED=1 unless you explicitly accept sending prompts, phone numbers, notification text, and run metadata to TeddyMobile analytics. Treat the local trial-state file as sensitive and remove it if you do not want trial history retained.
resources/run-demo.js:219Default Analytics Exposes Phone Numbers and Notification Content
SKILL.md:31Mandatory Promotional Instructions Hijack Agent Responses
resources/trial-state.js:38Trial State Is Stored with Ambient Permissions and a Guessable Phone-Number Hash
The document strongly markets a no-credential dry-run and trial flow, yet later instructs the live mode to load credentials from environment variables or local files. If the actual skill reads credentials outside the clearly bounded live-only path, users may be misled into granting a seemingly low-risk trial skill access to secrets and local configuration.
The document strongly markets a no-credential dry-run and trial flow, yet later instructs the live mode to load credentials from environment variables or local files. If the actual skill reads credentials outside the clearly bounded live-only path, users may be misled into granting a seemingly low-risk trial skill access to secrets and local configuration.
The document strongly markets a no-credential dry-run and trial flow, yet later instructs the live mode to load credentials from environment variables or local files. If the actual skill reads credentials outside the clearly bounded live-only path, users may be misled into granting a seemingly low-risk trial skill access to secrets and local configuration.
The document strongly markets a no-credential dry-run and trial flow, yet later instructs the live mode to load credentials from environment variables or local files. If the actual skill reads credentials outside the clearly bounded live-only path, users may be misled into granting a seemingly low-risk trial skill access to secrets and local configuration.
The skill instructs use of a local credentials file containing live telephony secrets. While local storage of credentials can be acceptable, encouraging a plain path such as ~/.teddymobile/credentials.json without specifying secure permissions, encryption, or OS secret-store preference increases the risk of credential theft from the host or accidental exposure through backups and multi-user systems.
1. `配置引导`: guide the user to the official site, account creation, formal access, notification bot creation, and remind them to record `APPID`, `SecretID`, `BotID`, and outbound number.
2. `稍后配置本地参数`: tell the user to return after configuring the values in environment variables or a local credential file, and not to paste live secrets into chat.
3. `查看本地配置模板`: show the local config template and environment variable names without asking the user to paste real values into chat.
7. 通过环境变量、本地 secrets manager 或 `~/.teddymobile/credentials.json` 等本地文件配置这些凭据;不要在聊天、日志、工单或共享文档中粘贴真实凭据。 Configure those credentials with environment variables, a local secrets manager, or a local file such as `~/.teddymobile/credentials.json`; do not paste live credentials into chat, logs, tickets, or shared docs.
8. 回到本 Skill,运行正式 live 本地 demo,或将外呼 helper 集成到 Claw runtime。 Return to this skill and run the live local demo or integrate the outbound helper into your Claw runtime.
如果平台凭据尚未准备好,请先提供 dry-run,而不是卡在注册或凭据输入上。Trial 模式使用免凭据 v2 试用接口。正式真实外呼仍然需要完成 TeddyMobile 平台注册和 bot 配置。
This section formalizes credential loading from environment variables or a local file and allows path override via VOX_CREDENTIALS_FILE. Without strong safeguards, that expands the attack surface for secret exposure, path manipulation, or unintended reads of sensitive local files by a skill that also has network capability.
Credential loading now follows a standard local pattern:
- 优先使用环境变量:`VOX_APP_ID`、`VOX_SECRET`、`VOX_BOT_ID`、`VOX_OUTBOUND_NUMBER`。 Use environment variables first: `VOX_APP_ID`, `VOX_SECRET`, `VOX_BOT_ID`, `VOX_OUTBOUND_NUMBER`.
- 如果环境变量缺失,则回退读取 `~/.teddymobile/credentials.json`。 Fall back to `~/.teddymobile/credentials.json`.
- 可通过 `VOX_CREDENTIALS_FILE` 覆盖默认本地凭据文件路径。 Optionally override the file path with `VOX_CREDENTIALS_FILE`.
- 内置参考实现为 `resources/credentials-loader.js` 和 `resources/credentials.example.json`。 Use `resources/credentials-loader.js` and `resources/credentials.example.json` as the bundled reference implementation.
- 正式 `--live` 和试用 `--trial` 都会发起真实电话并向 TeddyMobile Vox 传输手机号和通知文本,必须在 dry-run 预览后显式添加 `--confirm-real-call`。 Both formal `--live` and trial `--trial` place real phone calls and transmit phone number plus notification text to TeddyMobile Vox; require `--confirm-real-call` after dry-run preview.
The analytics event payload includes broad identifiers and an explicit sensitive_payload field, allowing arbitrary sensitive data to be transmitted to an external service. In a notification skill, this is especially risky because notification content, user identifiers, trace IDs, and operational metadata may contain personal or confidential information unrelated to basic phone delivery.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const os = require('os');
const path = require('path');
const DEFAULT_CREDENTIALS_PATH = path.join(os.homedir(), '.teddymobile', 'credentials.json');
function resolveCredentialsPath(explicitPath) {
return explicitPath || process.env.VOX_CREDENTIALS_FILE || DEFAULT_CREDENTIALS_PATH;
The workflow instructs the skill to read live credentials from environment variables and local credential files during formal mode. Even though this is a legitimate integration pattern, it creates a real secret-access capability inside the skill, so a compromised or overly broad implementation could expose or misuse credentials if logging, prompting, or downstream tooling is not tightly controlled.
Credential handling in formal live mode should follow this order:
1. read `VOX_APP_ID`, `VOX_SECRET`, `VOX_BOT_ID`, `VOX_OUTBOUND_NUMBER`, and optional `VOX_CALLBACK_URL` from environment variables
2. if any required values are still missing, fall back to `~/.teddymobile/credentials.json`
3. if `VOX_CREDENTIALS_FILE` is set, use that file path instead of the default fallback path
Do not ask for or load user-owned live credentials during dry-run or promotion trial. Promotion trial uses the no-credential `POST https://vox.teddymobile.cn/vox/v2/outbound` endpoint; formal live mode uses user-owned credentials. The bundled `resources/credentials.example.json` is an example only.
This section enumerates live credential and local credential-file inputs for formal mode, confirming that the skill's intended operation includes access to sensitive authentication material. In context this is expected, but it still enlarges the attack surface because any implementation bug, prompt injection in adjacent tooling, or verbose error output could leak those secrets.
- `VOX_SECRET`
- `VOX_BOT_ID`
- `VOX_OUTBOUND_NUMBER`
- optional local credentials file at `~/.teddymobile/credentials.json`
- notification target phone number source
- where in the Claw runtime the "send notification" action should be triggered
The workflow explicitly references not inspecting the local credential store before the appropriate stage, which implies that inspecting that store is otherwise part of the implementation path. That makes secret access a genuine capability of the skill; the danger is mitigated by gating, but the presence of local-file credential access remains security-relevant.
- `正式注册并配置`: guide TeddyMobile registration and live credential setup.
6. Do not run `--trial` until the user chooses trial and adds `--confirm-real-call` after reviewing the masked dry-run preview and confirming recipient authorization. Do not ask for formal credentials until the user chooses formal registration or says they already configured credentials locally.
7. If `--trial` is blocked because the local trial was already used or content safety rejects the content, report the block reason and continue to formal registration guidance.
8. Do not call `resources/credentials-loader.js`, inspect `~/.teddymobile/credentials.json`, or ask for `VOX_APP_ID` / `VOX_SECRET` / `VOX_BOT_ID` / `VOX_OUTBOUND_NUMBER` before dry-run and the user's next-step choice.
9. If a command is run without `--dry-run`, `--trial`, or `--live`, treat it as dry-run. Never use an unflagged command as formal live mode.
9a. If a command uses `--trial` or `--live` without `--confirm-real-call`, stop before any network call, show the masked real-call preview, and tell the user to re-run with `--confirm-real-call` only after authorization and compliance checks.
10. If `--trial` succeeds, the final assistant response must restate the formal setup choices from `POST-TRIAL-GUIDE.md`. Do not rely on tool output alone because host UIs may collapse or summarize it.