Access-Control-Allow-Origin missing from responses since 0.0.34 — desktop app cannot connect to a remote environment on a separate HTTPS origin #8878

Description

@epavlenko

Summary

Since 0.0.34 the server answers the CORS preflight correctly but omits Access-Control-Allow-Origin from the actual response. Any browser-context client whose origin differs from the server's therefore discards the response, and the desktop app never gets past Failed to connect. Reconnecting....

0.0.33 is the last version that sends the header. Rolling the server back to 0.0.33 — changing nothing else — restored the desktop client immediately.

Environment

  • Server: headless t3 serve --host <tailnet-ip> --port 3010, Linux x64, Bun runtime
  • Reached from the desktop over a separate HTTPS origin via Tailscale Serve
    (https://<host>.ts.net:8445http://<tailnet-ip>:3010), valid Let's Encrypt cert
  • Desktop app: macOS, tested at 0.0.33, 0.0.35, 0.0.36, 0.0.37 — all fail against a 0.0.34+ server

Reproduction

t3 serve --host 127.0.0.1 --port 3099 --base-dir /tmp/t3probe
curl -sD- -o/dev/null -H 'Origin: https://app.t3.codes' \
http://127.0.0.1:3099/.well-known/t3/environment | grep -i access-control
Server versionReleasedAccess-Control-Allow-Origin on GET
0.0.332026-08-10*
0.0.342026-08-26absent
0.0.352026-08-27absent
0.0.362026-08-29absent
0.0.372026-08-31absent
0.0.38-nightly.202608312026-08-31absent

The preflight still looks right on 0.0.37:

$ curl -sD- -o/dev/null -X OPTIONS -H 'Origin: https://app.t3.codes' \
-H 'Access-Control-Request-Method: GET' .../.well-known/t3/environment
HTTP/2 204
access-control-allow-origin: *
access-control-allow-methods: GET, POST, OPTIONS
access-control-allow-headers: authorization,b3,traceparent,content-type,dpop
access-control-max-age: 600

The same GET that lacks the header returns 200 with the expected body, so this is purely a response-header regression. Reproduced on /.well-known/t3/environment, /api/auth/session and /health, and with every Origin value tried (https://app.t3.codes, file://, app://-, null, http://localhost:5173, the server's own origin).

Symptom in the desktop app

Provider settings are unavailable
Failed to connect. Reconnecting... Reason: Failed to fetch remote environment endpoint
https://<host>.ts.net:8445/.well-known/t3/environment
(HttpClientError: Transport error (GET https://<host>.ts.net:8445/.well-known/t3/environment))

What rules out a network or TLS problem:

  • curl -v from the same Mac, at the same moment, completes the handshake and returns HTTP/2 200 with the descriptor JSON; cert verifies, ALPN negotiates h2.
  • The iOS app connected to the same 0.0.35 server successfully while the desktop could not. A native client does not enforce CORS; curl does not either. Only the browser-context client fails.
  • Server-side /api/auth/clients showed no connection attempt from the desktop after the upgrade, while the stored bearer token was still valid for another two weeks.

Why it may have gone unnoticed

The common setup is a desktop app talking to a server on the same machine over loopback, where the request is not cross-origin. It only bites when the environment is remote and published on a different origin — the documented Tailscale Serve setup in docs/user/remote-access.md.

Expected

Responses carry Access-Control-Allow-Origin (as 0.0.33 did), so a remote environment on its own HTTPS origin stays reachable from browser-context clients.

Failing that, a documented way to configure allowed origins would be enough — there is no server setting for it today.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
       blocks
      (function() {
      function addCopyButtons() {
      document.querySelectorAll('pre code').forEach(function(codeBlock) {
      if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
      codeBlock.parentElement.setAttribute('data-copy-added', 'true');
      var btn = document.createElement('button');
      btn.textContent = 'Copy';
      btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
      btn.onmouseover = function() { this.style.opacity = '1'; };
      btn.onmouseout = function() { this.style.opacity = '0.7'; };
      btn.onclick = function() {
      navigator.clipboard.writeText(codeBlock.textContent).then(function() {
      btn.textContent = 'Copied!';
      setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
      });
      };
      codeBlock.parentElement.style.position = 'relative';
      codeBlock.parentElement.appendChild(btn);
      });
      }
      addCopyButtons();
      // Re-run on dynamic content
      var observer = new MutationObserver(addCopyButtons);
      observer.observe(document.body, { childList: true, subtree: true });
      })();
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      Access-Control-Allow-Origin missing from responses since 0.0.34 — desktop app cannot connect to a remote environment on a separate HTTPS origin #8878

      Description

      @epavlenko

      Summary

      Since 0.0.34 the server answers the CORS preflight correctly but omits Access-Control-Allow-Origin from the actual response. Any browser-context client whose origin differs from the server's therefore discards the response, and the desktop app never gets past Failed to connect. Reconnecting....

      0.0.33 is the last version that sends the header. Rolling the server back to 0.0.33 — changing nothing else — restored the desktop client immediately.

      Environment

      • Server: headless t3 serve --host <tailnet-ip> --port 3010, Linux x64, Bun runtime
      • Reached from the desktop over a separate HTTPS origin via Tailscale Serve
        (https://<host>.ts.net:8445http://<tailnet-ip>:3010), valid Let's Encrypt cert
      • Desktop app: macOS, tested at 0.0.33, 0.0.35, 0.0.36, 0.0.37 — all fail against a 0.0.34+ server

      Reproduction

      t3 serve --host 127.0.0.1 --port 3099 --base-dir /tmp/t3probe
      curl -sD- -o/dev/null -H 'Origin: https://app.t3.codes' \
      http://127.0.0.1:3099/.well-known/t3/environment | grep -i access-control
      Server versionReleasedAccess-Control-Allow-Origin on GET
      0.0.332026-08-10*
      0.0.342026-08-26absent
      0.0.352026-08-27absent
      0.0.362026-08-29absent
      0.0.372026-08-31absent
      0.0.38-nightly.202608312026-08-31absent

      The preflight still looks right on 0.0.37:

      $ curl -sD- -o/dev/null -X OPTIONS -H 'Origin: https://app.t3.codes' \
      -H 'Access-Control-Request-Method: GET' .../.well-known/t3/environment
      HTTP/2 204
      access-control-allow-origin: *
      access-control-allow-methods: GET, POST, OPTIONS
      access-control-allow-headers: authorization,b3,traceparent,content-type,dpop
      access-control-max-age: 600
      

      The same GET that lacks the header returns 200 with the expected body, so this is purely a response-header regression. Reproduced on /.well-known/t3/environment, /api/auth/session and /health, and with every Origin value tried (https://app.t3.codes, file://, app://-, null, http://localhost:5173, the server's own origin).

      Symptom in the desktop app

      Provider settings are unavailable
      Failed to connect. Reconnecting... Reason: Failed to fetch remote environment endpoint
      https://<host>.ts.net:8445/.well-known/t3/environment
      (HttpClientError: Transport error (GET https://<host>.ts.net:8445/.well-known/t3/environment))
      

      What rules out a network or TLS problem:

      • curl -v from the same Mac, at the same moment, completes the handshake and returns HTTP/2 200 with the descriptor JSON; cert verifies, ALPN negotiates h2.
      • The iOS app connected to the same 0.0.35 server successfully while the desktop could not. A native client does not enforce CORS; curl does not either. Only the browser-context client fails.
      • Server-side /api/auth/clients showed no connection attempt from the desktop after the upgrade, while the stored bearer token was still valid for another two weeks.

      Why it may have gone unnoticed

      The common setup is a desktop app talking to a server on the same machine over loopback, where the request is not cross-origin. It only bites when the environment is remote and published on a different origin — the documented Tailscale Serve setup in docs/user/remote-access.md.

      Expected

      Responses carry Access-Control-Allow-Origin (as 0.0.33 did), so a remote environment on its own HTTPS origin stays reachable from browser-context clients.

      Failing that, a documented way to configure allowed origins would be enough — there is no server setting for it today.

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          Access-Control-Allow-Origin missing from responses since 0.0.34 — desktop app cannot connect to a remote environment on a separate HTTPS origin #8878

          Description

          @epavlenko

          Summary

          Since 0.0.34 the server answers the CORS preflight correctly but omits Access-Control-Allow-Origin from the actual response. Any browser-context client whose origin differs from the server's therefore discards the response, and the desktop app never gets past Failed to connect. Reconnecting....

          0.0.33 is the last version that sends the header. Rolling the server back to 0.0.33 — changing nothing else — restored the desktop client immediately.

          Environment

          • Server: headless t3 serve --host <tailnet-ip> --port 3010, Linux x64, Bun runtime
          • Reached from the desktop over a separate HTTPS origin via Tailscale Serve
            (https://<host>.ts.net:8445http://<tailnet-ip>:3010), valid Let's Encrypt cert
          • Desktop app: macOS, tested at 0.0.33, 0.0.35, 0.0.36, 0.0.37 — all fail against a 0.0.34+ server

          Reproduction

          t3 serve --host 127.0.0.1 --port 3099 --base-dir /tmp/t3probe
          curl -sD- -o/dev/null -H 'Origin: https://app.t3.codes' \
          http://127.0.0.1:3099/.well-known/t3/environment | grep -i access-control
          Server versionReleasedAccess-Control-Allow-Origin on GET
          0.0.332026-08-10*
          0.0.342026-08-26absent
          0.0.352026-08-27absent
          0.0.362026-08-29absent
          0.0.372026-08-31absent
          0.0.38-nightly.202608312026-08-31absent

          The preflight still looks right on 0.0.37:

          $ curl -sD- -o/dev/null -X OPTIONS -H 'Origin: https://app.t3.codes' \
          -H 'Access-Control-Request-Method: GET' .../.well-known/t3/environment
          HTTP/2 204
          access-control-allow-origin: *
          access-control-allow-methods: GET, POST, OPTIONS
          access-control-allow-headers: authorization,b3,traceparent,content-type,dpop
          access-control-max-age: 600
          

          The same GET that lacks the header returns 200 with the expected body, so this is purely a response-header regression. Reproduced on /.well-known/t3/environment, /api/auth/session and /health, and with every Origin value tried (https://app.t3.codes, file://, app://-, null, http://localhost:5173, the server's own origin).

          Symptom in the desktop app

          Provider settings are unavailable
          Failed to connect. Reconnecting... Reason: Failed to fetch remote environment endpoint
          https://<host>.ts.net:8445/.well-known/t3/environment
          (HttpClientError: Transport error (GET https://<host>.ts.net:8445/.well-known/t3/environment))
          

          What rules out a network or TLS problem:

          • curl -v from the same Mac, at the same moment, completes the handshake and returns HTTP/2 200 with the descriptor JSON; cert verifies, ALPN negotiates h2.
          • The iOS app connected to the same 0.0.35 server successfully while the desktop could not. A native client does not enforce CORS; curl does not either. Only the browser-context client fails.
          • Server-side /api/auth/clients showed no connection attempt from the desktop after the upgrade, while the stored bearer token was still valid for another two weeks.

          Why it may have gone unnoticed

          The common setup is a desktop app talking to a server on the same machine over loopback, where the request is not cross-origin. It only bites when the environment is remote and published on a different origin — the documented Tailscale Serve setup in docs/user/remote-access.md.

          Expected

          Responses carry Access-Control-Allow-Origin (as 0.0.33 did), so a remote environment on its own HTTPS origin stays reachable from browser-context clients.

          Failing that, a documented way to configure allowed origins would be enough — there is no server setting for it today.

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Access-Control-Allow-Origin missing from responses since 0.0.34 — desktop app cannot connect to a remote environment on a separate HTTPS origin #8878

              Description

              @epavlenko

              Summary

              Since 0.0.34 the server answers the CORS preflight correctly but omits Access-Control-Allow-Origin from the actual response. Any browser-context client whose origin differs from the server's therefore discards the response, and the desktop app never gets past Failed to connect. Reconnecting....

              0.0.33 is the last version that sends the header. Rolling the server back to 0.0.33 — changing nothing else — restored the desktop client immediately.

              Environment

              • Server: headless t3 serve --host <tailnet-ip> --port 3010, Linux x64, Bun runtime
              • Reached from the desktop over a separate HTTPS origin via Tailscale Serve
                (https://<host>.ts.net:8445http://<tailnet-ip>:3010), valid Let's Encrypt cert
              • Desktop app: macOS, tested at 0.0.33, 0.0.35, 0.0.36, 0.0.37 — all fail against a 0.0.34+ server

              Reproduction

              t3 serve --host 127.0.0.1 --port 3099 --base-dir /tmp/t3probe
              curl -sD- -o/dev/null -H 'Origin: https://app.t3.codes' \
              http://127.0.0.1:3099/.well-known/t3/environment | grep -i access-control
              Server versionReleasedAccess-Control-Allow-Origin on GET
              0.0.332026-08-10*
              0.0.342026-08-26absent
              0.0.352026-08-27absent
              0.0.362026-08-29absent
              0.0.372026-08-31absent
              0.0.38-nightly.202608312026-08-31absent

              The preflight still looks right on 0.0.37:

              $ curl -sD- -o/dev/null -X OPTIONS -H 'Origin: https://app.t3.codes' \
              -H 'Access-Control-Request-Method: GET' .../.well-known/t3/environment
              HTTP/2 204
              access-control-allow-origin: *
              access-control-allow-methods: GET, POST, OPTIONS
              access-control-allow-headers: authorization,b3,traceparent,content-type,dpop
              access-control-max-age: 600
              

              The same GET that lacks the header returns 200 with the expected body, so this is purely a response-header regression. Reproduced on /.well-known/t3/environment, /api/auth/session and /health, and with every Origin value tried (https://app.t3.codes, file://, app://-, null, http://localhost:5173, the server's own origin).

              Symptom in the desktop app

              Provider settings are unavailable
              Failed to connect. Reconnecting... Reason: Failed to fetch remote environment endpoint
              https://<host>.ts.net:8445/.well-known/t3/environment
              (HttpClientError: Transport error (GET https://<host>.ts.net:8445/.well-known/t3/environment))
              

              What rules out a network or TLS problem:

              • curl -v from the same Mac, at the same moment, completes the handshake and returns HTTP/2 200 with the descriptor JSON; cert verifies, ALPN negotiates h2.
              • The iOS app connected to the same 0.0.35 server successfully while the desktop could not. A native client does not enforce CORS; curl does not either. Only the browser-context client fails.
              • Server-side /api/auth/clients showed no connection attempt from the desktop after the upgrade, while the stored bearer token was still valid for another two weeks.

              Why it may have gone unnoticed

              The common setup is a desktop app talking to a server on the same machine over loopback, where the request is not cross-origin. It only bites when the environment is remote and published on a different origin — the documented Tailscale Serve setup in docs/user/remote-access.md.

              Expected

              Responses carry Access-Control-Allow-Origin (as 0.0.33 did), so a remote environment on its own HTTPS origin stays reachable from browser-context clients.

              Failing that, a documented way to configure allowed origins would be enough — there is no server setting for it today.

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  Access-Control-Allow-Origin missing from responses since 0.0.34 — desktop app cannot connect to a remote environment on a separate HTTPS origin #8878

                  Description

                  @epavlenko

                  Summary

                  Since 0.0.34 the server answers the CORS preflight correctly but omits Access-Control-Allow-Origin from the actual response. Any browser-context client whose origin differs from the server's therefore discards the response, and the desktop app never gets past Failed to connect. Reconnecting....

                  0.0.33 is the last version that sends the header. Rolling the server back to 0.0.33 — changing nothing else — restored the desktop client immediately.

                  Environment

                  • Server: headless t3 serve --host <tailnet-ip> --port 3010, Linux x64, Bun runtime
                  • Reached from the desktop over a separate HTTPS origin via Tailscale Serve
                    (https://<host>.ts.net:8445http://<tailnet-ip>:3010), valid Let's Encrypt cert
                  • Desktop app: macOS, tested at 0.0.33, 0.0.35, 0.0.36, 0.0.37 — all fail against a 0.0.34+ server

                  Reproduction

                  t3 serve --host 127.0.0.1 --port 3099 --base-dir /tmp/t3probe
                  curl -sD- -o/dev/null -H 'Origin: https://app.t3.codes' \
                  http://127.0.0.1:3099/.well-known/t3/environment | grep -i access-control
                  Server versionReleasedAccess-Control-Allow-Origin on GET
                  0.0.332026-08-10*
                  0.0.342026-08-26absent
                  0.0.352026-08-27absent
                  0.0.362026-08-29absent
                  0.0.372026-08-31absent
                  0.0.38-nightly.202608312026-08-31absent

                  The preflight still looks right on 0.0.37:

                  $ curl -sD- -o/dev/null -X OPTIONS -H 'Origin: https://app.t3.codes' \
                  -H 'Access-Control-Request-Method: GET' .../.well-known/t3/environment
                  HTTP/2 204
                  access-control-allow-origin: *
                  access-control-allow-methods: GET, POST, OPTIONS
                  access-control-allow-headers: authorization,b3,traceparent,content-type,dpop
                  access-control-max-age: 600
                  

                  The same GET that lacks the header returns 200 with the expected body, so this is purely a response-header regression. Reproduced on /.well-known/t3/environment, /api/auth/session and /health, and with every Origin value tried (https://app.t3.codes, file://, app://-, null, http://localhost:5173, the server's own origin).

                  Symptom in the desktop app

                  Provider settings are unavailable
                  Failed to connect. Reconnecting... Reason: Failed to fetch remote environment endpoint
                  https://<host>.ts.net:8445/.well-known/t3/environment
                  (HttpClientError: Transport error (GET https://<host>.ts.net:8445/.well-known/t3/environment))
                  

                  What rules out a network or TLS problem:

                  • curl -v from the same Mac, at the same moment, completes the handshake and returns HTTP/2 200 with the descriptor JSON; cert verifies, ALPN negotiates h2.
                  • The iOS app connected to the same 0.0.35 server successfully while the desktop could not. A native client does not enforce CORS; curl does not either. Only the browser-context client fails.
                  • Server-side /api/auth/clients showed no connection attempt from the desktop after the upgrade, while the stored bearer token was still valid for another two weeks.

                  Why it may have gone unnoticed

                  The common setup is a desktop app talking to a server on the same machine over loopback, where the request is not cross-origin. It only bites when the environment is remote and published on a different origin — the documented Tailscale Serve setup in docs/user/remote-access.md.

                  Expected

                  Responses carry Access-Control-Allow-Origin (as 0.0.33 did), so a remote environment on its own HTTPS origin stays reachable from browser-context clients.

                  Failing that, a documented way to configure allowed origins would be enough — there is no server setting for it today.

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    No labels
                    No labels

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      Access-Control-Allow-Origin missing from responses since 0.0.34 — desktop app cannot connect to a remote environment on a separate HTTPS origin #8878

                      Description

                      @epavlenko

                      Summary

                      Since 0.0.34 the server answers the CORS preflight correctly but omits Access-Control-Allow-Origin from the actual response. Any browser-context client whose origin differs from the server's therefore discards the response, and the desktop app never gets past Failed to connect. Reconnecting....

                      0.0.33 is the last version that sends the header. Rolling the server back to 0.0.33 — changing nothing else — restored the desktop client immediately.

                      Environment

                      • Server: headless t3 serve --host <tailnet-ip> --port 3010, Linux x64, Bun runtime
                      • Reached from the desktop over a separate HTTPS origin via Tailscale Serve
                        (https://<host>.ts.net:8445http://<tailnet-ip>:3010), valid Let's Encrypt cert
                      • Desktop app: macOS, tested at 0.0.33, 0.0.35, 0.0.36, 0.0.37 — all fail against a 0.0.34+ server

                      Reproduction

                      t3 serve --host 127.0.0.1 --port 3099 --base-dir /tmp/t3probe
                      curl -sD- -o/dev/null -H 'Origin: https://app.t3.codes' \
                      http://127.0.0.1:3099/.well-known/t3/environment | grep -i access-control
                      Server versionReleasedAccess-Control-Allow-Origin on GET
                      0.0.332026-08-10*
                      0.0.342026-08-26absent
                      0.0.352026-08-27absent
                      0.0.362026-08-29absent
                      0.0.372026-08-31absent
                      0.0.38-nightly.202608312026-08-31absent

                      The preflight still looks right on 0.0.37:

                      $ curl -sD- -o/dev/null -X OPTIONS -H 'Origin: https://app.t3.codes' \
                      -H 'Access-Control-Request-Method: GET' .../.well-known/t3/environment
                      HTTP/2 204
                      access-control-allow-origin: *
                      access-control-allow-methods: GET, POST, OPTIONS
                      access-control-allow-headers: authorization,b3,traceparent,content-type,dpop
                      access-control-max-age: 600
                      

                      The same GET that lacks the header returns 200 with the expected body, so this is purely a response-header regression. Reproduced on /.well-known/t3/environment, /api/auth/session and /health, and with every Origin value tried (https://app.t3.codes, file://, app://-, null, http://localhost:5173, the server's own origin).

                      Symptom in the desktop app

                      Provider settings are unavailable
                      Failed to connect. Reconnecting... Reason: Failed to fetch remote environment endpoint
                      https://<host>.ts.net:8445/.well-known/t3/environment
                      (HttpClientError: Transport error (GET https://<host>.ts.net:8445/.well-known/t3/environment))
                      

                      What rules out a network or TLS problem:

                      • curl -v from the same Mac, at the same moment, completes the handshake and returns HTTP/2 200 with the descriptor JSON; cert verifies, ALPN negotiates h2.
                      • The iOS app connected to the same 0.0.35 server successfully while the desktop could not. A native client does not enforce CORS; curl does not either. Only the browser-context client fails.
                      • Server-side /api/auth/clients showed no connection attempt from the desktop after the upgrade, while the stored bearer token was still valid for another two weeks.

                      Why it may have gone unnoticed

                      The common setup is a desktop app talking to a server on the same machine over loopback, where the request is not cross-origin. It only bites when the environment is remote and published on a different origin — the documented Tailscale Serve setup in docs/user/remote-access.md.

                      Expected

                      Responses carry Access-Control-Allow-Origin (as 0.0.33 did), so a remote environment on its own HTTPS origin stays reachable from browser-context clients.

                      Failing that, a documented way to configure allowed origins would be enough — there is no server setting for it today.

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        No labels
                        No labels

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          Access-Control-Allow-Origin missing from responses since 0.0.34 — desktop app cannot connect to a remote environment on a separate HTTPS origin #8878

                          Description

                          @epavlenko

                          Summary

                          Since 0.0.34 the server answers the CORS preflight correctly but omits Access-Control-Allow-Origin from the actual response. Any browser-context client whose origin differs from the server's therefore discards the response, and the desktop app never gets past Failed to connect. Reconnecting....

                          0.0.33 is the last version that sends the header. Rolling the server back to 0.0.33 — changing nothing else — restored the desktop client immediately.

                          Environment

                          • Server: headless t3 serve --host <tailnet-ip> --port 3010, Linux x64, Bun runtime
                          • Reached from the desktop over a separate HTTPS origin via Tailscale Serve
                            (https://<host>.ts.net:8445http://<tailnet-ip>:3010), valid Let's Encrypt cert
                          • Desktop app: macOS, tested at 0.0.33, 0.0.35, 0.0.36, 0.0.37 — all fail against a 0.0.34+ server

                          Reproduction

                          t3 serve --host 127.0.0.1 --port 3099 --base-dir /tmp/t3probe
                          curl -sD- -o/dev/null -H 'Origin: https://app.t3.codes' \
                          http://127.0.0.1:3099/.well-known/t3/environment | grep -i access-control
                          Server versionReleasedAccess-Control-Allow-Origin on GET
                          0.0.332026-08-10*
                          0.0.342026-08-26absent
                          0.0.352026-08-27absent
                          0.0.362026-08-29absent
                          0.0.372026-08-31absent
                          0.0.38-nightly.202608312026-08-31absent

                          The preflight still looks right on 0.0.37:

                          $ curl -sD- -o/dev/null -X OPTIONS -H 'Origin: https://app.t3.codes' \
                          -H 'Access-Control-Request-Method: GET' .../.well-known/t3/environment
                          HTTP/2 204
                          access-control-allow-origin: *
                          access-control-allow-methods: GET, POST, OPTIONS
                          access-control-allow-headers: authorization,b3,traceparent,content-type,dpop
                          access-control-max-age: 600
                          

                          The same GET that lacks the header returns 200 with the expected body, so this is purely a response-header regression. Reproduced on /.well-known/t3/environment, /api/auth/session and /health, and with every Origin value tried (https://app.t3.codes, file://, app://-, null, http://localhost:5173, the server's own origin).

                          Symptom in the desktop app

                          Provider settings are unavailable
                          Failed to connect. Reconnecting... Reason: Failed to fetch remote environment endpoint
                          https://<host>.ts.net:8445/.well-known/t3/environment
                          (HttpClientError: Transport error (GET https://<host>.ts.net:8445/.well-known/t3/environment))
                          

                          What rules out a network or TLS problem:

                          • curl -v from the same Mac, at the same moment, completes the handshake and returns HTTP/2 200 with the descriptor JSON; cert verifies, ALPN negotiates h2.
                          • The iOS app connected to the same 0.0.35 server successfully while the desktop could not. A native client does not enforce CORS; curl does not either. Only the browser-context client fails.
                          • Server-side /api/auth/clients showed no connection attempt from the desktop after the upgrade, while the stored bearer token was still valid for another two weeks.

                          Why it may have gone unnoticed

                          The common setup is a desktop app talking to a server on the same machine over loopback, where the request is not cross-origin. It only bites when the environment is remote and published on a different origin — the documented Tailscale Serve setup in docs/user/remote-access.md.

                          Expected

                          Responses carry Access-Control-Allow-Origin (as 0.0.33 did), so a remote environment on its own HTTPS origin stays reachable from browser-context clients.

                          Failing that, a documented way to configure allowed origins would be enough — there is no server setting for it today.

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            No labels
                            No labels

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              Access-Control-Allow-Origin missing from responses since 0.0.34 — desktop app cannot connect to a remote environment on a separate HTTPS origin #8878

                              Description

                              @epavlenko

                              Summary

                              Since 0.0.34 the server answers the CORS preflight correctly but omits Access-Control-Allow-Origin from the actual response. Any browser-context client whose origin differs from the server's therefore discards the response, and the desktop app never gets past Failed to connect. Reconnecting....

                              0.0.33 is the last version that sends the header. Rolling the server back to 0.0.33 — changing nothing else — restored the desktop client immediately.

                              Environment

                              • Server: headless t3 serve --host <tailnet-ip> --port 3010, Linux x64, Bun runtime
                              • Reached from the desktop over a separate HTTPS origin via Tailscale Serve
                                (https://<host>.ts.net:8445http://<tailnet-ip>:3010), valid Let's Encrypt cert
                              • Desktop app: macOS, tested at 0.0.33, 0.0.35, 0.0.36, 0.0.37 — all fail against a 0.0.34+ server

                              Reproduction

                              t3 serve --host 127.0.0.1 --port 3099 --base-dir /tmp/t3probe
                              curl -sD- -o/dev/null -H 'Origin: https://app.t3.codes' \
                              http://127.0.0.1:3099/.well-known/t3/environment | grep -i access-control
                              Server versionReleasedAccess-Control-Allow-Origin on GET
                              0.0.332026-08-10*
                              0.0.342026-08-26absent
                              0.0.352026-08-27absent
                              0.0.362026-08-29absent
                              0.0.372026-08-31absent
                              0.0.38-nightly.202608312026-08-31absent

                              The preflight still looks right on 0.0.37:

                              $ curl -sD- -o/dev/null -X OPTIONS -H 'Origin: https://app.t3.codes' \
                              -H 'Access-Control-Request-Method: GET' .../.well-known/t3/environment
                              HTTP/2 204
                              access-control-allow-origin: *
                              access-control-allow-methods: GET, POST, OPTIONS
                              access-control-allow-headers: authorization,b3,traceparent,content-type,dpop
                              access-control-max-age: 600
                              

                              The same GET that lacks the header returns 200 with the expected body, so this is purely a response-header regression. Reproduced on /.well-known/t3/environment, /api/auth/session and /health, and with every Origin value tried (https://app.t3.codes, file://, app://-, null, http://localhost:5173, the server's own origin).

                              Symptom in the desktop app

                              Provider settings are unavailable
                              Failed to connect. Reconnecting... Reason: Failed to fetch remote environment endpoint
                              https://<host>.ts.net:8445/.well-known/t3/environment
                              (HttpClientError: Transport error (GET https://<host>.ts.net:8445/.well-known/t3/environment))
                              

                              What rules out a network or TLS problem:

                              • curl -v from the same Mac, at the same moment, completes the handshake and returns HTTP/2 200 with the descriptor JSON; cert verifies, ALPN negotiates h2.
                              • The iOS app connected to the same 0.0.35 server successfully while the desktop could not. A native client does not enforce CORS; curl does not either. Only the browser-context client fails.
                              • Server-side /api/auth/clients showed no connection attempt from the desktop after the upgrade, while the stored bearer token was still valid for another two weeks.

                              Why it may have gone unnoticed

                              The common setup is a desktop app talking to a server on the same machine over loopback, where the request is not cross-origin. It only bites when the environment is remote and published on a different origin — the documented Tailscale Serve setup in docs/user/remote-access.md.

                              Expected

                              Responses carry Access-Control-Allow-Origin (as 0.0.33 did), so a remote environment on its own HTTPS origin stays reachable from browser-context clients.

                              Failing that, a documented way to configure allowed origins would be enough — there is no server setting for it today.

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                No labels
                                No labels

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions