Back to skill

Security audit

Samsung Smartthings

Security checks for vulnerabilities and agentic risk

Overview

This Samsung TV skill is mostly purpose-aligned, but it uses broad SmartThings device authority and has unsafe OAuth handling that could expose credentials or device-control access.

Review before installing. Use a redirect URI you control instead of the httpbin default, create the narrowest SmartThings PAT and OAuth permissions available, understand that stored tokens can control SmartThings devices until revoked, and avoid running the npx fallback unless you trust the package source/version. Treat the generated .env file as a secret and remove or revoke credentials when no longer needed.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/setup_smartthings.py:17
Finding

OAuth Authorization Code Disclosed to Third-Party Redirect Service

Content
View full analysis
dict: return { "appName": app_name, "displayName": DEFAULT_DISPLAY_NAME, "description": DEFAULT_DESCRIPTION, "appType": "API_ONLY", "oauth": { "clientName": DEFAULT_DISPLAY_NAME, "scope": DEFAULT_SCOPES, "redirectUris": [DEFAULT_REDIRECT_URI], }, } ``` ```python parser.add_argument( "--redirect-uri", default=DEFAULT_REDIRECT_URI, help=f"Redirect URI for OAuth (default: {DEFAULT_REDIRECT_URI}).", ) ``` ```python if not args.auth_code: auth_url = build_authorize_url(auth_base, client_id, redirect_uri, scopes) print("Open this URL on your phone and complete SmartThings login:") print(auth_url) ``` The associated documentation states: ```text Redirect URI defaults to https://httpbin.org/get (can be overridden via redirect-uri option). ``` ```text The default redirect uses https://httpbin.org/get to show the code in the URL; you can switch to your own redirect URI if you don’t want to use httpbin. ``` ### Technical Analysis The default OAuth application configuration registers `https://httpbin.org/get` as its redirect URI. After authentication, SmartThings redirects the browser to that unrelated service with the OAuth authorization code in the query string. Consequently, the authorization code and associated request metadata are transmitted to infrastructure outside SmartThings and outside the user's control. The third-party service can process or log the compl ...[truncated 1893 chars]
Remediation
View remediation
/callback`, using a dynamically selected local port where SmartThings permits it. 3. Alternatively, require the user to explicitly provide a redirect URI hosted on infrastructure they control. 4. Do not offer unrelated request-inspection services as recommended OAuth redirect endpoints. 5. Generate and validate a cryptographically random OAuth `state` value to protect the authorization response from request substitution and CSRF. 6. Use PKCE with an S256 code challenge where supported, so interception of the authorization code alone is insufficient to complete the exchange. 7. Update `SKILL.md` to explain the trust and confidentiality requirements for redirect endpoints. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/setup_smartthings.py:139
Finding

Unrestricted OAuth Base URL Can Receive Client Secret and Authorization Code

Content
View full analysis
dict: token_url = base.rstrip("/") + "/token" auth_bytes = f"{client_id}:{client_secret}".encode("utf-8") auth_header = base64.b64encode(auth_bytes).decode("ascii") body = urlencode( { "grant_type": "authorization_code", "client_id": client_id, "redirect_uri": redirect_uri, "code": code, } ).encode("utf-8") request = Request( token_url, data=body, headers={ "Content-Type": "application/x-www-form-urlencoded", "Authorization": f"Basic {auth_header}", }, method="POST", ) with urlopen(request, timeout=30) as response: payload = response.read().decode("utf-8", errors="replace") ``` The destination is exposed as an unrestricted command-line option: ```python parser.add_argument( "--auth-base", default=DEFAULT_AUTH_BASE, help=f"OAuth base URL (default: {DEFAULT_AUTH_BASE}).", ) ``` It is subsequently used without scheme or hostname validation: ```python auth_base = normalize_auth_base(args.auth_base) ``` ```python token_payload = exchange_auth_code( auth_base, client_id, client_secret, redirect_uri, args.auth_code ) ``` ### Technical Analysis The `--auth-base` argument accepts an arbitrary URL. `normalize_auth_base()` only adjusts the path to contain `/oauth`; it does not enforce HTTPS or verify that the hostname belong ...[truncated 1789 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/setup_smartthings.py:41
Finding

Automatic Execution of Unpinned SmartThings CLI Package

Content
View full analysis
list[str]: if which("smartthings"): return ["smartthings"] npx_path = which("npx") if npx_path: return [npx_path, "-y", "@smartthings/cli"] raise RuntimeError("Missing SmartThings CLI. Install node (npx) or smartthings.") ``` ### Technical Analysis When a locally installed `smartthings` executable is unavailable, the script falls back to `npx -y @smartthings/cli`. The `-y` option suppresses installation confirmation, while the absence of an explicit package version permits the registry-selected version to change after this Skill has been audited. This creates a remote mutable-code execution path. The package is expected to be the SmartThings CLI, but package-registry compromise, publisher-account compromise, or a malicious future release could cause arbitrary code to execute with the user's local privileges. The CLI is launched while the setup process handles a SmartThings PAT. The PAT is also passed to the child process, making compromise of this dependency especially consequential. ### Attack Path 1. The victim's system has `npx` but no locally installed `smartthings` executable. 2. The setup script selects `npx -y @smartthings/cli`. 3. `npx` resolves and downloads the current package version from its configured registry. 4. A compromised registry response, publisher account, or package release supplies malicious package code. 5. The package executes automatically without an installation confirmation. 6. The malicious code reads the PAT passed to it, inspects the process environment, or accesses files available to the current user. 7. The attacker uses the stolen credential to access SmartThings resources within the PAT's permissions. ### Impact Assessment The downloaded packag ...[truncated 479 chars]
Remediation
View remediation
`. 3. Lock and verify dependency integrity with a trusted lockfile and registry integrity hash. 4. Avoid `npx -y` so the user receives a clear notice before external package code is downloaded and executed. 5. Verify the executable's expected source and version before providing it with credentials. 6. Run dependency installation separately from the credential-handling phase. 7. Consider executing the CLI in a restricted environment with minimal filesystem and environment-variable access. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/setup_smartthings.py:109
Finding

SmartThings Personal Access Token Exposed Through Process Arguments and Error Output

Content
View full analysis
dict: payload_path = write_payload(payload) try: cmd = [*cli, "apps:create", "--input", str(payload_path), "--json"] if token: cmd.extend(["--token", token]) result = subprocess.run( cmd, check=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) finally: payload_path.unlink(missing_ok=True) if result.returncode != 0: raise RuntimeError( "SmartThings CLI failed.\n" f"Command: {' '.join(cmd)}\n" f"stdout: {result.stdout.strip()}\n" f"stderr: {result.stderr.strip()}" ) ``` The exception is subsequently printed: ```python try: data = run_create(cli, payload, token) except RuntimeError as exc: err = str(exc) if "appName" in err and "already" in err.lower(): fallback_name = f"{DEFAULT_APP_NAME}-{uuid.uuid4().hex[:8]}" payload = build_payload(fallback_name) payload["oauth"]["redirectUris"] = [redirect_uri] payload["oauth"]["scope"] = scopes.split() data = run_create(cli, payload, token) else: print(f"Error: {exc}", file=sys.stderr) return 1 ``` ### Technical Analysis The SmartThings PAT is appended directly to the child process argument vector as the value of `--token`. Process arguments may be visible to other local users or processes through process-inspection facilities, monitoring agents, shell wrappers, debugging tools, or diagnostic collection. If the CLI exits unsuccessfully, the script includes the ent ...[truncated 1559 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (7)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill clearly describes capabilities requiring environment access, file read/write, network access, and shell/CLI execution, yet it declares no permissions. This mismatch prevents informed review and consent, and can hide a materially broad attack surface involving credential handling, local persistence, and remote service interaction.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script requests broad SmartThings scopes (r:devices:* and x:devices:*) and persists app credentials plus tokens, which grants general control over SmartThings devices beyond a narrowly TV-focused integration. In the context of a Samsung TV skill, this is overprivileged and increases blast radius if the token or local state is compromised.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

Granting generic read/execute access to all SmartThings devices is not justified by a Samsung TV-specific purpose. If misused or stolen, these credentials could operate other household devices, making the skill materially more dangerous than its stated function suggests.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill instructs users to use https://httpbin.org/get as the default OAuth redirect, which sends the authorization code to a third-party endpoint outside the SmartThings/Clawdbot trust boundary. Even if only the code is exposed, interception or logging by that service, intermediaries, browser history, or shared devices could allow token exchange and compromise the SmartThings account or app session.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The skill states that client secrets and OAuth tokens are written to a local .env file without an explicit warning about the sensitivity of those credentials. Storing long-lived secrets in plaintext on disk increases the risk of theft via local compromise, backups, logs, misconfigured permissions, or accidental sharing.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script stores client secrets, access tokens, and refresh tokens in a local .env file, creating a persistent plaintext secret store on disk. Even though it attempts to chmod 0600, users are not explicitly warned about persistence risks, backups, shell tooling exposure, or accidental inclusion in logs or source control.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
86% confidence
Finding

The skill explicitly provisions an OAuth app, exchanges an authorization code, and stores resulting credentials for later reuse, creating persistent access to the user's SmartThings environment. In this context, persistence is expected functionality, but it is still security-relevant because long-lived stored tokens can enable continued device control if the host or state directory is compromised.

Content

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

md
This skill provisions a SmartThings OAuth app and stores the credentials for Clawdbot.

Setup (one-time)
- Create the SmartThings OAuth app headlessly (requires a PAT) and print a phone login URL, using plain text instructions only.
- Open the URL on your phone, log in, then copy the code query parameter from the redirect page and re-run to exchange it.
- If PAT app creation fails (403), create the app on a normal machine using the SmartThings CLI login flow and then set the client id/secret in the .env before running the code-exchange step.
- Re-run to refresh credentials: describe the action in plain text (no code snippets).

Static analysis

No suspicious patterns detected.