Skip to content

Comments

Add tuner validation with SSRF protection and single-channel M3U8 support#82

Closed
Copilot wants to merge 4 commits intomainfrom
copilot/add-safety-checks-tuner-management
Closed

Add tuner validation with SSRF protection and single-channel M3U8 support#82
Copilot wants to merge 4 commits intomainfrom
copilot/add-safety-checks-tuner-management

Conversation

Copy link
Contributor

Copilot AI commented Feb 15, 2026

Tuner URLs were accepted without validation, allowing duplicates, malformed URLs, and potential SSRF attacks. M3U parser only handled multi-channel playlists.

Changes

Tuner validation (add_tuner())

  • Reject duplicate names before DB insert
  • Enforce http:///https:// schemes for M3U (required) and XML (optional)
  • Verify M3U reachability via HEAD request with 5s timeout
  • Block localhost (127.0.0.0/8) and link-local (169.254.0.0/16) addresses

Error handling (/change_tuner route)

  • Wrap add_tuner() in try-except to surface ValueError as flash messages
  • Log validation failures

Single-channel M3U8 (parse_m3u())

  • Detect playlists lacking #EXTINF tags
  • Extract single stream URL and derive channel name from path
  • Fallback to "Live Stream" for empty paths

Tests

  • 17 unit tests covering validation, SSRF protection, and M3U parsing

Example

# Before
add_tuner("IPTV", "", "ftp://bad.url")  # Silently inserts

# After
add_tuner("IPTV", "", "ftp://bad.url")
# ValueError: M3U URL must start with http:// or https://

add_tuner("IPTV", "", "http://localhost:8080/stream")
# ValueError: M3U URL cannot point to localhost (127.0.0.0/8)
Original prompt

Overview

Add safety checks and validation to tuner management, plus support for single-channel M3U8 playlists.

Features to Implement

1. Add Safety Checks in add_tuner()

File: app.py - add_tuner() function (around line 208)

Add the following validation before inserting into the database:

Duplicate Name Prevention

  • Check if tuner name already exists in the database
  • Raise a clear error message if duplicate found
  • Display user-friendly flash message in the UI

URL Validation

  • Validate that M3U URL starts with http:// or https://
  • Validate that XML URL (if provided) starts with http:// or https://
  • Check URLs are not empty strings

Optional URL Reachability Check

  • Attempt to ping/HEAD request the M3U URL with a 5-second timeout
  • Provide helpful error message if URL is unreachable
  • Make this check optional/graceful (don't block if network is slow)

Error Handling in /change_tuner Route

  • Modify the /change_tuner route (around line 675-760) to catch validation exceptions
  • Use flash() to display user-friendly error messages
  • Prevent bad data from being inserted into the database

Example validation logic:

def add_tuner(name, xml_url, m3u_url):
    """Insert a new tuner into DB with validation."""
    # Check for duplicate name
    tuners = get_tuners()
    if name in tuners:
        raise ValueError(f"Tuner '{name}' already exists")
    
    # Validate M3U URL
    if not m3u_url or not m3u_url.strip():
        raise ValueError("M3U URL is required")
    if not m3u_url.startswith(('http://', 'https://')):
        raise ValueError("M3U URL must start with http:// or https://")
    
    # Validate XML URL if provided
    if xml_url and xml_url.strip():
        if not xml_url.startswith(('http://', 'https://')):
            raise ValueError("XML URL must start with http:// or https://")
    
    # Optional: Check URL reachability
    try:
        r = requests.head(m3u_url, timeout=5, allow_redirects=True)
        r.raise_for_status()
    except requests.RequestException as e:
        raise ValueError(f"M3U URL unreachable: {str(e)}")
    
    # Existing insert logic
    with sqlite3.connect(TUNER_DB, timeout=10) as conn:
        c = conn.cursor()
        c.execute(
            "INSERT INTO tuners (name, xml, m3u) VALUES (?, ?, ?)",
            (name, xml_url, m3u_url)
        )
        conn.commit()

2. Support for Single-Channel M3U8 Playlists

File: app.py - parse_m3u() function (around line 265-287)

Currently the parser expects multi-channel M3U format with #EXTINF tags. Add support for simple single-channel M3U8 files that contain just a stream URL.

Detection Logic

  • After fetching the M3U content, check if it contains any #EXTINF lines
  • If no #EXTINF found, check if the file contains a single valid stream URL
  • Support both .m3u8 HLS streams and direct video URLs

Implementation

Add this logic at the start of the channel parsing loop:

def parse_m3u(m3u_url):
    channels = []
    try:
        r = requests.get(m3u_url, timeout=10)
        r.raise_for_status()
        lines = r.text.splitlines()
    except:
        return channels
    
    # Filter out empty lines and comments (except #EXTINF)
    non_empty_lines = [line.strip() for line in lines if line.strip()]
    
    # Check if this is a single-channel playlist (no #EXTINF tags)
    has_extinf = any(line.startswith('#EXTINF:') for line in non_empty_lines)
    
    if not has_extinf:
        # Look for a single stream URL
        stream_urls = [line for line in non_empty_lines 
                      if line.startswith(('http://', 'https://')) 
                      and not line.startswith('#')]
        
        if len(stream_urls) == 1:
            url = stream_urls[0]
            # Extract a channel name from the URL or use default
            try:
                from urllib.parse import urlparse
                parsed = urlparse(url)
                name = parsed.path.split('/')[-1].replace('.m3u8', '').replace('_', ' ').title()
                if not name:
                    name = 'Live Stream'
            except:
                name = 'Live Stream'
            
            channels.append({
                'name': name,
                'logo': '',
                'url': url,
                'tvg_id': 'stream_1'
            })
            return channels
    
    # Existing multi-channel parsing logic continues here...
    for i, line in enumerate(lines):
        if line.startswith('#EXTINF:'):
            # ... existing code ...

Testing Requirements

  1. Test adding a tuner with duplicate name - should show error
  2. Test adding a tuner with invalid URL (no http/https) - should show error
  3. Test adding a tuner with unreachable URL - should show helpful error
  4. Test adding a single-channel M3U8 file - should create one channel entry
  5. Test existing multi-channel M3U files still work correctly

Files to Modify

  • `app....

This pull request was created from Copilot chat.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 3 commits February 15, 2026 03:19
Co-authored-by: thehack904 <35552907+thehack904@users.noreply.github.com>
Co-authored-by: thehack904 <35552907+thehack904@users.noreply.github.com>
Co-authored-by: thehack904 <35552907+thehack904@users.noreply.github.com>
Copilot AI changed the title [WIP] Add safety checks and validation to tuner management Add validation and SSRF protection to tuner management; support single-channel M3U8 Feb 15, 2026
Copilot AI requested a review from thehack904 February 15, 2026 03:26
Copilot AI changed the title Add validation and SSRF protection to tuner management; support single-channel M3U8 Add tuner validation, SSRF protection, and single-channel M3U8 support Feb 15, 2026
Copilot AI changed the title Add tuner validation, SSRF protection, and single-channel M3U8 support Add tuner validation with SSRF protection and single-channel M3U8 support Feb 15, 2026
@thehack904 thehack904 closed this Feb 16, 2026
@thehack904 thehack904 deleted the copilot/add-safety-checks-tuner-management branch February 16, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants