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

Content
View full analysis
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
View remediation
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

Content
View full analysis
Authorization server error: %s %s

" "

Please return to WorkBuddy and retry.

" % (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", "

Authorization server error: %s %s

" "

Please return to WorkBuddy and retry.

" % (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= ``` 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
View remediation
`, 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

Content
View full analysis
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", "

The state validation failed.

", 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
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/validate_asset.py:87
Finding

The mandatory validator does not scan Skill assets for hardcoded credentials

Content
View full analysis
Remediation
View remediation
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
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Env Variable Harvesting

High
Category
Data Exfiltration
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.

Content

Scanner excerpt · templates/buddy_oauth_callback_scf.py (reported line 36)May include surrounding context.

python
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,按用户维度隔离,禁止明文落盘。