✅ Feature is optional - Tailscale validation only applies when users create shares with tailscale_only=true
📝 Your choice - Implement if you want to restrict certain shares to your Tailscale network
Without Tailscale validation (current default):
tailscale_only=falseshares → Accessible from anywhere ✅tailscale_only=trueshares → Still accessible from anywhere⚠️ (validation disabled)
With Tailscale validation (when implemented):
tailscale_only=falseshares → Accessible from anywhere ✅tailscale_only=trueshares → Only accessible from Tailnet ✅ (validated)
Skip Tailscale validation if:
- You only use the public share gateway (all shares are
tailscale_only=false) - You trust users not to abuse
tailscale_onlyflag - Simpler setup is more important than this specific security control
Implement Tailscale validation if:
- You want users to create Tailnet-only shares (private conversations)
- You expose ushadow directly to your Tailnet (not just via gateway)
- You need strong network-based access control
If ushadow runs directly as a Tailscale node (not behind a proxy):
# In share_service.py:_validate_tailscale_access(), around line 465:
try:
ip = ipaddress.ip_address(request_ip)
tailscale_range = ipaddress.ip_network("100.64.0.0/10")
is_tailscale = ip in tailscale_range
logger.debug(f"IP {request_ip} {'is' if is_tailscale else 'is NOT'} in Tailscale range")
return is_tailscale
except ValueError:
logger.warning(f"Invalid IP address: {request_ip}")
return FalseHow it works:
- Tailscale uses CGNAT IP range 100.64.0.0/10
- Check if request IP falls in this range
- Fast, no API calls
Pros: Simple, fast, no external dependencies Cons: Only works if ushadow is directly on Tailscale (not behind nginx/proxy)
Enable: Set SHARE_VALIDATE_TAILSCALE=true in .env
If you expose ushadow via Tailscale Serve (reverse proxy):
Current limitation: This requires passing the full Request object, not just IP.
Architecture change needed:
# In share_service.py:validate_share_access()
# Instead of:
is_tailscale = await self._validate_tailscale_access(request_ip)
# Pass full request:
is_tailscale = await self._validate_tailscale_access(request)
# In _validate_tailscale_access():
async def _validate_tailscale_access(self, request: Request) -> bool:
tailscale_user = request.headers.get("X-Tailscale-User")
if tailscale_user:
logger.debug(f"Validated Tailscale user: {tailscale_user}")
return True
return FalseHow it works:
- Tailscale Serve adds
X-Tailscale-Userheader with authenticated user - If header present → user is on your Tailnet
- Cryptographically verified by Tailscale
Pros: Most secure, user identity available Cons: Requires refactoring to pass Request object, only works with Tailscale Serve
Don't set SHARE_VALIDATE_TAILSCALE=true and leave as-is.
What happens:
- All shares work regardless of IP
tailscale_onlyflag is ignored (becomes cosmetic)- Simpler setup, no code changes needed
Trade-off: Users can't create truly Tailnet-restricted shares
Skip Tailscale validation for now because:
- Your architecture: Friends access via public gateway, not directly to ushadow
- Gateway handles it: The gateway itself is on your Tailnet, providing network isolation
- Simpler: One less thing to configure and maintain
- The flag still useful: Even without validation,
tailscale_onlyserves as metadata/intent
When you WOULD need it:
- If users access ushadow directly via Tailscale (not just gateway)
- If you want to enforce Tailnet-only shares for specific conversations
Public Share (tailscale_only=false):
Friend → Public Gateway → [Tailscale] → ushadow
Tailscale-Only Share (tailscale_only=true):
Friend on your Tailnet → ushadow (direct access)
↑ THIS is where Tailscale validation matters
The validation prevents a friend from accessing a tailscale_only share via the public gateway or from outside your network.
Once implemented:
# 1. Create Tailscale-only share
curl -X POST http://localhost:8080/api/share/create \
-H "Content-Type: application/json" \
-H "Cookie: ushadow_auth=YOUR_TOKEN" \
-d '{
"resource_type": "conversation",
"resource_id": "abc123",
"permissions": ["read"],
"tailscale_only": true
}'
# 2. Try to access from Tailscale IP (100.64.x.x)
# Expected: ✅ Access granted
# 3. Try to access from public IP (not Tailscale)
# Expected: ❌ 403 "Access restricted to Tailscale network"| Option | When to Use | Complexity | Security |
|---|---|---|---|
| Skip | Public gateway only | ⭐ Easy | Medium (gateway isolated) |
| IP Range | Direct Tailscale access | ⭐⭐ Medium | High (network-level) |
| Serve Headers | Tailscale Serve setup | ⭐⭐⭐ Complex | Highest (crypto verified) |
Recommended: Skip for now, implement later if needed.
- Decide: Do you need Tailscale-only shares?
- If No: Leave as-is, move to frontend integration
- If Yes: Set
SHARE_VALIDATE_TAILSCALE=trueand add 5-10 lines (Option A)