Skip to content

dglazer/cs4vwb

Repository files navigation

Claude Science for Verily Workbench

A production-ready custom app package that enables Claude Science to run within Verily Workbench environments, with automatic authentication handling and session management.

Overview

This repository contains a complete integration solution for running Claude Science as a Verily Workbench custom app. It includes:

  • Automatic binary download from official sources during build
  • Intelligent authentication proxy that handles nonce-based auth transparently
  • Header rewriting to satisfy Claude Science's origin/host validation
  • Auto-restart mechanism to maintain service availability
  • Iframe embedding support via security header stripping

Architecture

The integration uses a three-layer proxy architecture to work around Claude Science's authentication and validation requirements:

Workbench (iframe)
    ↓ (HTTPS)
nginx:8000 (reverse proxy)
    ↓ (HTTP, strips X-Frame-Options/CSP headers)
nonce-redirect:8002 (authentication proxy)
    ↓ (HTTP, rewrites Host/Origin/Referer, manages nonces)
Claude Science:8001 (actual service)
    ↓
Data Volume: /home/claude-user/.claude-science

Components

  1. nginx (port 8000) - Entry point for Workbench traffic

    • Strips X-Frame-Options and Content-Security-Policy headers to allow iframe embedding
    • Proxies all traffic to nonce-redirect service
  2. nonce-redirect.py (port 8002) - Python authentication proxy

    • Detects unauthenticated requests and redirects with fresh nonce
    • Rewrites Host, Origin, and Referer headers to localhost:8001
    • Filters cookies to distinguish Workbench cookies from Claude Science session cookies
    • Forwards all headers (including CSRF tokens) to Claude Science
  3. Claude Science (port 8001) - The actual Claude Science service

    • Runs as claude-user (non-root)
    • Binds to 127.0.0.1:8001 (internal only)
    • Runs as a persistent background daemon; entrypoint.sh health-checks it and only restarts it if it actually stops responding

Prerequisites

  • Access to a Verily Workbench workspace
  • Internet access for downloading Claude Science binary (during build)
  • Minimum 4 CPUs, 8GB RAM recommended

Quick Start

Deploy to Verily Workbench

  1. Navigate to your Verily Workbench workspace
  2. Click "Create App" → "Custom"
  3. Provide Git repository URL: https://github.com/dglazer/cs4vwb
  4. Configure compute resources (4+ CPUs, 8GB+ RAM)
  5. Click "Launch"

The Docker build will automatically:

  • Download the latest stable Claude Science binary from Google Cloud Storage
  • Install all required dependencies (bubblewrap, socat, nginx, python3)
  • Configure the three-layer proxy architecture
  • Set up data persistence volume

Access Claude Science

Once deployed:

  1. Click on the app URL in Workbench (format: https://<UUID>.workbench-app-prod.verily.com/)
  2. You'll be automatically redirected with a fresh authentication nonce
  3. Sign in with your Claude account when prompted
  4. Paste the authentication code when requested
  5. Start using Claude Science!

Technical Challenges Solved

This integration required working around several Claude Science behaviors that aren't designed for proxy/iframe environments:

1. Host Header Validation

Problem: Claude Science validates the Host header and rejects requests not matching its configured host.

Solution: nginx rewrites the Host header to localhost:8001 before proxying.

Location: nginx.conf:18

2. Nonce-Based Authentication

Problem: Claude Science uses single-use nonce URLs (?nonce=xxx) for authentication. Workbench loads the app without a nonce, causing immediate "session expired" errors.

Solution: Created nonce-redirect.py that:

  • Detects unauthenticated requests (no nonce, no valid session cookie)
  • Executes claude-science url to generate a fresh nonce
  • Redirects the user to the same URL with ?nonce=xxx appended

Location: nonce-redirect.py:101-155

3. Iframe Embedding Blocked

Problem: Claude Science returns X-Frame-Options: DENY and Content-Security-Policy headers that prevent iframe embedding (required by Workbench).

Solution: nginx strips these headers before returning responses to Workbench.

Location: nginx.conf:11-12

4. Origin Validation

Problem: Claude Science validates the Origin and Referer headers, rejecting requests from Workbench's domain.

Solution: nonce-redirect proxy rewrites Origin to http://localhost:8001 and Referer to http://localhost:8001/.

Location: nonce-redirect.py:26-28

5. Cookie Filtering

Problem: Workbench sends many cookies (IAP, analytics, etc.) alongside Claude Science session cookies. Need to distinguish between them to detect authenticated sessions.

Solution: Implemented cookie filtering that recognizes known Workbench cookie prefixes (GCP_IAP_UID, AMCVS_, _ga, etc.) and treats any other cookie as a potential Claude Science session.

Location: nonce-redirect.py:71-99

6. CSRF Token Forwarding

Problem: Claude Science uses CSRF tokens in cookies and request headers. Initial implementation only whitelisted specific headers, dropping CSRF tokens and causing 403 errors.

Solution: Changed to forward ALL headers from original request, then override only the specific ones that need rewriting (Host, Origin, Referer).

Location: nonce-redirect.py:20-28

7. serve Is a Start-or-Attach CLI, Not a Foreground Server

Problem: An earlier version of entrypoint.sh treated claude-science serve's exit as "Claude Science died" and restarted it in a loop whenever it exited with code 0. In practice serve exited almost immediately, every time, causing a restart every 2-3 seconds indefinitely. It turned out this was chasing a phantom: claude-science serve starts (or attaches to) a background daemon and then exits right away regardless of outcome -- confirmed by claude-science --help: "serve — Start the background program (the daemon) and open the web UI in your browser. Start here." and "status — Is it running? ... always exits 0". The actual daemon kept running uninterrupted the whole time; only the thin CLI wrapper was being restarted. This was diagnosed from a Cloud Logging export showing 136 restart cycles in 6 minutes, all exit code 0, while the same daemon pid persisted throughout.

Solution: entrypoint.sh now runs serve once to ensure the daemon is up, then polls the daemon directly with a raw TCP connect to 127.0.0.1:8001 every 30 seconds, only re-invoking serve if the daemon is actually unreachable.

Location: entrypoint.sh:113-147

Impact: The daemon is no longer restarted on a fixed cadence, so it isn't cycling and sessions aren't disrupted by this mechanism. Restarts now only happen if the daemon genuinely stops responding.

File Structure

.
├── .devcontainer.json       # Workbench devcontainer config
├── docker-compose.yaml      # Service definition (container must be named "application-server")
├── Dockerfile               # Downloads Claude Science, installs dependencies
├── entrypoint.sh           # Startup script with auto-restart loop
├── nginx.conf              # Reverse proxy config (strips security headers)
├── nonce-redirect.py       # Authentication proxy (handles nonces, header rewriting)
├── .gitignore              # Excludes binary, data directory, logs
└── README.md               # This file

Configuration Details

Environment Variables

  • CLAUDE_SCIENCE_DATA_DIR=/home/claude-user/.claude-science - Data persistence location (mounted as Docker volume)

Ports

  • 8000: External port exposed to Workbench (nginx)
  • 8001: Internal port for Claude Science (not exposed)
  • 8002: Internal port for nonce-redirect service (not exposed)

User Permissions

  • Entrypoint runs as root to start nginx and nonce-redirect
  • Claude Science runs as claude-user (non-root) via su - claude-user
  • Data directory owned by claude-user:claude-user

Command-Line Arguments

Claude Science is started with:

serve --host 127.0.0.1 --port 8001 --no-browser --allow-ephemeral-data-dir --dangerously-no-sandbox
  • --host 127.0.0.1: Bind to localhost only (nginx proxies from external port)
  • --port 8001: Internal port
  • --no-browser: Don't attempt to open browser (no display in container)
  • --allow-ephemeral-data-dir: Allow running in container environments
  • --dangerously-no-sandbox: Disable sandbox (already in container, may lack CAP_SYS_ADMIN)

Note: These arguments are modified by entrypoint.sh from the original --host 0.0.0.0 in docker-compose.yaml.

Binary Versioning

Dockerfile resolves the stable pointer at https://storage.googleapis.com/.../operon-releases/stable fresh on every build and downloads whatever binary it currently points to — there is no version pin. This is a deliberate tradeoff: a newer Claude Science build is more likely to fix bugs than introduce them, so always tracking stable is preferred over pinning to a version that will eventually go stale.

Implication: two builds run on different days (or even the same day, if a rebuild happens in between) may be running different Claude Science binaries. When comparing behavior across test sessions, check the BuildID reported by file /home/claude-user/claude-science (logged at container startup) or the Stable version: <SHA> line from the build logs to confirm whether you're actually comparing like-for-like.

Troubleshooting

Container fails to start

Check logs in Workbench for specific errors. Common issues:

  1. Binary download failed: Verify internet access during build
  2. Permission errors: Data directory should be owned by claude-user
  3. Port conflicts: Ensure ports 8000, 8001, 8002 are available

Authentication loops or "session expired" errors

Symptoms: Continuously redirected or asked to authenticate

Checks:

  1. Verify nonce-redirect service is running: grep "nonce-redirect" /tmp/entrypoint.log
  2. Check if claude-science url command works: su - claude-user -c "CLAUDE_SCIENCE_DATA_DIR=/home/claude-user/.claude-science /home/claude-user/claude-science url"
  3. Examine cookie filtering logic in logs

"Forbidden origin" or "Forbidden host" errors

Symptoms: {"detail":"forbidden origin"} or {"detail":"forbidden host"}

Solution: Verify header rewriting is working:

  1. Check nginx config has proxy_set_header Host localhost:8001
  2. Check nonce-redirect is rewriting Origin/Referer headers
  3. Look for header logs in [nonce-redirect] output

App becomes unresponsive after a few minutes

Symptoms: Page shows "reloading" or hangs

Likely cause: The Claude Science daemon has actually stopped responding on port 8001

Check:

  1. Look for "daemon not reachable on port 8001" messages in /tmp/entrypoint.log
  2. "Starting Claude Science..." should normally appear only once per container start -- if you see it repeatedly, the daemon is genuinely dying and being restarted, which is worth investigating (fresh logs + check the daemon's own output for the actual failure, not just entrypoint.sh's health check)
  3. If restarts aren't happening at all despite the daemon being down, check entrypoint.sh syntax

403 errors on POST requests

Symptoms: Authentication works but actions fail with HTTP 403

Cause: CSRF tokens not being forwarded

Solution: Verify nonce-redirect.py copies ALL headers (line 20-23), not just whitelisted ones

Future Enhancements

Short-term Improvements

  1. Health checks: Add proper health endpoints to monitor service status
  2. Graceful restarts: Attempt to preserve sessions during genuine Claude Science restarts (now rare -- entrypoint.sh only restarts the daemon if it stops responding, rather than on a fixed cadence)
  3. Configurable health-check interval: Make the 30-second daemon health-check interval in entrypoint.sh configurable
  4. Better logging: Add structured logging with timestamps and severity levels
  5. Metrics: Expose Prometheus metrics for restart frequency, authentication failures, etc.

Medium-term Improvements

  1. Session persistence: Investigate if sessions can be preserved across restarts
  2. Connection pooling: Optimize proxy connections for better performance
  3. Custom error pages: Provide helpful error pages instead of generic 500/502 errors
  4. Automatic version updates: Script to check for and download new Claude Science versions
  5. Multi-user support: Handle multiple concurrent users more robustly

Long-term Improvements

  1. Horizontal scaling: Support multiple Claude Science instances behind a load balancer
  2. WebSocket optimization: Improve WebSocket handling for real-time features
  3. SSO integration: Integrate with Workbench's authentication instead of separate Claude login
  4. Resource management: Automatic CPU/memory allocation based on usage

Claude Science Enhancement Requests

These are suggested changes to Claude Science that would make integrations like this much cleaner and more robust:

Critical Priority

  1. Configuration-based host/origin validation

    • Current behavior: Hard-coded validation against expected host/origin
    • Desired: Environment variables or config file to specify allowed hosts/origins
    • Example: ALLOWED_ORIGINS=https://example.com,https://*.workbench-app-prod.verily.com
    • Impact: Would eliminate need for header rewriting proxies
  2. Optional iframe embedding support

    • Current behavior: Always sends X-Frame-Options: DENY and restrictive CSP
    • Desired: Flag like --allow-iframe-embedding to omit these headers
    • Impact: Would eliminate need for nginx header stripping
  3. Token-based authentication alternative

    • Current behavior: Only nonce-based auth (single-use URLs)
    • Desired: Support long-lived API tokens or OAuth flows
    • Impact: Would eliminate need for nonce-redirect proxy entirely

High Priority

  1. Session persistence documentation
    • Current behavior: Unclear what state persists across restarts
    • Desired: Document what's stored in data directory vs. memory
    • Impact: Would help understand restart behavior and session continuity

Medium Priority

  1. Structured logging

    • Current behavior: Human-readable text logs
    • Desired: Option for JSON-structured logs with severity levels
    • Impact: Easier to parse and monitor in container environments
  2. Health check endpoint

    • Current behavior: No dedicated health endpoint
    • Desired: /health or /ready endpoint that returns service status
    • Impact: Better integration with orchestrators (Docker, Kubernetes, Workbench); would also let entrypoint.sh health-check the daemon without relying on a raw TCP connect
  3. Graceful shutdown

    • Current behavior: Unknown shutdown behavior
    • Desired: Handle SIGTERM/SIGINT gracefully, finish in-flight requests
    • Impact: Better session continuity during restarts
  4. Configuration file support

    • Current behavior: All configuration via CLI flags
    • Desired: Support for config file (YAML/TOML) for complex setups
    • Impact: Easier to manage in container environments

Nice to Have

  1. Multi-user mode

    • Current behavior: Designed for single local user
    • Desired: Built-in support for multiple concurrent authenticated users
    • Impact: Better fit for shared/enterprise environments like Workbench
  2. Metrics endpoint

    • Current behavior: No metrics exposure
    • Desired: Prometheus-compatible /metrics endpoint
    • Impact: Better observability in production deployments
  3. Configurable authentication timeout

    • Current behavior: Fixed session timeout (unknown duration)
    • Desired: --session-timeout=24h flag
    • Impact: Reduce re-authentication frequency in trusted environments

Known Limitations

  1. Single instance only: Currently supports only one Claude Science instance per container
  2. No offline mode: Requires internet access for authentication
  3. Limited error handling: Some proxy errors may not have helpful error messages

Contributing

Improvements welcome! Areas where contributions would be particularly valuable:

  • Testing session persistence across restarts
  • Improving auto-restart logic to minimize disruption
  • Adding health checks and metrics
  • Testing with multiple concurrent users
  • Documenting Claude Science's actual exit behavior

Support

For issues with:

License

This integration configuration is provided as-is under MIT License. Claude Science is subject to Anthropic's license terms.

Acknowledgments

Built through iterative debugging across 31 deployment attempts, solving challenges with:

  • Host/origin validation
  • Nonce-based authentication in iframe contexts
  • Cookie filtering across proxy boundaries
  • CSRF token preservation
  • Service availability and auto-restart

Special thanks to the Verily Workbench team for the custom app platform and documentation.

About

Claude Science custom app for Verily Workbench

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages