Back to skill

Security audit

StepFun step-audio-r1.1

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it says, but it allows the StepFun API destination to be changed in a way that could expose your API key, prompts, and selected audio to an unintended server.

Review before installing. Use only with a trusted StepFun key and avoid sending sensitive prompts or private audio unless your organization permits StepFun processing. Do not set STEP_API_BASE_URL or pass --api-base-url unless you have verified it is the intended trusted HTTPS StepFun endpoint; prefer a revised version that validates or removes custom API destinations.

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 (1)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/stepfun_audio_chat.py:86
Finding

Unrestricted API Base URL Can Expose Credentials and User Data

Content
View full analysis

Vulnerability Details

File Location: scripts/stepfun_audio_chat.py:86-89, 284-305, 313-334, 404, 433
Vulnerability Type: Unvalidated destination override causing credential and sensitive-data disclosure
Risk Level: High

Vulnerable Code

python
parser.add_argument(
    "--api-base-url",
    default=os.environ.get("STEP_API_BASE_URL", DEFAULT_API_BASE_URL),
    help=f"API base URL. Default: {DEFAULT_API_BASE_URL}.",
)
python
def post_json(url: str, api_key: str, payload: Dict[str, Any]) -> Dict[str, Any]:
    body = json.dumps(payload).encode("utf-8")
    req = request.Request(
        url,
        data=body,
        method="POST",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
            "Accept": "application/json",
        },
    )
    try:
        with request.urlopen(req) as response:
            raw = response.read().decode("utf-8")
python
def get_json(url: str, api_key: str) -> Dict[str, Any]:
    req = request.Request(
        url,
        method="GET",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Accept": "application/json",
        },
    )
    try:
        with request.urlopen(req) as response:
            raw = response.read().decode("utf-8")
python
voices = get_json(list_voices_url(args.api_base_url, args.voice_limit), api_key)
python
response = post_json(api_url(args.api_base_url), api_key, payload)

Technical Analysis

The script permits the request destination to be overridden through either the --api-base-url command-line argument or the STEP_API_BASE_URL environment variable. It does not validate the URL scheme, hostname, port, embedded credentials, or path.

Both network request functions unconditionally attach the StepFun API key as a bearer token. The chat request also sends the complete JSON payload, which can contain the user's prompt, an optional system instruction, a ...[truncated 2427 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove the custom API-base override if it is not essential to the declared functionality, and always use:

    python
    DEFAULT_API_BASE_URL = "https://api.stepfun.com"
    
  2. If an override is operationally necessary, parse and validate it before any credential is resolved or request is created:

    • Require the https scheme.
    • Allowlist api.stepfun.com.
    • Reject embedded usernames or passwords.
    • Reject fragments and unexpected query strings.
    • Reject unexpected ports.
    • Reject non-root path prefixes that could alter endpoint construction.
    • Normalize the hostname before comparing it with the allowlist.
  3. Keep authorization scoped to trusted destinations. Before adding the bearer header, independently verify that the final request URL remains on an approved HTTPS origin.

  4. If private enterprise endpoints must be supported, require an explicit high-friction opt-in and use separate endpoint-specific credentials rather than forwarding the StepFun key.

  5. Avoid following cross-origin redirects while retaining the authorization header. Explicitly reject redirects to origins other than the validated API origin.

  6. Add automated security tests covering:

    • Plain HTTP URLs.
    • Unapproved hosts and deceptive subdomains.
    • Embedded credentials.
    • Alternate ports.
    • Cross-origin redirects.
    • Environment-variable overrides.
    • Both chat and voice-list requests.
  7. Clearly warn users that prompts and selected audio are transmitted to an external service, while retaining --dry-run as the non-network inspection option.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (9)

Tainted flow: 'req' from os.environ.get (line 314, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

The script allows the destination API base URL to be overridden from an environment variable or CLI argument and then sends the bearer token in the Authorization header to that URL. If an attacker can influence STEP_API_BASE_URL or --api-base-url, they can redirect requests and exfiltrate the StepFun API key and any prompt/audio data to an attacker-controlled endpoint.

Content

Scanner excerpt · scripts/stepfun_audio_chat.py (reported line 297)May include surrounding context.

python
},
    )
    try:
        with request.urlopen(req) as response:
            raw = response.read().decode("utf-8")
    except error.HTTPError as exc:
        detail = exc.read().decode("utf-8", errors="replace")

Tainted flow: 'req' from os.environ.get (line 314, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

The voice-listing GET request uses the same attacker-influenceable base URL and includes the bearer token in the Authorization header. This enables credential exfiltration and disclosure of account metadata if the base URL is redirected to an untrusted host.

Content

Scanner excerpt · scripts/stepfun_audio_chat.py (reported line 323)May include surrounding context.

python
},
    )
    try:
        with request.urlopen(req) as response:
            raw = response.read().decode("utf-8")
    except error.HTTPError as exc:
        detail = exc.read().decode("utf-8", errors="replace")

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill advertises capabilities that include environment access, file read/write, network access, and shell execution, but it does not declare any explicit tool scope or permission boundaries. That creates an over-privileged integration surface where an agent may invoke the skill with broader capabilities than users expect, increasing the chance of unintended file access, command execution, or external data transmission.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill clearly sends user prompts and optional local audio to an external API, but the description does not prominently warn about the privacy and data-handling implications of transmitting potentially sensitive text or speech off-host. In this context, the risk is elevated because audio input may contain personal, confidential, or regulated information, and users may not realize that local files are being encoded and uploaded to a third party.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/stepfun-chat-api.md (reported line 11)May include surrounding context.

md
## Endpoint

- Method: `POST`
- URL: `https://api.stepfun.com/v1/chat/completions`

## Request Fields Used By This Skill

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The documentation explicitly instructs use of API credentials for a third-party service but does not warn users that chat text and optional audio will be transmitted off-system to StepFun. In a speech skill, this omission matters because audio and transcripts may contain sensitive personal or enterprise data, so users may unknowingly exfiltrate content to an external provider.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The manifest describes a skill for sending text with optional local audio input to step-audio-r1.1 and saving the returned audio, transcript, and raw response. The added --list-voices mode changes the skill into an account metadata enumerator by calling the voices API and printing custom/cloned voice IDs, which is outside that stated request/response artifact workflow.

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 · scripts/stepfun_audio_chat.py (reported line 186)May include surrounding context.

python
"or install ffmpeg/afconvert so this script can convert it."
            )

        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode != 0 or not wav_path.is_file():
            stderr = result.stderr.strip()
            stdout = result.stdout.strip()

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

Building and calling /v1/audio/voices lets the script retrieve account-specific voice assets unrelated to the manifest's stated purpose of performing non-streaming speech turns and saving outputs. This is a separate capability that exposes additional account metadata and is not an obvious requirement for completing a single audio chat completion request.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.