Back to skill

Security audit

WB Open Builder

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent WorkBuddy asset generator, but it ships OAuth callback templates and a validator with security gaps that users should review before relying on it.

Before installing or using this skill, treat its generated assets as drafts. Do not deploy the bundled OAuth callback templates to production without adding strict token-endpoint allowlisting, HTTPS enforcement, HTML escaping, session-bound one-time state, PKCE where supported, durable encrypted token storage, and a stable configured state secret. Run an independent secret/security scan on generated skill packages, and require explicit confirmation for file writes, overwrites, and high-impact financial or account-authority behavior.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
templates/buddy_oauth_callback_local.py:42
Finding
OAuth credentials may be transmitted to an arbitrary or plaintext token endpoint<![CDATA[ ## Vulnerability Details **File Locations**: - `templates/buddy_oauth_callback_local.py:42,86-105` - `templates/buddy_oauth_callback_scf.py:34,70-90` **Vulnerability Type**: Unvalidated sensitive-data destination and insecure transport **Risk Level**: High ### Vulnerable Code Local callback implementation: ```python TOKEN_ENDPOINT = os.environ.get("WB_TOKEN_ENDPOINT", "") def exchange_code(code: str) -> dict: """Authorization-code exchange.""" if not TOKEN_ENDPOINT: raise RuntimeError( "WB_TOKEN_ENDPOINT is not configured" ) form = { "grant_type": "authorization_code", "code": code, "redirect_uri": REDIRECT_URI, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, } req = urllib.request.Request( TOKEN_ENDPOINT, data=urllib.parse.urlencode(form).encode("utf-8"), headers={ "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json", }, method="POST", ) with urllib.request.urlopen(req, timeout=15) as resp: return json.loads(resp.read().decode("utf-8")) ``` Serverless callback implementation: ```python TOKEN_ENDPOINT = os.environ.get("WB_TOKEN_ENDPOINT", "") def exchange_code(code: str) -> dict: """Exchange an authorization code for a token.""" if not TOKEN_ENDPOINT: raise RuntimeError( "WB_TOKEN_ENDPOINT is not configured" ) form = { "grant_type": "authorization_code", "code": code, "redirect_uri": REDIRECT_URI, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, } req = urllib.request.Request( TOKEN_ENDPOINT, data=urllib.parse.urlencode(form).encode("utf-8"), headers={ "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json", }, method="POST", ) with urllib.request. ...[truncated 2336 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the endpoint before processing any callback: ```python from urllib.parse import urlsplit ALLOWED_TOKEN_HOSTS = { "approved-token-host.example", } def validate_token_endpoint(value: str) -> str: parsed = urlsplit(value) if parsed.scheme != "https": raise RuntimeError("The OAuth token endpoint must use HTTPS") if parsed.hostname not in ALLOWED_TOKEN_HOSTS: raise RuntimeError("The OAuth token endpoint host is not approved") if parsed.username or parsed.password: raise RuntimeError("Embedded URL credentials are prohibited") if parsed.fragment: raise RuntimeError("Token endpoint fragments are prohibited") if parsed.port not in (None, 443): raise RuntimeError("Unexpected token endpoint port") return value ``` 2. Prefer a fixed endpoint supplied by trusted application configuration rather than a freely configurable URL. 3. Validate the endpoint during application startup so an unsafe deployment fails before accepting callbacks. 4. Use TLS certificate validation and do not disable Python's default certificate checks. 5. Prevent or tightly control redirects during token exchange. If redirects are required, revalidate every destination before transmitting sensitive data. 6. Never include authorization codes, secrets, or full tokens in logs or error responses. 7. Add tests that reject HTTP, loopback, link-local, private-network, credential-bearing, and unapproved-host URLs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
templates/buddy_oauth_callback_local.py:162
Finding
Reflected HTML injection in OAuth error responses<![CDATA[ ## Vulnerability Details **File Locations**: - `templates/buddy_oauth_callback_local.py:162-166` - `templates/buddy_oauth_callback_scf.py:119-122` **Vulnerability Type**: Reflected cross-site scripting **Risk Level**: Medium ### Vulnerable Code Local callback implementation: ```python if "error" in q: desc = q.get("error_description", [""])[0] self._send_html( "Authorization incomplete", "<p>Authorization server error: <code>%s</code> %s</p>" "<p>Please return to WorkBuddy and retry.</p>" % (q["error"][0], desc), status=400, ) return ``` Serverless callback implementation: ```python if q.get("error"): desc = q.get("error_description", "") return _resp( 400, "Authorization incomplete", "<p>Authorization server error: <code>%s</code> %s</p>" "<p>Please return to WorkBuddy and retry.</p>" % (q["error"], desc), ) ``` The original files use localized user-facing strings, but the vulnerable interpolation behavior is shown unchanged. ### Technical Analysis The `error` and `error_description` query parameters are untrusted input. Both implementations interpolate these values directly into an HTML response without HTML escaping. The error branch is processed before state validation. An attacker therefore does not need a valid OAuth transaction or signed state value to reach the vulnerable response. A payload containing an HTML element, event handler, or script-capable construct can be reflected into the page served by the trusted callback origin. The templates also omit a Content Security Policy that could reduce exploitation. ### Attack Path 1. An attacker constructs a callback URL such as: ```text https://trusted-callback.example/oauth/callback?error=denied&error_description=<img src=x onerror=alert(document.domain)> ``` 2. The attacker convinces a user or administrator to open the URL. 3. The callback enters the OAuth error branch withou ...[truncated 960 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every untrusted value before placing it in HTML: ```python import html error = html.escape(str(q.get("error", ["unknown"])[0]), quote=True) description = html.escape( str(q.get("error_description", [""])[0]), quote=True, ) ``` 2. Prefer fixed user-facing messages. Log only a sanitized provider error identifier rather than reflecting arbitrary descriptions. 3. Validate `error` against a conservative pattern or an explicit OAuth error allowlist. 4. Require and validate state on error callbacks when the provider returns state as required by OAuth. 5. Add restrictive response headers: ```python headers = { "Content-Type": "text/html; charset=utf-8", "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; " "base-uri 'none'; frame-ancestors 'none'; form-action 'none'", "X-Content-Type-Options": "nosniff", "Referrer-Policy": "no-referrer", } ``` 6. Add automated tests using payloads containing `<script>`, event handlers, quotes, ampersands, and encoded markup. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
templates/buddy_oauth_callback_local.py:53
Finding
OAuth state is reusable and is not bound to a user session or authorization transaction<![CDATA[ ## Vulnerability Details **File Locations**: - `templates/buddy_oauth_callback_local.py:53-76,145-162,174-180` - `templates/buddy_oauth_callback_scf.py:43-61,126-131,155-169` **Vulnerability Type**: Weak OAuth state management and replay protection **Risk Level**: Medium ### Vulnerable Code The local implementation generates a self-contained, reusable state value: ```python def make_state() -> str: """Generate signed state: timestamp.nonce.signature.""" payload = "%d.%s" % (int(time.time()), secrets.token_hex(8)) sig = hmac.new( STATE_SECRET.encode(), payload.encode(), hashlib.sha256, ).hexdigest()[:24] return "%s.%s" % (payload, sig) def check_state(state: str, max_age_sec: int = 600) -> bool: """Check that state has a valid signature and has not expired.""" try: ts, nonce, sig = state.split(".") payload = "%s.%s" % (ts, nonce) except ValueError: return False expect = hmac.new( STATE_SECRET.encode(), payload.encode(), hashlib.sha256, ).hexdigest()[:24] if not hmac.compare_digest(expect, sig): return False try: return (time.time() - int(ts)) < max_age_sec except ValueError: return False ``` The public index creates a state value without establishing a user session: ```python query = urllib.parse.urlencode({ "response_type": "code", "client_id": CLIENT_ID, "redirect_uri": REDIRECT_URI, "state": make_state(), }) ``` The callback only validates the signature and age: ```python if not check_state(state): self._send_html( "Security validation failed", "<p>The state validation failed.</p>", status=400, ) return token = exchange_code(code) ``` The SCF implementation uses the same state construction and validation model. ### Technical Analysis OAuth state should correlate one callback with one authorization request initiated by a particular browse ...[truncated 2527 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Generate at least 128 bits of random state and store only a hash of it in a server-side transaction store. 2. Associate each pending state with: - The authenticated application user or browser session. - Creation and expiration times. - The intended redirect URI. - Requested scopes. - A PKCE code verifier. - An explicit unused/consumed status. 3. On callback, look up and atomically consume the state before exchanging the code. 4. Reject unknown, expired, already consumed, or session-mismatched states. 5. Enforce both lower and upper timestamp bounds if timestamps remain part of the design. 6. Use PKCE with `S256` when supported. 7. Do not expose a production authorization-link generator through an unauthenticated health-check page. 8. Associate stored tokens with an explicit internal user and authorization grant identifier. 9. Use a durable, encrypted datastore for production tokens rather than process-global memory. 10. Configure a stable `WB_STATE_SECRET` for serverless deployments if signed state is retained; random secrets generated during cold starts invalidate outstanding flows. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/validate_asset.py:87
Finding
The mandatory validator does not scan Skill assets for hardcoded credentials<![CDATA[ ## Vulnerability Details **File Location**: `scripts/validate_asset.py:87-99,102-151,404-405` **Vulnerability Type**: Incomplete credential validation and false security assurance **Risk Level**: Medium ### Vulnerable Code A credential scanner is implemented: ```python def check_security(root: Path): for f in root.rglob("*"): if not f.is_file() or f.suffix in { ".png", ".jpg", ".svg", ".ico", ".zip" }: continue try: text = f.read_text(encoding="utf-8") except Exception: continue text = re.sub( r"\$\{[A-Za-z0-9_]+\}", "PLACEHOLDER", text, ) text = re.sub( r"\{\{[A-Za-z0-9_]+\}\}", "PLACEHOLDER", text, ) for pattern, label in SECRET_PATTERNS: m = re.search(pattern, text) if m: add( "FAIL", f"Security scan: {f.relative_to(root)}", f"{label}; replace it with a ${{VAR}} placeholder", ) ``` However, `check_skill()` ends without invoking it: ```python def check_skill(root: Path): skill_md = None for cand in [ root / "SKILL.md", root / f"{root.name}" / "SKILL.md", ]: if cand.is_file(): skill_md = cand break if skill_md is None: md_files = list(root.rglob("SKILL.md")) if md_files: skill_md = md_files[0] add( "WARN", "SKILL.md location", f"Located under {skill_md.parent.name}/", ) if skill_md is None: add( "FAIL", "SKILL.md existence", "SKILL.md was not found", ) return fm = parse_frontmatter(skill_md) if fm is None: return for field in [ "description", "description_zh", "des ...[truncated 3753 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Invoke the security scanner for Skill assets: ```python def check_skill(root: Path): try: # Existing validation logic ... finally: check_security(root) ``` Alternatively, run `check_security(root)` once from `main()` for every supported asset type so future validators cannot accidentally omit it: ```python validator = { "skill": check_skill, "connector": check_connector, "expert": check_expert, "team": check_team, }[args.type] validator(root) check_security(root) ``` 2. Remove duplicate per-type scanner calls if the centralized approach is used. 3. Scan text-based SVG files with size limits and safe text decoding. 4. Add detection for: - Generic secret assignments such as `password=`, `client_secret=`, and `api_key=`. - Private keys. - Provider-specific credentials. - High-entropy strings near credential field names. 5. Scan archives in a controlled temporary directory with path-traversal and decompression-size protections if packaged assets are accepted. 6. Emit a clear warning that pattern matching is not proof that an asset contains no secrets. 7. Add regression tests demonstrating that the same test token fails validation for all four asset types. 8. Treat unreadable candidate text files as warnings rather than silently skipping them. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (11)

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The template’s team design document defines a 4-phase workflow and a specific set of roles, but the later lead-agent SOP expands to 5 phases and introduces additional undeclared members. This inconsistency can cause generated team assets to be structurally invalid, orchestrate nonexistent agents, or bypass intended review steps, weakening reliability and policy enforcement in downstream generated skills.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The plugin.json example references different agents across the agents array, teamInfo.memberAgents, and members list, including members without corresponding files and files without matching role declarations. In a skill generator, this can produce broken or miswired team packages that invoke unintended roles or fail at runtime, undermining the integrity of generated assets.

Intent-Code Divergence

Medium
Confidence
92% confidence
Finding
The lead prompt states the lead must not perform professional analysis, yet later requires the lead to output final BUY/SELL/HOLD recommendations and action plans without a fully defined supporting role chain. That contradiction encourages policy drift where the orchestrator may synthesize ungrounded financial advice, defeating separation-of-duties controls intended by the team design.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The trigger list includes broad phrases such as '开放平台', '生成技能', and '上架', which are common task terms and can cause the skill to activate in contexts broader than intended. Over-broad invocation increases the chance that the skill handles unrelated requests with powerful file and Bash capabilities, expanding exposure to prompt-injection or unsafe automation pathways.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The template prescribes a fixed bilingual output scheme ('正文中文,display_name 补英文') without indicating that language should follow user preference or explicit consent. In an asset-generation skill, this can cause unwanted language imposition, reduce accessibility or usability for non-Chinese contexts, and lead to non-compliant outputs when platform or customer requirements mandate different localization behavior.

Vague Triggers

Medium
Confidence
86% confidence
Finding
The trigger phrase includes a broad everyday expression, “本周总结”, which can match ordinary conversation and cause the skill to activate when the user did not explicitly intend to invoke it. In a skill-generation context, overbroad activation increases the chance of unintended execution paths, confusing UX, and accidental processing of user content under the wrong workflow.

Missing User Warnings

Medium
Confidence
80% confidence
Finding
The template instructs the skill to write a generated weekly report to disk without stating that the user should be informed or asked to confirm the destination and write action. Even though the output is not inherently sensitive, automatic persistence can create privacy, overwrite, or unintended data-retention risks if user work logs or task data are stored silently.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The template explicitly describes generating structured research reports with actionable operating suggestions for retail investors, but provides no risk warning, suitability limitation, or financial-advice safety framing. In this skill context, that increases the chance the generated asset will present high-stakes investment guidance as authoritative output without appropriate guardrails.

Vague Triggers

Medium
Confidence
83% confidence
Finding
The description placeholder explicitly tells authors to include the skill's purpose and trigger words in a single brief sentence, but provides no constraints on precision, specificity, or safe activation boundaries. In a platform that auto-routes or suggests skills based on descriptions, vague or broad trigger wording can cause unintended activation, misrouting, or overbroad invocation of capabilities.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The placeholder '当用户需要 {{触发场景}} 时' invites authors to define activation conditions in free-form, potentially ambiguous language without requiring eligibility criteria, disambiguation rules, or negative conditions. This increases the risk that downstream agents or orchestration logic will invoke the skill in situations it was not designed or authorized to handle.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
AUTH_ENDPOINT = os.environ.get("WB_AUTH_ENDPOINT", "")
TOKEN_ENDPOINT = os.environ.get("WB_TOKEN_ENDPOINT", "")
REDIRECT_URI = os.environ.get("WB_REDIRECT_URI", "")
STATE_SECRET = os.environ.get("WB_STATE_SECRET", "") or secrets.token_hex(16)

# 演示用内存令牌库(同一函数实例复用期间有效,冷启动后清空)。
# 生产环境请改用加密数据库 / KMS,按用户维度隔离,禁止明文落盘。
Confidence
77% confidence
Finding
If WB_STATE_SECRET is absent, the code generates a fresh random secret at cold start, which makes state validation dependent on a single warm instance. In serverless environments with multiple instances or cold starts between authorization initiation and callback, legitimate callbacks may fail unpredictably, weakening reliability of CSRF protection and encouraging operators to disable or bypass the check.

Static analysis

No suspicious patterns detected.