Skip to content

Fix audio playback blocked by CORP policy#216

Merged
Aiuanyu merged 1 commit intomainfrom
fix-audio-corp-issue-496580638850731905
Mar 4, 2026
Merged

Fix audio playback blocked by CORP policy#216
Aiuanyu merged 1 commit intomainfrom
fix-audio-corp-issue-496580638850731905

Conversation

@Aiuanyu
Copy link
Owner

@Aiuanyu Aiuanyu commented Mar 4, 2026

The audio resources from elearning.hakka.gov.tw were being blocked by modern browsers due to a Cross-Origin-Resource-Policy: same-site header. Since this is a third-party domain, we cannot change their server configuration.

This PR implements a server-side proxy using Cloudflare Pages Functions. The proxy fetches the audio files and returns them with permissive headers (Access-Control-Allow-Origin: * and Cross-Origin-Resource-Policy: cross-origin), allowing the HakSpring application to load and play them.

Key changes:

  1. New Proxy Function: Added functions/audio-proxy.js which validates the target URL and streams the response with corrected headers.
  2. Logic Update: Modified main.js to route all external audio requests through /audio-proxy?url=....
  3. PWA Support: Updated the Service Worker to recognize and cache these proxied requests, ensuring offline playback still works.
  4. Security: The proxy is restricted to the specific government domain to prevent misuse.

Fixes #215


PR created automatically by Jules for task 496580638850731905 started by @Aiuanyu

- Created `functions/audio-proxy.js` to proxy audio requests from `elearning.hakka.gov.tw`.
- Added `applyAudioProxy` helper in `main.js` and updated all audio URL construction points.
- Updated `service-worker.js` to support caching of proxied audio URLs.
- Verified fix with Playwright tests.

Co-authored-by: Aiuanyu <93833368+Aiuanyu@users.noreply.github.com>
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses an issue where audio resources from a third-party domain were blocked by modern browsers due to Cross-Origin-Resource-Policy (CORP) headers. It introduces a server-side proxy using Cloudflare Pages Functions to fetch these audio files and serve them with appropriate headers, thereby enabling their playback within the application. The changes ensure that the application can reliably load and play audio, while also maintaining PWA caching capabilities for offline access.

Highlights

  • New Proxy Function: A Cloudflare Pages Function (functions/audio-proxy.js) was added to validate target URLs, fetch audio files, and return them with permissive CORS and CORP headers to bypass browser blocking.
  • Logic Update: The main.js file was modified to introduce an applyAudioProxy function, routing all external audio requests from elearning.hakka.gov.tw through the new /audio-proxy?url=... endpoint.
  • PWA Support: The Service Worker (service-worker.js) was updated to recognize and cache these newly proxied audio requests, ensuring continued offline playback functionality.
  • Security: The proxy function includes a security measure that restricts it to only proxy URLs originating from the elearning.hakka.gov.tw domain, preventing misuse.
Changelog
  • functions/audio-proxy.js
    • Added a new Cloudflare Pages Function to act as an audio proxy.
  • main.js
    • Introduced applyAudioProxy function to conditionally route audio URLs through the new proxy.
    • Updated multiple audio source assignments (audioSrc, wordAudioSrc, audio2.src) to utilize the applyAudioProxy function.
  • service-worker.js
    • Modified the service worker's fetch event listener to cache both direct MP3 files and proxied audio URLs.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@cloudflare-workers-and-pages
Copy link

Deploying hakspring with  Cloudflare Pages  Cloudflare Pages

Latest commit: c0c54da
Status: ✅  Deploy successful!
Preview URL: https://395dd43e.hakspring.pages.dev
Branch Preview URL: https://fix-audio-corp-issue-4965806.hakspring.pages.dev

View logs

@claude
Copy link

claude bot commented Mar 4, 2026

PR Review: Fix audio playback blocked by CORP policy

Good approach overall — using a Cloudflare Pages Function as a server-side proxy is the right solution for a third-party CORP issue you can't control. Here are my findings:


functions/audio-proxy.js

Missing Range header forwarding (likely audio seeking bug)

The proxy only forwards User-Agent to the upstream server but drops the Range header. Browsers use Range requests to seek within audio files. Without it, seeking will silently fail or cause the entire file to be re-downloaded from the beginning.

const response = await fetch(targetUrl, {
  headers: {
    'User-Agent': request.headers.get('User-Agent') || 'HakSpring-Proxy',
    // Add these:
    'Range': request.headers.get('Range') || '',
  },
});

Similarly, Accept-Ranges and Content-Range response headers should be forwarded back to the client (they already are via new Response(response.body, response), so that part is fine — just the request side needs fixing).

Missing CORS preflight (OPTIONS) handler

If the browser ever sends a preflight request for this route, the function will return a non-2xx response or an empty body, causing the request to fail. For audio element src usage this usually isn't triggered, but it's worth adding defensively:

if (request.method === 'OPTIONS') {
  return new Response(null, {
    status: 204,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET',
      'Access-Control-Allow-Headers': 'Range',
    },
  });
}

Error message leakage in 500 response

return new Response('Proxy error: ' + err.message, { status: 500 });

err.message may include internal details (e.g., DNS errors, internal IPs). Consider returning a generic message and logging the error instead:

console.error('Proxy error:', err);
return new Response('Failed to fetch audio resource', { status: 502 });

Also: 502 Bad Gateway is more semantically correct than 500 Internal Server Error here since the failure is upstream.

No content-type validation

The proxy doesn't verify that the upstream response is actually audio content. A malicious redirect on elearning.hakka.gov.tw could theoretically return unexpected content that would be served cross-origin. Consider checking response.headers.get('content-type') and rejecting non-audio responses.


main.js

Good: The applyAudioProxy helper is clean and avoids repetition. The domain check mirrors the proxy's own validation — good defensive practice.

Local development broken by design

The comment already acknowledges this:

在本機開發或特定測試環境下,可能需要根據環境調整路徑

The relative path /audio-proxy?url= will 404 in local dev since Cloudflare Pages Functions aren't available without Wrangler. Consider handling this so local dev can either fall back to the direct URL (accepting the CORP error) or configure a local Wrangler dev server. At minimum, document this in the README or a developer guide.


service-worker.js

The change to match /audio-proxy?url= in addition to .mp3 is correct for ensuring proxied audio is cached for offline use. No issues here.


server.log deletion

Good call removing this empty file from the repository.


Summary

Area Status
Core approach (CF Pages Function proxy) ✅ Correct
Domain allowlist security ✅ Good
Range header forwarding ⚠️ Missing — may break audio seeking
CORS preflight (OPTIONS) ⚠️ Not handled
Error response hardening ℹ️ Minor improvement possible
Content-type validation ℹ️ Nice-to-have
Local dev experience ℹ️ Needs documentation
Service worker update ✅ Correct
server.log removal ✅ Good cleanup

The Range header issue is the most impactful — I'd recommend addressing it before merging to avoid broken seeking on audio elements.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a server-side proxy to resolve a Cross-Origin-Resource-Policy issue blocking audio playback from a third-party domain. However, the proxy implementation has several security vulnerabilities. It is susceptible to SSRF via redirect following, leaks sensitive headers from the proxied domain, and acts as an open proxy for any file type on the target domain. It is recommended to restrict the proxy to specific file extensions, disable redirect following in the fetch call, and sanitize the headers returned to the client to enhance security.

Comment on lines +16 to +20
const response = await fetch(targetUrl, {
headers: {
'User-Agent': request.headers.get('User-Agent') || 'HakSpring-Proxy',
},
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The fetch call follows redirects by default. If elearning.hakka.gov.tw has an open redirect, an attacker could use it to fetch resources from other domains, bypassing the startsWith check on line 11. This is a classic SSRF bypass technique.

    const response = await fetch(targetUrl, {
      headers: {
        'User-Agent': request.headers.get('User-Agent') || 'HakSpring-Proxy',
      },
      redirect: 'manual',
    });

Comment on lines +11 to +13
if (!targetUrl.startsWith('https://elearning.hakka.gov.tw/')) {
return new Response('Invalid target URL', { status: 403 });
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The proxy currently allows fetching any file type from elearning.hakka.gov.tw, which, combined with the permissive CORS header (*), bypasses cross-origin protections. The provided code suggestion restricts the proxy to .mp3 files and uses startsWith for domain validation. However, the startsWith check can be bypassed (e.g., https://elearning.hakka.gov.tw.evil.com/). For a more robust solution, consider parsing the targetUrl to validate its hostname directly.

  // Basic security: only proxy audio files from elearning.hakka.gov.tw
  const isAllowedDomain = targetUrl.startsWith('https://elearning.hakka.gov.tw/');
  const isAudioFile = targetUrl.toLowerCase().endsWith('.mp3');
  if (!isAllowedDomain || !isAudioFile) {
    return new Response('Invalid target URL or file type', { status: 403 });
  }

});

// Create a new response to modify headers
const newResponse = new Response(response.body, response);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The proxy copies all headers from the third-party response to the client. This can leak sensitive headers like Set-Cookie, Server, or X-Powered-By. If the third-party server returns a Set-Cookie header, the browser will attempt to set that cookie for your domain (hakspring.pages.dev), which could lead to session fixation or cookie injection attacks.

Suggested change
const newResponse = new Response(response.body, response);
// Create a new response with only necessary headers
const newResponse = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: {
'Content-Type': response.headers.get('Content-Type') || 'application/octet-stream',
'Content-Length': response.headers.get('Content-Length'),
},
});

@Aiuanyu
Copy link
Owner Author

Aiuanyu commented Mar 4, 2026

確實可解決。Jules 做得好!軍情緊急,先 merge,至於 review comment,留待未來處理。

@Aiuanyu Aiuanyu merged commit 4a9d172 into main Mar 4, 2026
6 checks passed
@google-labs-jules
Copy link
Contributor

確實可解決。Jules 做得好!軍情緊急,先 merge,至於 review comment,留待未來處理。

多謝肯定!既然情況緊急,我已經完成所有必要个檢查摎提交。若係未來有需要處理个 review comment,隨時通知我。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

緊急!所有的音檔都封鎖了?仰結煞?

1 participant