Back to skill

Security audit

API Failover

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent AI failover proxy, but it can expose credential-backed model access through an unauthenticated local HTTP service and broad local discovery, so it needs careful review before use.

Install only if you intend to run a credential-backed local AI routing proxy. Keep it bound to 127.0.0.1 unless you add real authentication and network controls, review every configured upstream provider because prompts may be forwarded there, and avoid running the scripts with elevated privileges until the /tmp file handling and health endpoint exposure are hardened.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/http_proxy.py:166
Finding
Unauthenticated HTTP Proxy Exposes Credential-Backed AI Provider Access<![CDATA[ ## Vulnerability Details **File Location**: `scripts/http_proxy.py:166-213`, `scripts/http_proxy.py:237-248`, `scripts/failover_proxy.py:156-186` **Vulnerability Type**: Missing authentication and access control **Risk Level**: High when bound to a network-accessible interface; Low with the default loopback-only binding ### Vulnerable Code ```python def do_GET(self): parsed = urlparse(self.path) if parsed.path == '/health': config, state = self.app.load() return self._json(200, { 'ok': True, 'profiles': list(config.get('task_profiles', {}).keys()), 'providers': list(config.get('providers', {}).keys()), 'state_file': self.app.state_file, 'state': state, }) return self._json(404, {'error': 'NOT_FOUND'}) def do_POST(self): parsed = urlparse(self.path) if parsed.path not in ('/v1/chat/completions', '/chat/completions'): return self._json(404, {'error': 'NOT_FOUND'}) length = int(self.headers.get('Content-Length', '0')) raw = self.rfile.read(length) try: payload = json.loads(raw.decode('utf-8')) if raw else {} except Exception: return self._json(400, {'error': 'INVALID_JSON'}) profile, profile_source, profile_reason = resolve_profile( self.headers, payload, fallback=self.app.default_profile ) try: config, state = self.app.load() result = call_with_failover(config, state, profile, payload) self.app.save(state) except KeyError as e: return self._json(400, { 'error': 'UNKNOWN_PROFILE_OR_PROVIDER', 'detail': str(e) }) except Exception as e: return self._json(500, { 'error': 'PROXY_ERROR', 'detail': str(e) }) ``` ```python ap.add_argument('--config', required=True) ap.add_argument('--state-file', default='/tmp/api-failover-state.json') ap.add_argument('--profile', default='default') ap ...[truncated 4270 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require authentication for all nontrivial endpoints: - Accept a dedicated proxy bearer token. - Compare tokens using `hmac.compare_digest`. - Prefer mTLS for shared or production environments. - Do not reuse upstream provider credentials as proxy-client credentials. 2. Enforce safe binding: - Refuse non-loopback `--host` values unless authentication is explicitly configured. - Emit a prominent warning before listening on a non-loopback interface. - Document firewall and network-segmentation requirements. 3. Add authorization controls: - Restrict which clients may select `critical` or other expensive profiles. - Consider ignoring client-selected routing hints unless explicitly enabled. - Apply per-client provider, model, token, and cost limits. 4. Add abuse controls: - Enforce a maximum `Content-Length` before reading the request body. - Add rate limiting, concurrency limits, and request timeouts. - Enforce maximum token and message-size limits. 5. Minimize health output: - Return only a basic readiness result to unauthenticated callers. - Protect detailed provider and circuit-breaker diagnostics with administrative authentication. - Do not disclose local state-file paths over the network. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/bootstrap_failover.py:48
Finding
Predictable Temporary Files Permit Symlink-Based File Overwrites and Metadata Exposure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/bootstrap_failover.py:48-58`, `scripts/bootstrap_failover.py:70-75`, `scripts/activate_secondary.py:12-14`, `scripts/activate_secondary.py:75-81`, `scripts/failover_proxy.py:28-52`, `scripts/failover_proxy.py:355` **Vulnerability Type**: Unsafe temporary-file handling and non-atomic shared state **Risk Level**: Medium ### Vulnerable Code The bootstrap process creates a predictable PID-based discovery file and uses a fixed log path: ```python report = {'steps': []} discovery_tmp = f'/tmp/api-failover-discovery-{os.getpid()}.json' discover = run_json([sys.executable, str(SCRIPTS / 'discover_env.py')]) report['steps'].append({'discover_env': discover}) if discover.get('ok'): Path(discovery_tmp).write_text( json.dumps(discover.get('data', {}), ensure_ascii=False, indent=2), encoding='utf-8' ) gen = run_json([ sys.executable, str(SCRIPTS / 'generate_config.py'), '--default-model', args.default_model, '--output', args.config_out, '--discovery-json', discovery_tmp, ]) ``` ```python if args.start_proxy: log_path = '/tmp/api-failover-http.log' with open(log_path, 'a', encoding='utf-8') as logf: proc = subprocess.Popen([ sys.executable, str(SCRIPTS / 'http_proxy.py'), '--config', args.config_out, '--host', args.host, '--port', str(args.port), '--state-file', args.state_file, ], stdout=logf, stderr=logf) ``` The activation drill uses fixed paths: ```python STATE = Path('/tmp/api-failover-activation-drill-state.json') ENV_FILE = Path('/root/.config/api-failover.env') ``` ```python payload = Path('/tmp/api-failover-activation-payload.json') payload.write_text(json.dumps({ 'messages': [{'role': 'user', 'content': 'Reply with exactly: ok'}], 'max_tokens': 16, 'temperature': 0, }, ensure_ascii=False), encoding='utf-8') ``` The circuit-breaker state is also written directly ...[truncated 4272 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace predictable temporary files with secure APIs: - Use `tempfile.NamedTemporaryFile` or `tempfile.mkstemp`. - Create files with mode `0600`. - Use unpredictable names generated by the operating system. - Delete temporary discovery and payload files in a `finally` block. 2. Move persistent runtime files out of shared `/tmp`: - Use `$XDG_RUNTIME_DIR/api-failover/` for transient state and logs. - Use a dedicated user-owned application directory when runtime storage must survive longer. - Verify directory ownership and permissions before use. 3. Prevent symbolic-link attacks: - Use exclusive creation with `O_CREAT | O_EXCL`. - Use `O_NOFOLLOW` where supported. - Reject existing symbolic links using `lstat`. - Verify the opened file's owner and type with `fstat`. 4. Make state updates atomic: - Write to a secure temporary file in the same directory. - Flush and `fsync` the temporary file. - Replace the state file using `os.replace`. - Apply a process-safe file lock around the complete load-modify-save sequence. 5. Restrict log handling: - Open logs with restrictive permissions. - Use a logging framework or service-manager journal instead of a fixed `/tmp` log. - Avoid recording request bodies, credentials, or upstream authorization headers. 6. Fail safely on state corruption: - Report malformed state rather than silently treating it as empty. - Preserve the last known valid state or quarantine the corrupted file. ]]>
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (27)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The documented purpose is failover and routing, but the described behavior extends into environment discovery, reading local configuration, checking environment variables for API keys, scanning localhost ports, and enumerating providers/models. This mismatch is dangerous because it can justify sensitive reconnaissance and configuration harvesting under a benign-sounding reliability skill, leading to credential exposure, infrastructure fingerprinting, or collection of internal system details.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def main():
    env_file_vars = read_env_file(ENV_FILE)
    env = {**env_file_vars, **os.environ}
    report = {
        'env_file': str(ENV_FILE),
        'env_file_exists': ENV_FILE.exists(),
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill advertises and encourages scripts that inspect the environment, generate files, start proxies, and perform health checks, but it declares no explicit tool scope or permission boundaries. In an agent setting, this can lead to over-broad execution of shell, file, network, and environment access without clear user consent or policy enforcement, increasing the risk of unintended data exposure or unsafe actions.

External Transmission

Medium
Category
Data Exfiltration
Content
- Service: `systemctl --user status api-failover.service`
- Primary route: `custom-ai-td-ee/gpt-5.4`
- Primary credential inheritance from OpenClaw config: working
- Health check and real curl chat request: working
- Failure responses now return a readable `user_message` and `summary` when all routes fail

### Not yet active
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
providers:
  primary-openai:
    type: openai-compatible
    base_url: https://api.example.com/v1
    api_key_env: OPENAI_API_KEY
    timeout_ms: 30000
    models:
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This configuration explicitly routes prompts and responses to multiple third-party providers and a local model, but the file contains no indication of consent, disclosure, or data-classification controls before off-host transmission. In a failover skill, this is especially relevant because requests may be automatically retried or downgraded to alternate providers, increasing the chance that sensitive data is sent to an unintended external service without operator awareness.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This markdown file explains that the layer manages configured providers and later documents remote provider endpoints and request routing, but it does not warn users that their prompts and related request data may be transmitted to third-party services. For a skill description that routes user content across multiple providers, a user-facing privacy/disclosure warning is expected.

External Transmission

Medium
Category
Data Exfiltration
Content
### Minimal chat check

```bash
curl -s http://127.0.0.1:4010/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "messages": [{"role": "user", "content": "Reply with exactly: ok"}],
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
### Minimal chat check

```bash
curl -s http://127.0.0.1:4010/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "messages": [{"role": "user", "content": "Reply with exactly: ok"}],
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
### Minimal chat check

```bash
curl -s http://127.0.0.1:4010/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "messages": [{"role": "user", "content": "Reply with exactly: ok"}],
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Unbounded Resource Access

Medium
Category
Excessive Agency
Content
ENV_FILE = Path('/root/.config/api-failover.env')


def can_connect(host, port, timeout=0.5):
    try:
        with socket.create_connection((host, port), timeout=timeout):
            return True
Confidence
75% confidence
Finding
Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Unbounded Resource Access

Medium
Category
Excessive Agency
Content
ENV_FILE = Path('/root/.config/api-failover.env')


def can_connect(host, port, timeout=0.5):
    try:
        with socket.create_connection((host, port), timeout=timeout):
            return True
Confidence
75% confidence
Finding
Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Unbounded Resource Access

Medium
Category
Excessive Agency
Content
ENV_FILE = Path('/root/.config/api-failover.env')


def can_connect(host, port, timeout=0.5):
    try:
        with socket.create_connection((host, port), timeout=timeout):
            return True
Confidence
75% confidence
Finding
Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script loads values from /root/.config/api-failover.env and the process environment, then explicitly checks for ANTHROPIC_API_KEY and OPENROUTER_API_KEY. Although the credentials are not printed, this is sensitive credential access in a code file with no confirmation prompt, user-facing log, or explanatory comment/docstring disclosing that secret-bearing environment variables will be read.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script reloads user systemd state, restarts api-failover.service, and launches another Python script via subprocess. These actions modify runtime system state and execute external commands, but there is no visible confirmation, user-facing log before execution, or explanatory comment/docstring warning the user about these operational side effects.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_json(cmd):
    p = subprocess.run(cmd, capture_output=True, text=True)
    if p.returncode != 0:
        return {'ok': False, 'cmd': cmd, 'stdout': p.stdout, 'stderr': p.stderr, 'code': p.returncode}
    try:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_json(cmd):
    p = subprocess.run(cmd, capture_output=True, text=True)
    if p.returncode != 0:
        return {'ok': False, 'cmd': cmd, 'stdout': p.stdout, 'stderr': p.stderr, 'code': p.returncode}
    try:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script writes environment discovery results to /tmp using a predictable filename derived from the PID, which may expose potentially sensitive configuration details to other local users or processes. In a failover/bootstrap context, discovery data may include provider endpoints, deployment metadata, or other operational information that should not be broadly readable.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
Starting a local HTTP proxy and writing state/log files under /tmp can expose request metadata, operational state, or credentials if those files are readable or replaceable by other local users. In this skill's context, the proxy handles AI/provider failover, so logs and state may contain sensitive routing, health, and API interaction details, making silent creation of these artifacts more dangerous.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if args.start_proxy:
        log_path = '/tmp/api-failover-http.log'
        with open(log_path, 'a', encoding='utf-8') as logf:
            proc = subprocess.Popen([
                sys.executable, str(SCRIPTS / 'http_proxy.py'),
                '--config', args.config_out,
                '--host', args.host,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The script inspects a hard-coded local configuration file at /root/.openclaw/openclaw.json and extracts details beyond failover health requirements, including execution security settings, gateway bind/mode, provider inventory, and whether API keys are inline. That broad environment discovery can expose sensitive local topology and security posture to the caller, which is especially risky for an agent skill because it collects unrelated host configuration without clear necessity or consent.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The main routine silently probes environment variables, local listening services, and local application configuration, then prints a consolidated report without any user-facing warning or confirmation. In a skill context, undisclosed host inspection increases the risk of covert reconnaissance because the user may believe they are only configuring failover logic, not enumerating local services and security-relevant settings.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This script sends request bodies built from the payload file to external provider endpoints via HTTP POST. The file contains no confirmation prompt, print/log disclosure before transmission, or inline warning that user content from the payload will be sent to third-party services.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The unauthenticated /health endpoint returns provider names, available profiles, the on-disk state file path, and full runtime state. In a failover proxy, that state can reveal internal topology, degraded routes, outage history, and filesystem details that help an attacker map the service or target follow-on attacks; the risk is higher because this component is an infrastructure-facing proxy rather than a public content API.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The example user-facing message is presented only in Chinese, which can impose a specific language on users without opt-in. The file does not indicate that this skill is intentionally limited to Chinese-speaking users or provide an alternative language choice.

Static analysis

Detected: suspicious.exposed_secret_literal, suspicious.install_untrusted_source

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/failover_proxy.py:162

Install source points to URL shortener or raw IP.

Warn
Code
suspicious.install_untrusted_source
Location
references/config-example.yaml:22

Install source points to URL shortener or raw IP.

Warn
Code
suspicious.install_untrusted_source
Location
references/config-forced-failover-drill.yaml:4

Install source points to URL shortener or raw IP.

Warn
Code
suspicious.install_untrusted_source
Location
references/config-model-downgrade-drill.yaml:4

Install source points to URL shortener or raw IP.

Warn
Code
suspicious.install_untrusted_source
Location
references/config-production.yaml:16

Install source points to URL shortener or raw IP.

Warn
Code
suspicious.install_untrusted_source
Location
references/config-realworld-example.yaml:31