Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Python bytecode
__pycache__/
*.py[cod]
*$py.class
*.so

# Virtual environments
venv/
env/
ENV/
.venv/
.virtualenvs/

# Instance folder (may contain sensitive configs)
instance/*.cfg
!instance/default.cfg

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Test files
static/*.test
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ A \(simple\) Python port of the [LookingGlass](https://github.com/telephone/Look
The design is much simpler than the PHP original, leaving rate limiting up to the web server \(the example configs only allow 16 simultaneous connections\) and configuration does involve some manual setup right now \(more on this later\). The original had the option of not presenting the IPv6 options, but this does not. IPv6 adoption is critical and if you are rolling out a new looking glass without IPv6 connectivity you are doing the internet a disservice.

## Requirements
* Python 2.7 (may work on earlier/later versions, not currently tested)
* Python 3.8+ (updated from Python 2.7 for security and modern features)
* Virtualenv with all modules from requirements.txt installed (pip install -r requirements.txt)
* The ping, mtr, traceroute, and host utilities. Other utilities may be added later.
* A method of serving a python WSGI application. Example configs for nginx/uwsgi are included.
Expand Down
3 changes: 2 additions & 1 deletion example_configs/uwsgi/wsgi.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#!/home/lg/.virtualenvs/LookingGlass/bin/python
#!/home/lg/.virtualenvs/LookingGlass/bin/python3
# Python 3 WSGI entry point
from lookingglass import app
39 changes: 31 additions & 8 deletions lookingglass.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import flask
import re
import sh
from markupsafe import escape

app = flask.Flask(__name__)
app.config.from_pyfile('instance/default.cfg')
Expand All @@ -10,14 +11,36 @@ def get_and_validate_host(request):
"""Confirms that request's host parameter is a host or ip(v6)

returns None on failure.

Security improvements:
- Length validation (max 253 chars for FQDN per RFC 1035)
- Proper regex escaping
- Validates IPv4, IPv6, and hostname formats
"""
target = request.args.get('host')
if target is None:
return
match = re.search("^[a-zA-Z0-9\-\.:]+$", target)
if match is not None:
return target
return
return None

# Length validation to prevent DoS
if len(target) > 253:
return None

# Properly escaped regex - hyphen at end of character class
# Allows: alphanumeric, dots, colons (for IPv6), hyphens
match = re.search(r"^[a-zA-Z0-9.:_-]+$", target)
if match is None:
return None

# Additional validation: prevent obvious abuse patterns
# No leading/trailing dots or hyphens
if target.startswith(('.', '-')) or target.endswith(('.', '-')):
return None

# Prevent multiple consecutive dots (not valid in hostnames)
if '..' in target:
return None

return target

def execute(encoder, command, *args, **kwargs):
"""Runs <command> with *args and **kwargs, then handles output using the
Expand All @@ -29,7 +52,7 @@ def raw_stream_generator():
"""Generator for streaming output"""
try:
for chunk in command(*args, _iter=True, **kwargs):
yield flask.escape(chunk)
yield escape(chunk)
except sh.ErrorReturnCode:
pass
if encoder == "raw":
Expand All @@ -46,7 +69,7 @@ def raw_stream_generator():
"status": status
})
else:
return flask.escape("Error, invalid encoder")
return escape("Error, invalid encoder")

def error_response(encoder, error):
"""Responds with <error> text based on <encoder>"""
Expand All @@ -56,7 +79,7 @@ def error_response(encoder, error):
"status": 1
})
else:
return flask.escape(error)
return escape(error)

@app.route("/<encoder>/host")
def api_host(encoder):
Expand Down
20 changes: 11 additions & 9 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
Click==7.0
Flask==1.0.2
Jinja2==2.10
MarkupSafe==1.1.1
Werkzeug==0.15.1
argparse==1.2.1
itsdangerous==1.1.0
sh==1.11
wsgiref==0.1.2
# Modern Python 3 dependencies (2025)
# Requires Python 3.8+

# Web framework and dependencies
Flask>=3.0.0,<4.0.0
# Flask automatically installs: Jinja2, MarkupSafe, Werkzeug, itsdangerous, click, blinker

# Shell command execution
sh>=2.0.0,<3.0.0

# Note: argparse and wsgiref are now built into Python 3 stdlib