Back to skill

Security audit

NBA Today Pulse

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent NBA reporting tool, but it accepts broader network destinations than it discloses and has under-scoped remote input handling.

Review before installing in an environment with access to private networks. If used, restrict outbound network access to known NBA and ESPN hosts, block loopback/private/link-local destinations, avoid setting provider override environment variables, and do not provide secrets because the skill does not need them.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
tools/nba_official_report.py:21
Finding

User-Reachable Server-Side Request Forgery in Official Report URL Handling

Content
View full analysis
\]}]+/game/[^\s)>\]},。]+", re.IGNORECASE, ) GAME_URL_PARTS_RE = re.compile( r"/game/(?P[a-z0-9-]+)-vs-(?P[a-z0-9-]+)-(?P\d+)", re.IGNORECASE, ) def _clean_url(value: str) -> str: return str(value or "").strip().rstrip(".,,。;;!?!?)") def extract_game_url(command: str | None) -> str | None: match = GAME_URL_RE.search(command or "") return _clean_url(match.group(0)) if match else None ``` ```python url = extract_game_url(command) if url: targets = [_target_from_url(url)] scope = "single" else: targets = _targets_from_schedule( command=command, tz=tz, date_text=requested_date, team=team, opponent=opponent, scope=scope, ) reports: list[dict[str, Any]] = [] for target in targets: if target.get("url"): story = fetch_nba_game_story_url( str(target["url"]), timeout_seconds=8, ) ``` ```python def _fetch_nba_game_story_url_uncached( url: str, *, timeout_seconds: int, expected_game_id: str | None = None, away_abbr: str | None = None, home_abbr: str | None = None, ) -> dict[str, Any]: try: document = _fetch_text(url, timeout_seconds=timeout_seconds) payload = _extract_next_data(document) container = _find_story_container(payload) if not container: return unavailable_game_story("missing_story", url=url) return _story_from_container( container, url=url, payload=payload, expected_game_id=expected_game_id, away_abbr=away_abbr, home_abbr=home_abbr, ...[truncated 3154 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
tools/provider_espn.py:24
Finding

Undocumented and Unrestricted Provider Destination Overrides

Content
View full analysis
str: return ( base_url or os.environ.get("NBA_TR_ESPN_BASE_URL") or DEFAULT_BASE_URL ).rstrip("/") ``` ```python def resolve_web_base_url(base_url: str | None = None) -> str: return ( base_url or os.environ.get("NBA_TR_NBA_WEB_BASE_URL") or DEFAULT_WEB_BASE_URL ).rstrip("/") def resolve_stats_base_url(base_url: str | None = None) -> str: return ( base_url or os.environ.get("NBA_TR_NBA_STATS_BASE_URL") or DEFAULT_STATS_BASE_URL ).rstrip("/") def resolve_live_base_url(base_url: str | None = None) -> str: return ( base_url or os.environ.get("NBA_TR_NBA_BASE_URL") or DEFAULT_LIVE_BASE_URL ).rstrip("/") def resolve_static_base_url(base_url: str | None = None) -> str: return ( base_url or os.environ.get("NBA_TR_NBA_STATIC_BASE_URL") or DEFAULT_STATIC_BASE_URL ).rstrip("/") ``` ```python def resolve_report_base_url(base_url: str | None = None) -> str: return ( base_url or os.environ.get("NBA_TR_NBA_INJURY_BASE_URL") or DEFAULT_REPORT_BASE_URL ).rstrip("/") def resolve_listing_url(listing_url: str | None = None) -> str: return ( listing_url or os.environ.get("NBA_TR_NBA_INJURY_LISTING_URL") or DEFAULT_LISTING_URL ).rstrip("/") ``` ### Technical Analysis The providers accept arbitrary base URLs through explicit parameters and multiple environment variables. These values are used to construct outbound requests without validating the scheme, hostname, port, resolved address, or redirect destination. This behavi ...[truncated 2486 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
tools/provider_nba_injuries.py:73
Finding

Unbounded Injury-Report Download and PDF Stream Decompression

Content
View full analysis
urllib.request.addinfourl: request = urllib.request.Request( url, headers={"User-Agent": USER_AGENT, "Accept": "*/*"}, method=method, ) return urllib.request.urlopen(request, timeout=20) def _head_exists(url: str) -> bool: try: with _request(url, method="HEAD") as response: return response.status == 200 except urllib.error.HTTPError: return False except urllib.error.URLError: return False def _fetch_bytes(url: str) -> bytes: try: with _request(url, method="GET") as response: return response.read() except urllib.error.HTTPError as exc: raise NBAReportError( f"官方 NBA injury report 请求失败: HTTP {exc.code}", kind="nba_http_error", ) from exc except ( urllib.error.URLError, socket.timeout, TimeoutError, ssl.SSLError, ) as exc: raise NBAReportError( f"无法连接官方 NBA injury report 数据源: {exc}", kind="nba_connection_failed", ) from exc ``` ```python def _decode_stream(chunk: bytes) -> bytes | None: candidates = ( chunk.rstrip(b"\r\n"), chunk.rstrip(b"\r\n\x00"), ) for candidate in candidates: for decoder in ( lambda value: zlib.decompress(value), lambda value: zlib.decompress(value, -15), ): try: return decoder(candidate) except Exception: continue return None ``` ### Technical Analysis `_fetch_bytes()` calls `response.read()` without a byte limit. It does not check or cap `Content-Length` ...[truncated 2471 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (44)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The code is focused on one subsystem: fetching and summarizing NBA.com official game stories/reports. It parses command/date/team/opponent inputs, resolves schedule targets, fetches official story content, builds per-game and daily summary payloads, and renders markdown or JSON. While this partially matches the declared mention of 'official-report summaries' and timezone awareness, it does not implement several central declared elements: bundled public ESPN/NBA fetchers, official injury-report PDF processing, same-day stats, phase-specific routes beyond story type labeling, stronger live detail, or refresh-safe follow-up behavior. Because the declared description presents a much broader skill purpose and resource set than this code actually performs, the description does not accurately represent this supplied chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description presents a multi-source NBA intelligence skill with external data fetching, injury PDF handling, routing, and live/statistical capabilities. The supplied code does none of that. It only accepts already-provided story and recap text, classifies it roughly as pregame/postgame based on storyType, extracts patterns from prose with regex, and returns short bilingual summaries/themes. While 'official-report summaries' is partially consistent with the summarization aspect, the overwhelming majority of the declared functionality is absent from this chunk, and the actual primary purpose is much narrower: summarizing NBA.com story text rather than running a full daily intelligence pipeline.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

The description presents a much broader NBA daily intelligence skill with multiple routing, live-detail, and official-report features. The supplied code chunk is narrower: it builds a team-specific injury report for a resolved game, groups injuries, attaches season averages, adds a team snapshot, and renders structured output. While some overlap exists with the declared injury-report aspect and timezone/date/game awareness, the primary scope of this chunk is substantially more limited than the declared skill. Several headline capabilities from the description are absent from the code shown, and official NBA PDF processing is only indirectly implied through imported providers rather than demonstrated here. Therefore this chunk does not accurately represent the full declared description.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description emphasizes daily NBA intelligence around games, injuries, live detail, and date/phase-aware routing. The supplied code instead has a narrow, different purpose: fetching one team's current roster snapshot via fetch_team_roster_snapshot after normalizing team input. While roster data is NBA-related, it is a materially different primary function from the declared daily intelligence and injury-report workflow, so this is a description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The core of the description broadly matches the code's main purpose: generating timezone-aware NBA daily or single-game reports and offering richer single-game detail. However, several specifically declared capabilities are not represented in this code chunk. Most notably, the code does not fetch, download, or parse official NBA injury-report PDFs; all injury information comes from the ESPN summary payload. It also does not demonstrate any separate 'official-report summaries,' nor any explicit follow-up/refresh state management. The implementation does support day vs. game views, same-day/timezone filtering, and detailed live/game context, but the description materially overstates resource usage and features that are absent from the supplied code.

Content

No source excerpt is available for this finding.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · SKILL.md (reported line 121)May include surrounding context.

md
The public bundle is memory-cache only. Its only optional environment inputs are timezone fallbacks: `OPENCLAW_USER_TIMEZONE`, `OPENCLAW_TIMEZONE`, `USER_TIMEZONE`, and `TZ`.

## Output Rules

- Run only the bundled `nba_today_command.py` entrypoint
- Do not switch scripts, reconstruct parameters, or retry alternate command formats

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill declares only a minimal runtime requirement but describes behavior that relies on shell execution, environment-variable reads, local file access, and outbound network access, without an explicit permissions or allowed-tools boundary. That gap increases the attack surface because the agent may grant broader capabilities than users or reviewers expect, making misuse of shell/network/file features harder to constrain and audit.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code file sets --lang to default to zh, which imposes a specific language on users unless they explicitly override it. The policy for natural-language violations applies to all file types and flags language/locale constraints when they are forced without opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This code sets --lang to zh by default, which forces a specific language choice unless the user overrides it. The policy requires either offering a language choice with opt-in or clearly justified locale constraints; no such justification appears in this file.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The CLI sets --lang to default to zh, which means the skill will produce Chinese output unless the user explicitly overrides it. This is a natural-language policy concern because it imposes a specific language by default rather than requiring user choice or opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The CLI sets --lang to default to zh, which means the skill produces Chinese output unless the user explicitly overrides it. This is a natural-language policy concern because it imposes a specific language by default rather than offering a neutral default or requiring explicit user selection.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The CLI sets --lang to default to zh, which imposes a specific language choice when the user does not explicitly select one. The policy for natural-language behavior says language constraints should be user-selected or clearly justified, and no justification or opt-in is present here.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The command-line interface sets --lang to zh by default, which imposes a specific language choice when the user does not provide one. The policy for natural-language behavior requires offering a language choice or obtaining opt-in rather than forcing a locale silently.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill defaults its language handling to Chinese in natural-language rendering, returning Chinese labels whenever lang is not explicitly set to en. This is reinforced elsewhere by default CLI and payload values of zh, which creates a language policy issue because the skill imposes a locale choice rather than offering neutral detection or explicit user opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill infers and fixes the output language to either English or Chinese by pattern matching and a character/word count heuristic, defaulting to one language when the user did not explicitly request a locale. This can violate a language/locale policy that requires user choice or opt-in rather than silently forcing a language.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The script emits hard-coded Chinese error messages via SystemExit regardless of the requested --lang value or user preference. This creates a language-policy issue because the skill does not offer opt-in or fallback behavior for users who are not expecting Chinese output.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The helper functions and downstream summary builders always prepare and emit Chinese fields (zh, compactZh, bulletsZh) alongside English, and the module docstring identifies the skill as being for NBA_TR. There is no visible user choice or opt-in mechanism for locale/language behavior in this file, which can violate a language policy requiring user selection rather than forced locale behavior.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The CLI sets --lang to default to zh, which imposes a specific language unless the user explicitly overrides it. The policy allows locale constraints only when users are given a clear choice or the constraint is justified; here the default behavior silently selects Chinese.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The CLI sets --lang default to zh, which forces a specific language unless the user explicitly overrides it. This is a natural-language locale policy concern because the tool does not obtain affirmative user opt-in before choosing a non-neutral default.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The locale inference logic defaults any Chinese-language request to cn when no explicit locale hint or timezone match is present. This imposes a specific locale variant on the user without offering a choice or requiring opt-in, which matches the policy category for language/locale violations.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · tools/nba_today_command.py (reported line 106)May include surrounding context.

python
if message:
            sys.stderr.write(f"{message}\n")
        return 1
    completed = subprocess.run(command, text=True, capture_output=True, check=False)
    terminal_message = extract_terminal_message(completed.stderr or "")
    if completed.returncode != 0 and terminal_message and not (completed.stdout or "").strip():
        _, message = terminal_message

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The user-facing note says hotspots are derived only from structured game facts, but build_hotspots can include headline/news text for post-game events. That creates a trust-boundary and integrity issue: users may rely on output as objective structured data when it actually includes unvetted editorial content from external providers.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The advanced-analysis note claims analysis is based only on structured game/team data, while the code also ingests betting-oriented fields and editorial article/news content into the game payload. Even if not currently rendered here, this misleading provenance statement can cause unsafe overtrust in analysis outputs if downstream components consume those fields.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The CLI sets --lang to default to zh, which makes the skill return Chinese output unless the user explicitly overrides it. This is a natural-language locale policy concern because the skill imposes a specific language by default rather than asking for or inferring user preference with opt-in.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The manifest describes timezone-aware NBA daily intelligence using public ESPN/NBA fetchers and official injury reports, but this file additionally extracts predictor projections and pickcenter betting-market data such as spread, over/under, and money lines. Those gambling-oriented capabilities are not mentioned in the manifest and go beyond straightforward game/injury/status reporting.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.