T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/bridge.py:523- Finding
Unauthenticated Outbound Call Creation Using Privileged Twilio Credentials
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill is a real Twilio-to-Gemini phone bridge, but it exposes internet-facing phone-call and live-audio capabilities without enough authentication, scoping, or safe deployment defaults.
Review this carefully before installing. Only run it behind strong authentication, Twilio signature validation, trusted-host enforcement, rate limits, destination and caller-ID allowlists, explicit PUBLIC_URL/TWILIO_ACCOUNT_SID/TWILIO_FROM configuration, and clear consent and privacy handling for live audio and any recording. Pin dependencies before deployment.
scripts/bridge.py:523Unauthenticated Outbound Call Creation Using Privileged Twilio Credentials
scripts/bridge.py:502Twilio WebSocket and Webhook Requests Are Accepted Without Origin Authentication
scripts/bridge.py:487Untrusted Host Header Controls Twilio Media Stream Destination
scripts/bridge.py:119Hardcoded Twilio Account, Telephone Number, and External Deployment Defaults
requirements.txt:1Open-Ended and Unhashed Python Dependency Versions Permit Unreviewed Upgrades
The documented purpose understates the actual behavior by focusing on a call bridge while also exposing outbound call initiation and webhook handling, and the finding indicates additional recording-related behavior and embedded Twilio identifiers not disclosed in the description. That mismatch is dangerous because users may deploy a skill capable of initiating real-world telecom actions and handling sensitive call metadata without informed consent or adequate review.
Package name closely resembles a popular package, suggesting possible typosquatting. Attackers publish malicious packages with similar names to trick developers into installing them.
Suspicious Unicode normalization or mixed-script content
The skill declares required environment variables and implies operational capabilities, but it does not declare an explicit tool/permission scope despite functionality that can place calls and expose networked endpoints. This weakens least-privilege review and can mislead operators about what the skill is allowed to do, especially because it bridges external communications and may write or generate runtime artifacts in practice.
The skill bridges live phone audio through Twilio and Google Gemini, but the documentation does not warn about privacy, data sharing, retention, or disclosure obligations. Because the content is real-time voice from phone calls, missing privacy guidance is especially risky and may lead to unlawful processing of sensitive communications data.
The documentation provides a ready-to-run API example that places outbound calls to arbitrary phone numbers but does not warn that this triggers real telephony activity with possible cost, abuse, and consent implications. In this context, omission of such warnings increases the chance of accidental or unauthorized calls from a deployed system.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
curl -X POST https://your-domain/gemini-live/call \
-H 'Content-Type: application/json' \
-d '{"to": "+1234567890", "greeting": "Hello! This is Marcia."}'
Suspicious Unicode normalization or mixed-script content
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
if self._file:
self._file.close()
path = OPENCLAW_LOG_DIR / f"openclaw-{now_date}.log"
self._file = open(path, "a", encoding="utf-8")
self._current_date = now_date
def emit(self, record: logging.LogRecord):
The bridge forwards live caller audio to the Gemini API for processing, but this file does not provide any caller disclosure, consent prompt, or gating before transmission begins. In a telephony context this can create privacy, compliance, and data-handling exposure, especially for jurisdictions or deployments requiring notice before AI processing or third-party sharing of call content.
The outbound call endpoint allows recording to be enabled and then initiates Twilio recording without any user-facing disclosure or consent mechanism in the generated call flow. Recording phone calls without notice can violate legal requirements and materially increases privacy risk because sensitive voice content may be stored and retained.
The dependency is specified with a lower bound only, which allows future installs to resolve to different versions over time. This weakens build reproducibility and can unintentionally pull in newly introduced vulnerable or breaking releases in a network-facing FastAPI service.
fastapi>=0.135.0
uvicorn>=0.41.0
google-genai>=1.60.0
twilio>=9.0.0
FastAPI has known advisories, and because the manifest does not pin an exact version, it is impossible to determine from this file whether an affected release will be installed. In a public-facing API service, that uncertainty is a real dependency-risk finding even if the currently resolved version might be safe.
Using an unpinned uvicorn version permits uncontrolled dependency drift across deployments. For an internet-exposed ASGI server, this can lead to inconsistent security posture and accidental adoption of vulnerable releases.
fastapi>=0.135.0
uvicorn>=0.41.0
google-genai>=1.60.0
twilio>=9.0.0
audioop-lts>=0.2.0
uvicorn has historical security advisories, but the unpinned requirement prevents verifying whether deployments resolve to a patched version. Since uvicorn is the HTTP/WebSocket server, this ambiguity matters more than it would for a non-network-exposed library.
The Google GenAI client is not pinned, so installations may fetch different versions over time. While not proof of an active exploit, this creates supply-chain and stability risk for a real-time voice integration that depends on predictable library behavior.
fastapi>=0.135.0
uvicorn>=0.41.0
google-genai>=1.60.0
twilio>=9.0.0
audioop-lts>=0.2.0
websockets>=16.0
The Twilio SDK is specified as a minimum version only, allowing later versions to be installed without review. In a telephony bridge handling live calls, unreviewed dependency changes can affect security-sensitive request handling and authentication flows.
fastapi>=0.135.0
uvicorn>=0.41.0
google-genai>=1.60.0
twilio>=9.0.0
audioop-lts>=0.2.0
websockets>=16.0
python-multipart>=0.0.20
The audio processing library is unpinned, so builds are not reproducible and may pull unexpected future releases. This is a supply-chain hygiene issue rather than direct evidence of compromise, but still increases operational risk.
uvicorn>=0.41.0
google-genai>=1.60.0
twilio>=9.0.0
audioop-lts>=0.2.0
websockets>=16.0
python-multipart>=0.0.20
The websockets package is unpinned, which allows dependency drift and makes it hard to know whether deployments include a safe version. Because this skill bridges live audio over WebSockets, predictable and patched dependency versions are especially important.
google-genai>=1.60.0
twilio>=9.0.0
audioop-lts>=0.2.0
websockets>=16.0
python-multipart>=0.0.20
websockets has known DoS-related advisories, and the requirement is not pinned, so the deployed version cannot be verified from the manifest. Given this skill's heavy reliance on persistent real-time WebSocket sessions, unresolved version ambiguity increases exposure to availability issues.
python-multipart is unpinned despite being a package with a history of parser-related advisories. In a FastAPI app, leaving it unconstrained makes it difficult to verify whether deployments are protected from multipart parsing flaws and DoS issues.
twilio>=9.0.0
audioop-lts>=0.2.0
websockets>=16.0
python-multipart>=0.0.20
python-multipart has numerous parser and DoS advisories, and the dependency is unpinned, so there is meaningful uncertainty about whether a vulnerable version may be installed. In a web service context, multipart parser flaws can be exploited remotely to degrade availability or trigger excessive resource consumption.
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
config.echo_decay_ms = args.echo_decay_ms
logging.basicConfig(
level=getattr(logging, config.log_level.upper()),
format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
)