Tp4
- Category
- MCP Tool Poisoning
- Confidence
- 99% confidence
- Finding
声明描述的是科研项目工作区组织方法与相关脚本,关注项目目录、索引登记、跨工具映射和跨机同步纪律;而代码与这些功能没有实质关系。该代码的核心用途是管理加密凭据,并且还能在更新凭据后触发外部命令,这属于未声明且与所述科研项目工作区方法论明显不同的能力。两者的资源对象、主要用途和触发操作都不一致,因此应判定为明显不匹配。
- Content
Security audit
Security checks for vulnerabilities and agentic risk
This research-workspace skill mostly matches its stated project-organization purpose, but it also ships a sensitive credential vault with keyring access and optional command hooks that are not clearly scoped in the main skill instructions.
Install only if you are comfortable with this skill including a general-purpose credential vault. Before using creds.py, review where it stores the master key, avoid adding hooks.json unless you fully trust every command in it, and do not store sensitive credentials unless this behavior is explicitly intended for your workflow.
声明描述的是科研项目工作区组织方法与相关脚本,关注项目目录、索引登记、跨工具映射和跨机同步纪律;而代码与这些功能没有实质关系。该代码的核心用途是管理加密凭据,并且还能在更新凭据后触发外部命令,这属于未声明且与所述科研项目工作区方法论明显不同的能力。两者的资源对象、主要用途和触发操作都不一致,因此应判定为明显不匹配。
The declared description is about cross-project research workspace organization rules and scripts involving local files, Zotero, Obsidian, Feishu, project indexing, sync procedures, and chat/buffer export workflows. The supplied code does not implement any of those behaviors. Instead, it implements a generic encrypted credential vault for storing secrets, including key management via environment variables, OS keyring, and local files. This is a materially different primary purpose and introduces undeclared sensitive-capability behavior around credential handling. Therefore the description does not accurately represent the code chunk.
The file introduces a reusable credential vault CLI inside a skill whose declared purpose is research workspace methodology, not secret management. This mismatch expands the skill's privilege and behavior surface unexpectedly, increasing the chance that users or agents invoke secret-handling capabilities they did not anticipate or review.
The documentation advertises automatic post-secret-update hooks, including syncing rotated credentials to external systems, despite the skill being framed as a research workspace utility. Embedding secret-propagation automation in this context is dangerous because it normalizes hidden side effects and creates a path for credentials to be copied to additional machines or services without strong controls.
After set, the code matches the key against patterns in hooks.json and executes arbitrary command arrays via subprocess.run. This creates a direct arbitrary-code-execution mechanism tied to secret updates, which is especially sensitive because the commands run from the skill root and may access newly stored credentials or exfiltrate them.
The file implements a general-purpose secret vault that is materially outside the declared scope of a research-ops skill focused on project organization. In an agent-skill context, adding hidden credential storage and retrieval expands capability to manage sensitive secrets without a clear user-justified need, increasing the risk of unauthorized secret persistence and later abuse by other skill logic.
The documented design explicitly includes OS keyring, environment variables, and a local master-key file as credential sources, confirming that the skill has built-in credential-access and secret-management capability. In this skill context, that capability is unjustified and broadens the attack surface for secret discovery and persistence.
- 主密钥来源(按优先级):
1. 环境变量 OPENCODE_SKILL_MASTER_KEY ← 跨机同步:各设备填同一个值(首选)
2. 兼容旧环境变量 CAMPUS_MASTER_KEY / AIGW_MASTER_KEY / YUANBAO_MASTER_KEY
3. OS keyring (service=opencode-skill, account=__master_key)
4. 密钥文件 <skill>/runtime/vault/master.key (0600)
- 密文随 skill 文件夹同步到各设备;换机只需 export 同一个主密钥
- Scrypt 派生 Fernet key,每个保险箱文件独立 salt
Defining keyring-related constants is part of enabling credential retrieval/storage through the host keyring. While a constant alone is not harmful, here it is used by active secret-access logic, so it contributes to an unnecessary credential-management capability in an unrelated skill.
MASTER_KEY_ENV = "OPENCODE_SKILL_MASTER_KEY"
LEGACY_MASTER_KEY_ENVS = ("CAMPUS_MASTER_KEY", "AIGW_MASTER_KEY", "YUANBAO_MASTER_KEY")
KEYRING_SERVICE = "opencode-skill"
KEYRING_ACCOUNT = "__master_key"
SKILL_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
This constant identifies the account used for host keyring access, supporting persistent retrieval of master secrets. In the surrounding implementation, it is part of a real credential-access pathway that is out of scope for the skill and increases the risk of secret handling abuse.
MASTER_KEY_ENV = "OPENCODE_SKILL_MASTER_KEY"
LEGACY_MASTER_KEY_ENVS = ("CAMPUS_MASTER_KEY", "AIGW_MASTER_KEY", "YUANBAO_MASTER_KEY")
KEYRING_SERVICE = "opencode-skill"
KEYRING_ACCOUNT = "__master_key"
SKILL_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
RUNTIME_DIR = os.path.join(SKILL_ROOT, "runtime")
The presence of keyring cache state supports repeated host keyring probing and use, which is part of the broader secret-access behavior implemented by the module. On its own this is minor, but in context it underpins unnecessary credential operations in a non-credential skill.
VAULT_FILE = os.path.join(RUNTIME_DIR, "credentials.enc")
KEY_FILE = os.path.join(RUNTIME_DIR, "vault", "master.key")
_KEYRING_CACHE = None
class VaultLocked(Exception):
The _keyring_ok function actively tests host keyring usability, which is a form of credential-store interaction. In an agent setting, probing and using host secret stores without a manifest-declared need is risky because it expands the skill's ability to access or persist sensitive material.
# ==================== 主密钥来源 ====================
def _keyring_ok():
global _KEYRING_CACHE
if _KEYRING_CACHE is not None:
return _KEYRING_CACHE
This line is part of the keyring capability check and contributes to the active use of the host credential store. The risk is contextual rather than line-specific: the skill is implementing secret-store access outside its declared purpose.
# ==================== 主密钥来源 ====================
def _keyring_ok():
global _KEYRING_CACHE
if _KEYRING_CACHE is not None:
return _KEYRING_CACHE
try:
Importing the keyring module enables direct interaction with the host credential store. In this file, that import is not incidental—it is used for secret probing, retrieval, and storage, which is overly sensitive functionality for the declared skill scope.
def _keyring_ok():
global _KEYRING_CACHE
if _KEYRING_CACHE is not None:
return _KEYRING_CACHE
try:
import keyring
Writing a probe value into the host keyring demonstrates active modification of the credential store, not merely passive checking. This is dangerous because it performs credential-store operations without clear user awareness and establishes the capability needed to store real secrets later.
def _keyring_ok():
global _KEYRING_CACHE
if _KEYRING_CACHE is not None:
return _KEYRING_CACHE
try:
import keyring
keyring.set_password(KEYRING_SERVICE, "_probe", "1")
Reading back the probe value confirms operational access to the host keyring. Although used for testing, it still exercises secret-store read capability that is unnecessary for a research-ops methodology skill.
if _KEYRING_CACHE is not None:
return _KEYRING_CACHE
try:
import keyring
keyring.set_password(KEYRING_SERVICE, "_probe", "1")
ok = keyring.get_password(KEYRING_SERVICE, "_probe") == "1"
try:
This finding overlaps the same keyring deletion operation and is substantively the same risk: the skill manipulates the host credential store without a justified need. The concern is capability scope expansion rather than line-level exploit complexity.
return _KEYRING_CACHE
try:
import keyring
keyring.set_password(KEYRING_SERVICE, "_probe", "1")
ok = keyring.get_password(KEYRING_SERVICE, "_probe") == "1"
try:
keyring.delete_password(KEYRING_SERVICE, "_probe")
This finding overlaps the same keyring deletion operation and is substantively the same risk: the skill manipulates the host credential store without a justified need. The concern is capability scope expansion rather than line-level exploit complexity.
return _KEYRING_CACHE
try:
import keyring
keyring.set_password(KEYRING_SERVICE, "_probe", "1")
ok = keyring.get_password(KEYRING_SERVICE, "_probe") == "1"
try:
keyring.delete_password(KEYRING_SERVICE, "_probe")
This duplicate finding again reflects hidden error suppression around keyring cleanup, which can obscure persistent changes to the credential store. In a skill not meant to manage secrets, such hidden behavior is unnecessary and risky.
try:
import keyring
keyring.set_password(KEYRING_SERVICE, "_probe", "1")
ok = keyring.get_password(KEYRING_SERVICE, "_probe") == "1"
try:
keyring.delete_password(KEYRING_SERVICE, "_probe")
except Exception:
This duplicate finding again reflects hidden error suppression around keyring cleanup, which can obscure persistent changes to the credential store. In a skill not meant to manage secrets, such hidden behavior is unnecessary and risky.
try:
import keyring
keyring.set_password(KEYRING_SERVICE, "_probe", "1")
ok = keyring.get_password(KEYRING_SERVICE, "_probe") == "1"
try:
keyring.delete_password(KEYRING_SERVICE, "_probe")
except Exception:
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
keyring.set_password(KEYRING_SERVICE, "_probe", "1")
ok = keyring.get_password(KEYRING_SERVICE, "_probe") == "1"
try:
keyring.delete_password(KEYRING_SERVICE, "_probe")
except Exception:
pass
_KEYRING_CACHE = ok
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
keyring.set_password(KEYRING_SERVICE, "_probe", "1")
ok = keyring.get_password(KEYRING_SERVICE, "_probe") == "1"
try:
keyring.delete_password(KEYRING_SERVICE, "_probe")
except Exception:
pass
_KEYRING_CACHE = ok
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
keyring.set_password(KEYRING_SERVICE, "_probe", "1")
ok = keyring.get_password(KEYRING_SERVICE, "_probe") == "1"
try:
keyring.delete_password(KEYRING_SERVICE, "_probe")
except Exception:
pass
_KEYRING_CACHE = ok
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
keyring.set_password(KEYRING_SERVICE, "_probe", "1")
ok = keyring.get_password(KEYRING_SERVICE, "_probe") == "1"
try:
keyring.delete_password(KEYRING_SERVICE, "_probe")
except Exception:
pass
_KEYRING_CACHE = ok
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
keyring.set_password(KEYRING_SERVICE, "_probe", "1")
ok = keyring.get_password(KEYRING_SERVICE, "_probe") == "1"
try:
keyring.delete_password(KEYRING_SERVICE, "_probe")
except Exception:
pass
_KEYRING_CACHE = ok
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
keyring.set_password(KEYRING_SERVICE, "_probe", "1")
ok = keyring.get_password(KEYRING_SERVICE, "_probe") == "1"
try:
keyring.delete_password(KEYRING_SERVICE, "_probe")
except Exception:
pass
_KEYRING_CACHE = ok
No suspicious patterns detected.