JupyterHub Voice Input Permissions
Calliope Integration: This component is integrated into the Calliope AI platform. Some features and configurations may differ from the upstream project.
Problem
Voice input (microphone access) doesn’t work in JupyterHub deployments due to proxy chain:
User Browser → JupyterHub (hub.example.com) → IDE Container (WAIIDE Server)Root Cause
Permissions-Policy and CSP headers need to be properly forwarded through the JupyterHub proxy.
The browser sees:
- Origin:
hub.example.com/user/username/ - Frame: WAIIDE Server webview (nested iframe)
Solutions
Solution 1: JupyterHub Config (Recommended)
Add to JupyterHub configuration (jupyterhub_config.py on the hub, not container):
# Allow microphone access through proxy
c.JupyterHub.tornado_settings = {
'headers': {
'Permissions-Policy': 'microphone=(self), camera=(self)',
'Feature-Policy': 'microphone *; camera *'
}
}
# Ensure proxy passes through necessary headers
c.Spawner.environment = {
'JUPYTER_ENABLE_LAB': 'yes',
}Solution 2: Nginx/Reverse Proxy
If using Nginx in front of JupyterHub:
location /user/ {
proxy_pass http://jupyterhub:8000;
# Forward permissions policies
proxy_set_header Permissions-Policy "microphone=(self), camera=(self)";
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Forward original host
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}Solution 3: Container-Level Headers
Add to scripts/entrypoint-jupyterhub.sh:
# Set environment variable for jupyter-server-proxy
export JUPYTER_SINGLEUSER_APP_ARGS="--ServerApp.tornado_settings={'headers': {'Permissions-Policy': 'microphone=(self), camera=(self)'}}"Verification
Test microphone access:
- Check Browser Console:
// Run in browser DevTools console
navigator.permissions.query({name: 'microphone'}).then(result => {
console.log('Microphone permission:', result.state);
});- Check Headers:
curl -I https://hub.example.com/user/username/ | grep -i "policy"- Test in IDE:
- Open Calliope chat
- Press Cmd+Shift+T (or Ctrl+Shift+T)
- Check console for
[VoiceInput]logs - Grant permission when prompted
- Speak and verify transcription appears
Cross-Origin Considerations
If microphone still blocked:
- Ensure HTTPS: Some browsers require HTTPS for microphone access
- Check iframe context: WAIIDE webviews are nested iframes
- Verify CSP: Container’s CSP must allow
mediastream:sources - Test in standalone: Verify works in standalone container before debugging proxy
Testing Standalone
# Start container without JupyterHub
docker run -p 8070:8070 calliopeai/waiide
# Access directly
open http://localhost:8070
# Test microphone - should work locallyIf works standalone but not through Hub → proxy permissions issue
References
Status: Documented - requires JupyterHub admin configuration Container-side fixes: CSP already updated in webviewContent.ts