diff --git a/ddtrace/appsec/_iast/_handlers.py b/ddtrace/appsec/_iast/_handlers.py index 7a5113a7ddd..7d00fd87602 100644 --- a/ddtrace/appsec/_iast/_handlers.py +++ b/ddtrace/appsec/_iast/_handlers.py @@ -5,7 +5,9 @@ from wrapt import wrap_function_wrapper as _w from ddtrace.appsec._iast import _is_iast_enabled +from ddtrace.appsec._iast._iast_request_context import get_iast_stacktrace_reported from ddtrace.appsec._iast._iast_request_context import in_iast_context +from ddtrace.appsec._iast._iast_request_context import set_iast_stacktrace_reported from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source from ddtrace.appsec._iast._patch import _iast_instrument_starlette_request from ddtrace.appsec._iast._patch import _iast_instrument_starlette_request_body @@ -439,3 +441,69 @@ def _on_set_request_tags_iast(request, span, flask_config): OriginType.PARAMETER, override_pyobject_tainted=True, ) + + +def _on_django_finalize_response_pre(ctx, after_request_tags, request, response): + if not response or get_iast_stacktrace_reported() or not _is_iast_enabled() or not is_iast_request_enabled(): + return + + try: + from .taint_sinks.stacktrace_leak import asm_check_stacktrace_leak + + content = response.content.decode("utf-8", errors="ignore") + asm_check_stacktrace_leak(content) + except Exception: + log.debug("Unexpected exception checking for stacktrace leak", exc_info=True) + + +def _on_django_technical_500_response(request, response, exc_type, exc_value, tb): + if not _is_iast_enabled() or not is_iast_request_enabled() or not exc_value: + return + + try: + from .taint_sinks.stacktrace_leak import asm_report_stacktrace_leak_from_django_debug_page + + exc_name = exc_type.__name__ + module = tb.tb_frame.f_globals.get("__name__", "") + asm_report_stacktrace_leak_from_django_debug_page(exc_name, module) + except Exception: + log.debug("Unexpected exception checking for stacktrace leak on 500 response view", exc_info=True) + + +def _on_flask_finalize_request_post(response, _): + if not response or get_iast_stacktrace_reported() or not _is_iast_enabled() or not is_iast_request_enabled(): + return + + try: + from .taint_sinks.stacktrace_leak import asm_check_stacktrace_leak + + content = response[0].decode("utf-8", errors="ignore") + asm_check_stacktrace_leak(content) + except Exception: + log.debug("Unexpected exception checking for stacktrace leak", exc_info=True) + + +def _on_asgi_finalize_response(body, _): + if not _is_iast_enabled() or not is_iast_request_enabled() or not body: + return + + try: + from .taint_sinks.stacktrace_leak import asm_check_stacktrace_leak + + content = body.decode("utf-8", errors="ignore") + asm_check_stacktrace_leak(content) + except Exception: + log.debug("Unexpected exception checking for stacktrace leak", exc_info=True) + + +def _on_werkzeug_render_debugger_html(html): + if not _is_iast_enabled() or not is_iast_request_enabled() or not html: + return + + try: + from .taint_sinks.stacktrace_leak import asm_check_stacktrace_leak + + asm_check_stacktrace_leak(html) + set_iast_stacktrace_reported(True) + except Exception: + log.debug("Unexpected exception checking for stacktrace leak", exc_info=True) diff --git a/ddtrace/appsec/_iast/_iast_request_context.py b/ddtrace/appsec/_iast/_iast_request_context.py index 07ad4c9c238..3a9acd2ce4e 100644 --- a/ddtrace/appsec/_iast/_iast_request_context.py +++ b/ddtrace/appsec/_iast/_iast_request_context.py @@ -46,6 +46,7 @@ def __init__(self, span: Optional[Span] = None): self.iast_reporter: Optional[IastSpanReporter] = None self.iast_span_metrics: Dict[str, int] = {} self.iast_stack_trace_id: int = 0 + self.iast_stack_trace_reported: bool = False def _get_iast_context() -> Optional[IASTEnvironment]: @@ -88,6 +89,19 @@ def get_iast_reporter() -> Optional[IastSpanReporter]: return None +def get_iast_stacktrace_reported() -> bool: + env = _get_iast_context() + if env: + return env.iast_stack_trace_reported + return False + + +def set_iast_stacktrace_reported(reported: bool) -> None: + env = _get_iast_context() + if env: + env.iast_stack_trace_reported = reported + + def get_iast_stacktrace_id() -> int: env = _get_iast_context() if env: diff --git a/ddtrace/appsec/_iast/_listener.py b/ddtrace/appsec/_iast/_listener.py index 953e22b1288..37f721438d6 100644 --- a/ddtrace/appsec/_iast/_listener.py +++ b/ddtrace/appsec/_iast/_listener.py @@ -1,11 +1,16 @@ +from ddtrace.appsec._iast._handlers import _on_asgi_finalize_response +from ddtrace.appsec._iast._handlers import _on_django_finalize_response_pre from ddtrace.appsec._iast._handlers import _on_django_func_wrapped from ddtrace.appsec._iast._handlers import _on_django_patch +from ddtrace.appsec._iast._handlers import _on_django_technical_500_response +from ddtrace.appsec._iast._handlers import _on_flask_finalize_request_post from ddtrace.appsec._iast._handlers import _on_flask_patch from ddtrace.appsec._iast._handlers import _on_grpc_response from ddtrace.appsec._iast._handlers import _on_pre_tracedrequest_iast from ddtrace.appsec._iast._handlers import _on_request_init from ddtrace.appsec._iast._handlers import _on_set_http_meta_iast from ddtrace.appsec._iast._handlers import _on_set_request_tags_iast +from ddtrace.appsec._iast._handlers import _on_werkzeug_render_debugger_html from ddtrace.appsec._iast._handlers import _on_wsgi_environ from ddtrace.appsec._iast._iast_request_context import _iast_end_request from ddtrace.internal import core @@ -18,11 +23,16 @@ def iast_listen(): core.on("set_http_meta_for_asm", _on_set_http_meta_iast) core.on("django.patch", _on_django_patch) core.on("django.wsgi_environ", _on_wsgi_environ, "wrapped_result") + core.on("django.finalize_response.pre", _on_django_finalize_response_pre) core.on("django.func.wrapped", _on_django_func_wrapped) + core.on("django.technical_500_response", _on_django_technical_500_response) core.on("flask.patch", _on_flask_patch) core.on("flask.request_init", _on_request_init) core.on("flask._patched_request", _on_pre_tracedrequest_iast) core.on("flask.set_request_tags", _on_set_request_tags_iast) + core.on("flask.finalize_request.post", _on_flask_finalize_request_post) + core.on("asgi.finalize_response", _on_asgi_finalize_response) + core.on("werkzeug.render_debugger_html", _on_werkzeug_render_debugger_html) core.on("context.ended.wsgi.__call__", _iast_end_request) core.on("context.ended.asgi.__call__", _iast_end_request) diff --git a/ddtrace/appsec/_iast/constants.py b/ddtrace/appsec/_iast/constants.py index 55ec5a5e740..83284094dcf 100644 --- a/ddtrace/appsec/_iast/constants.py +++ b/ddtrace/appsec/_iast/constants.py @@ -1,3 +1,4 @@ +import re from typing import Any from typing import Dict @@ -14,6 +15,7 @@ VULN_HEADER_INJECTION = "HEADER_INJECTION" VULN_CODE_INJECTION = "CODE_INJECTION" VULN_SSRF = "SSRF" +VULN_STACKTRACE_LEAK = "STACKTRACE_LEAK" VULNERABILITY_TOKEN_TYPE = Dict[int, Dict[str, Any]] @@ -27,6 +29,12 @@ RC2_DEF = "rc2" RC4_DEF = "rc4" IDEA_DEF = "idea" +STACKTRACE_RE_DETECT = re.compile(r"Traceback \(most recent call last\):") +HTML_TAGS_REMOVE = re.compile(r"<!--[\s\S]*?-->|<[^>]*>|&#\w+;") +STACKTRACE_FILE_LINE = re.compile(r"File (.*?), line (\d+), in (.+)") +STACKTRACE_EXCEPTION_REGEX = re.compile( + r"^(?P<exc>[A-Za-z_]\w*(?:Error|Exception|Interrupt|Fault|Warning))" r"(?:\s*:\s*(?P<msg>.*))?$" +) DEFAULT_WEAK_HASH_ALGORITHMS = {MD5_DEF, SHA1_DEF} diff --git a/ddtrace/appsec/_iast/taint_sinks/stacktrace_leak.py b/ddtrace/appsec/_iast/taint_sinks/stacktrace_leak.py new file mode 100644 index 00000000000..2fa9a0016a9 --- /dev/null +++ b/ddtrace/appsec/_iast/taint_sinks/stacktrace_leak.py @@ -0,0 +1,102 @@ +import os +import re + +from ..._constants import IAST_SPAN_TAGS +from .. import oce +from .._iast_request_context import set_iast_stacktrace_reported +from .._metrics import _set_metric_iast_executed_sink +from .._metrics import increment_iast_span_metric +from .._taint_tracking._errors import iast_taint_log_error +from ..constants import HTML_TAGS_REMOVE +from ..constants import STACKTRACE_EXCEPTION_REGEX +from ..constants import STACKTRACE_FILE_LINE +from ..constants import VULN_STACKTRACE_LEAK +from ..taint_sinks._base import VulnerabilityBase + + +@oce.register +class StacktraceLeak(VulnerabilityBase): + vulnerability_type = VULN_STACKTRACE_LEAK + skip_location = True + + +def asm_report_stacktrace_leak_from_django_debug_page(exc_name, module): + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, StacktraceLeak.vulnerability_type) + _set_metric_iast_executed_sink(StacktraceLeak.vulnerability_type) + evidence = "Module: %s\nException: %s" % (module, exc_name) + StacktraceLeak.report(evidence_value=evidence) + set_iast_stacktrace_reported(True) + + +def asm_check_stacktrace_leak(content: str) -> None: + if not content: + return + + try: + # Quick check to avoid the slower operations if on stacktrace + if "Traceback (most recent call last):" not in content: + return + + text = HTML_TAGS_REMOVE.sub("", content) + lines = [line.strip() for line in text.splitlines() if line.strip()] + + file_lines = [] + exception_line = "" + + for i, line in enumerate(lines): + if line.startswith("Traceback (most recent call last):"): + # from here until we find an exception line + continue + + # See if this line is a "File ..." line + m_file = STACKTRACE_FILE_LINE.match(line) + if m_file: + file_lines.append(m_file.groups()) + continue + + # See if this line might be the exception line + m_exc = STACKTRACE_EXCEPTION_REGEX.match(line) + if m_exc: + # We consider it as the "final" exception line. Keep it. + exception_line = m_exc.group("exc") + # We won't break immediately because sometimes Django + # HTML stack traces can have repeated exception lines, etc. + # But typically the last match is the real final exception + # We'll keep updating exception_line if we see multiple + continue + + if not file_lines and not exception_line: + return + + module_path = None + if file_lines: + # file_lines looks like [ ("/path/to/file.py", "line_no", "funcname"), ... ] + last_file_entry = file_lines[-1] + module_path = last_file_entry[0] # the path in quotes + + # Attempt to convert a path like "/myproj/foo/bar.py" into "foo.bar" + # or "myproj.foo.bar" depending on your directory structure. + # This is a *best effort* approach (it can be environment-specific). + module_name = "" + if module_path: + mod_no_ext = re.sub(r"\.py$", "", module_path) + parts: list[str] = [] + while True: + head, tail = os.path.split(mod_no_ext) + if tail: + parts.insert(0, tail) + mod_no_ext = head + else: + # might still have a leftover 'head' if it’s not just root + break + + module_name = ".".join(parts) + if not module_name: + module_name = module_path # fallback: just the path + + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, StacktraceLeak.vulnerability_type) + _set_metric_iast_executed_sink(StacktraceLeak.vulnerability_type) + evidence = "Module: %s\nException: %s" % (module_name.strip(), exception_line.strip()) + StacktraceLeak.report(evidence_value=evidence) + except Exception as e: + iast_taint_log_error("[IAST] error in check stacktrace leak. {}".format(e)) diff --git a/ddtrace/contrib/internal/django/patch.py b/ddtrace/contrib/internal/django/patch.py index 7378d8da31c..c51c789aab7 100644 --- a/ddtrace/contrib/internal/django/patch.py +++ b/ddtrace/contrib/internal/django/patch.py @@ -696,6 +696,23 @@ def traced_as_view(django, pin, func, instance, args, kwargs): return wrapt.FunctionWrapper(view, traced_func(django, "django.view", resource=func_name(instance))) +@trace_utils.with_traced_module +def traced_technical_500_response(django, pin, func, instance, args, kwargs): + """ + Wrapper for django's views.debug.technical_500_response + """ + response = func(*args, **kwargs) + try: + request = get_argument_value(args, kwargs, 0, "request") + exc_type = get_argument_value(args, kwargs, 1, "exc_type") + exc_value = get_argument_value(args, kwargs, 2, "exc_value") + tb = get_argument_value(args, kwargs, 3, "tb") + core.dispatch("django.technical_500_response", (request, response, exc_type, exc_value, tb)) + except Exception: + log.debug("Error while trying to trace Django technical 500 response", exc_info=True) + return response + + @trace_utils.with_traced_module def traced_get_asgi_application(django, pin, func, instance, args, kwargs): from ddtrace.contrib.asgi import TraceMiddleware @@ -891,6 +908,9 @@ def _(m): trace_utils.wrap(m, "re_path", traced_urls_path(django)) when_imported("django.views.generic.base")(lambda m: trace_utils.wrap(m, "View.as_view", traced_as_view(django))) + when_imported("django.views.debug")( + lambda m: trace_utils.wrap(m, "technical_500_response", traced_technical_500_response(django)) + ) @when_imported("channels.routing") def _(m): @@ -935,6 +955,7 @@ def _unpatch(django): trace_utils.unwrap(django.conf.urls, "url") trace_utils.unwrap(django.contrib.auth.login, "login") trace_utils.unwrap(django.contrib.auth.authenticate, "authenticate") + trace_utils.unwrap(django.view.debug.technical_500_response, "technical_500_response") if django.VERSION >= (2, 0, 0): trace_utils.unwrap(django.urls, "path") trace_utils.unwrap(django.urls, "re_path") diff --git a/ddtrace/contrib/internal/flask/patch.py b/ddtrace/contrib/internal/flask/patch.py index 7111310b6ef..9476485b314 100644 --- a/ddtrace/contrib/internal/flask/patch.py +++ b/ddtrace/contrib/internal/flask/patch.py @@ -224,6 +224,10 @@ def patch(): _w("flask.templating", "_render", patched_render) _w("flask", "render_template", _build_render_template_wrapper("render_template")) _w("flask", "render_template_string", _build_render_template_wrapper("render_template_string")) + try: + _w("werkzeug.debug.tbtools", "DebugTraceback.render_debugger_html", patched_render_debugger_html) + except AttributeError: + log.debug("Failed to patch DebugTraceback.render_debugger_html, not supported by this werkzeug version") bp_hooks = [ "after_app_request", @@ -380,12 +384,8 @@ def patched_finalize_request(wrapped, instance, args, kwargs): Wrapper for flask.app.Flask.finalize_request """ rv = wrapped(*args, **kwargs) - response = None - headers = None if getattr(rv, "is_sequence", False): - response = rv.response - headers = rv.headers - core.dispatch("flask.finalize_request.post", (response, headers)) + core.dispatch("flask.finalize_request.post", (rv.response, rv.headers)) return rv @@ -419,6 +419,12 @@ def _wrap(rule, endpoint=None, view_func=None, **kwargs): return _wrap(*args, **kwargs) +def patched_render_debugger_html(wrapped, instance, args, kwargs): + res = wrapped(*args, **kwargs) + core.dispatch("werkzeug.render_debugger_html", (res,)) + return res + + def patched_add_url_rule(wrapped, instance, args, kwargs): """Wrapper for flask.app.Flask.add_url_rule to wrap all views attached to this app""" diff --git a/hatch.toml b/hatch.toml index 00fd9cff0a6..ae329db4ea4 100644 --- a/hatch.toml +++ b/hatch.toml @@ -57,7 +57,7 @@ checks = [ "suitespec-check", ] spelling = [ - "codespell -I docs/spelling_wordlist.txt --skip='ddwaf.h,*cassettes*,tests/tracer/fixtures/urls.txt' {args:ddtrace/ tests/ releasenotes/ docs/}", + "codespell -I docs/spelling_wordlist.txt --skip='ddwaf.h,*cassettes*,tests/tracer/fixtures/urls.txt,tests/appsec/iast/fixtures/*' {args:ddtrace/ tests/ releasenotes/ docs/}", ] typing = [ "mypy {args}", diff --git a/releasenotes/notes/stacktrace-leak-d6840ab48b29af99.yaml b/releasenotes/notes/stacktrace-leak-d6840ab48b29af99.yaml new file mode 100644 index 00000000000..937c4a8687c --- /dev/null +++ b/releasenotes/notes/stacktrace-leak-d6840ab48b29af99.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Code Security: Implement the detection of the Stacktrace-Leak vulnerability for + Django, Flask and FastAPI. diff --git a/tests/appsec/iast/fixtures/django_debug_page.html b/tests/appsec/iast/fixtures/django_debug_page.html new file mode 100644 index 00000000000..217a3350ecf --- /dev/null +++ b/tests/appsec/iast/fixtures/django_debug_page.html @@ -0,0 +1,2023 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <meta name="robots" content="NONE,NOARCHIVE"> + <title>IndexError + at /</title> + <style> + html * { padding:0; margin:0; } + body * { padding:10px 20px; } + body * * { padding:0; } + body { font-family: sans-serif; background-color:#fff; color:#000; } + body > :where(header, main, footer) { border-bottom:1px solid #ddd; } + h1 { font-weight:normal; } + h2 { margin-bottom:.8em; } + h3 { margin:1em 0 .5em 0; } + h4 { margin:0 0 .5em 0; font-weight: normal; } + code, pre { font-size: 100%; white-space: pre-wrap; word-break: break-word; } + summary { cursor: pointer; } + table { border:1px solid #ccc; border-collapse: collapse; width:100%; background:white; } + tbody td, tbody th { vertical-align:top; padding:2px 3px; } + thead th { + padding:1px 6px 1px 3px; background:#fefefe; text-align:left; + font-weight:normal; font-size: 0.6875rem; border:1px solid #ddd; + } + tbody th { width:12em; text-align:right; color:#666; padding-right:.5em; } + table.vars { margin:5px 10px 2px 40px; width: auto; } + table.vars td, table.req td { font-family:monospace; } + table td.code { width:100%; } + table td.code pre { overflow:hidden; } + table.source th { color:#666; } + table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; } + ul.traceback { list-style-type:none; color: #222; } + ul.traceback li.cause { word-break: break-word; } + ul.traceback li.frame { padding-bottom:1em; color:#4f4f4f; } + ul.traceback li.user { background-color:#e0e0e0; color:#000 } + div.context { padding:10px 0; overflow:hidden; } + div.context ol { padding-left:30px; margin:0 10px; list-style-position: inside; } + div.context ol li { font-family:monospace; white-space:pre; color:#777; cursor:pointer; padding-left: 2px; } + div.context ol li pre { display:inline; } + div.context ol.context-line li { color:#464646; background-color:#dfdfdf; padding: 3px 2px; } + div.context ol.context-line li span { position:absolute; right:32px; } + .user div.context ol.context-line li { background-color:#bbb; color:#000; } + .user div.context ol li { color:#666; } + div.commands, summary.commands { margin-left: 40px; } + div.commands a, summary.commands { color:#555; text-decoration:none; } + .user div.commands a { color: black; } + #summary { background: #ffc; } + #summary h2 { font-weight: normal; color: #666; } + #info { padding: 0; } + #info > * { padding:10px 20px; } + #explanation { background:#eee; } + #template, #template-not-exist { background:#f6f6f6; } + #template-not-exist ul { margin: 0 0 10px 20px; } + #template-not-exist .postmortem-section { margin-bottom: 3px; } + #unicode-hint { background:#eee; } + #traceback { background:#eee; } + #requestinfo { background:#f6f6f6; padding-left:120px; } + #summary table { border:none; background:transparent; } + #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; } + #requestinfo h3 { margin-bottom:-1em; } + .error { background: #ffc; } + .specific { color:#cc3300; font-weight:bold; } + h2 span.commands { font-size: 0.7rem; font-weight:normal; } + span.commands a:link {color:#5E5694;} + pre.exception_value { font-family: sans-serif; color: #575757; font-size: 1.5rem; margin: 10px 0 10px 0; } + .append-bottom { margin-bottom: 10px; } + .fname { user-select: all; } + </style> + + <script> + function hideAll(elems) { + for (var e = 0; e < elems.length; e++) { + elems[e].style.display = 'none'; + } + } + window.onload = function() { + hideAll(document.querySelectorAll('ol.pre-context')); + hideAll(document.querySelectorAll('ol.post-context')); + hideAll(document.querySelectorAll('div.pastebin')); + } + function toggle() { + for (var i = 0; i < arguments.length; i++) { + var e = document.getElementById(arguments[i]); + if (e) { + e.style.display = e.style.display == 'none' ? 'block': 'none'; + } + } + return false; + } + function switchPastebinFriendly(link) { + s1 = "Switch to copy-and-paste view"; + s2 = "Switch back to interactive view"; + link.textContent = link.textContent.trim() == s1 ? s2: s1; + toggle('browserTraceback', 'pastebinTraceback'); + return false; + } + </script> + +</head> +<body> +<header id="summary"> + <h1>IndexError + at /</h1> + <pre class="exception_value">No exception message supplied</pre> + <table class="meta"> + + <tr> + <th scope="row">Request Method:</th> + <td>GET</td> + </tr> + <tr> + <th scope="row">Request URL:</th> + <td>http://localhost:8000/</td> + </tr> + + <tr> + <th scope="row">Django Version:</th> + <td>5.1.5</td> + </tr> + + <tr> + <th scope="row">Exception Type:</th> + <td>IndexError</td> + </tr> + + + + <tr> + <th scope="row">Exception Location:</th> + <td><span class="fname">/home/foobaruser/sources/minimal-django-example/app.py</span>, line 20, in index_view</td> + </tr> + + + <tr> + <th scope="row">Raised during:</th> + <td>__main__.index_view</td> + </tr> + + <tr> + <th scope="row">Python Executable:</th> + <td>/home/foobaruser/.pyenv/versions/testsca/bin/python</td> + </tr> + <tr> + <th scope="row">Python Version:</th> + <td>3.12.5</td> + </tr> + <tr> + <th scope="row">Python Path:</th> + <td><pre><code>['/home/foobaruser/sources/minimal-django-example', + '/home/foobaruser/.pyenv/versions/3.12.5/lib/python312.zip', + '/home/foobaruser/.pyenv/versions/3.12.5/lib/python3.12', + '/home/foobaruser/.pyenv/versions/3.12.5/lib/python3.12/lib-dynload', + '/home/foobaruser/.pyenv/versions/testsca/lib/python3.12/site-packages']</code></pre></td> + </tr> + <tr> + <th scope="row">Server time:</th> + <td>Mon, 20 Jan 2025 04:38:00 -0600</td> + </tr> + </table> +</header> + +<main id="info"> + + + + + <div id="traceback"> + <h2>Traceback <span class="commands"><a href="#" onclick="return switchPastebinFriendly(this);"> + Switch to copy-and-paste view</a></span> + </h2> + <div id="browserTraceback"> + <ul class="traceback"> + + + <li class="frame django"> + + <code class="fname">/home/foobaruser/.pyenv/versions/testsca/lib/python3.12/site-packages/django/core/handlers/exception.py</code>, line 55, in inner + + + + <div class="context" id="c127129509161024"> + + <ol start="48" class="pre-context" id="pre127129509161024"> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre></pre></li> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre> return inner</pre></li> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre> else:</pre></li> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre></pre></li> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre> @wraps(get_response)</pre></li> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre> def inner(request):</pre></li> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre> try:</pre></li> + + </ol> + + <ol start="55" class="context-line"> + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre> response = get_response(request) + ^^^^^^^^^^^^^^^^^^^^^</pre> <span>…</span></li> + </ol> + + <ol start='56' class="post-context" id="post127129509161024"> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre> except Exception as exc:</pre></li> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre> response = response_for_exception(request, exc)</pre></li> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre> return response</pre></li> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre></pre></li> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre> return inner</pre></li> + + <li onclick="toggle('pre127129509161024', 'post127129509161024')"><pre></pre></li> + + </ol> + + </div> + + + + + <details> + <summary class="commands">Local vars</summary> + + <table class="vars" id="v127129509161024"> + <thead> + <tr> + <th scope="col">Variable</th> + <th scope="col">Value</th> + </tr> + </thead> + <tbody> + + <tr> + <td>exc</td> + <td class="code"><pre>IndexError()</pre></td> + </tr> + + <tr> + <td>get_response</td> + <td class="code"><pre><bound method BaseHandler._get_response of <django.core.handlers.wsgi.WSGIHandler object at 0x739fa54fbf20>></pre></td> + </tr> + + <tr> + <td>request</td> + <td class="code"><pre><WSGIRequest: GET '/'></pre></td> + </tr> + + </tbody> + </table> + </details> + + </li> + + + <li class="frame django"> + + <code class="fname">/home/foobaruser/.pyenv/versions/testsca/lib/python3.12/site-packages/django/core/handlers/base.py</code>, line 197, in _get_response + + + + <div class="context" id="c127129509160960"> + + <ol start="190" class="pre-context" id="pre127129509160960"> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre></pre></li> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre> if response is None:</pre></li> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre> wrapped_callback = self.make_view_atomic(callback)</pre></li> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre> # If it is an asynchronous view, run it in a subthread.</pre></li> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre> if iscoroutinefunction(wrapped_callback):</pre></li> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre> wrapped_callback = async_to_sync(wrapped_callback)</pre></li> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre> try:</pre></li> + + </ol> + + <ol start="197" class="context-line"> + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre> response = wrapped_callback(request, *callback_args, **callback_kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</pre> <span>…</span></li> + </ol> + + <ol start='198' class="post-context" id="post127129509160960"> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre> except Exception as e:</pre></li> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre> response = self.process_exception_by_middleware(e, request)</pre></li> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre> if response is None:</pre></li> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre> raise</pre></li> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre></pre></li> + + <li onclick="toggle('pre127129509160960', 'post127129509160960')"><pre> # Complain if the view returned None (a common error).</pre></li> + + </ol> + + </div> + + + + + <details> + <summary class="commands">Local vars</summary> + + <table class="vars" id="v127129509160960"> + <thead> + <tr> + <th scope="col">Variable</th> + <th scope="col">Value</th> + </tr> + </thead> + <tbody> + + <tr> + <td>callback</td> + <td class="code"><pre><function index_view at 0x739fa76b4d60></pre></td> + </tr> + + <tr> + <td>callback_args</td> + <td class="code"><pre>()</pre></td> + </tr> + + <tr> + <td>callback_kwargs</td> + <td class="code"><pre>{}</pre></td> + </tr> + + <tr> + <td>request</td> + <td class="code"><pre><WSGIRequest: GET '/'></pre></td> + </tr> + + <tr> + <td>response</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>self</td> + <td class="code"><pre><django.core.handlers.wsgi.WSGIHandler object at 0x739fa54fbf20></pre></td> + </tr> + + <tr> + <td>wrapped_callback</td> + <td class="code"><pre><function index_view at 0x739fa76b4d60></pre></td> + </tr> + + </tbody> + </table> + </details> + + </li> + + + <li class="frame user"> + + <code class="fname">/home/foobaruser/sources/minimal-django-example/app.py</code>, line 20, in index_view + + + + <div class="context" id="c127129509160576"> + + <ol start="13" class="pre-context" id="pre127129509160576"> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre> "DIRS": ["templates"],</pre></li> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre> },</pre></li> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre> ],</pre></li> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre>)</pre></li> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre></pre></li> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre></pre></li> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre>def index_view(request):</pre></li> + + </ol> + + <ol start="20" class="context-line"> + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre> raise IndexError() + ^^^^^^^^^^^^^^^^^^</pre> <span>…</span></li> + </ol> + + <ol start='21' class="post-context" id="post127129509160576"> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre> return HttpResponse("<h1>Hello World From Django!</h1>")</pre></li> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre></pre></li> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre></pre></li> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre>def hello_view(request, name):</pre></li> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre> return render(request, "template.html", {"name": name})</pre></li> + + <li onclick="toggle('pre127129509160576', 'post127129509160576')"><pre></pre></li> + + </ol> + + </div> + + + + + <details> + <summary class="commands">Local vars</summary> + + <table class="vars" id="v127129509160576"> + <thead> + <tr> + <th scope="col">Variable</th> + <th scope="col">Value</th> + </tr> + </thead> + <tbody> + + <tr> + <td>request</td> + <td class="code"><pre><WSGIRequest: GET '/'></pre></td> + </tr> + + </tbody> + </table> + </details> + + </li> + + </ul> + </div> + + <form action="https://dpaste.com/" name="pasteform" id="pasteform" method="post"> + <div id="pastebinTraceback" class="pastebin"> + <input type="hidden" name="language" value="PythonConsole"> + <input type="hidden" name="title" + value="IndexError at /"> + <input type="hidden" name="source" value="Django Dpaste Agent"> + <input type="hidden" name="poster" value="Django"> + <textarea name="content" id="traceback_area" cols="140" rows="25"> +Environment: + + +Request Method: GET +Request URL: http://localhost:8000/ + +Django Version: 5.1.5 +Python Version: 3.12.5 +Installed Applications: +[] +Installed Middleware: +[] + + + +Traceback (most recent call last): + File "/home/foobaruser/.pyenv/versions/testsca/lib/python3.12/site-packages/django/core/handlers/exception.py", line 55, in inner + response = get_response(request) + ^^^^^^^^^^^^^^^^^^^^^ + File "/home/foobaruser/.pyenv/versions/testsca/lib/python3.12/site-packages/django/core/handlers/base.py", line 197, in _get_response + response = wrapped_callback(request, *callback_args, **callback_kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/foobaruser/sources/minimal-django-example/app.py", line 20, in index_view + raise IndexError() + ^^^^^^^^^^^^^^^^^^ + +Exception Type: IndexError at / +Exception Value: +</textarea> + <br><br> + <input type="submit" value="Share this traceback on a public website"> + </div> + </form> + + </div> + + + <div id="requestinfo"> + <h2>Request information</h2> + + + + <h3 id="user-info">USER</h3> + <p>[unable to retrieve the current user]</p> + + + <h3 id="get-info">GET</h3> + + <p>No GET data</p> + + + <h3 id="post-info">POST</h3> + + <p>No POST data</p> + + + <h3 id="files-info">FILES</h3> + + <p>No FILES data</p> + + + <h3 id="cookie-info">COOKIES</h3> + + <table class="req"> + <thead> + <tr> + <th scope="col">Variable</th> + <th scope="col">Value</th> + </tr> + </thead> + <tbody> + + <tr> + <td>csrftoken</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + </tbody> + </table> + + + <h3 id="meta-info">META</h3> + <table class="req"> + <thead> + <tr> + <th scope="col">Variable</th> + <th scope="col">Value</th> + </tr> + </thead> + <tbody> + + <tr> + <td>AWS_ASSUME_ROLE_TTL</td> + <td class="code"><pre>'1h'</pre></td> + </tr> + + <tr> + <td>AWS_SESSION_TTL</td> + <td class="code"><pre>'24h'</pre></td> + </tr> + + <tr> + <td>AWS_VAULT_BACKEND</td> + <td class="code"><pre>'secret-service'</pre></td> + </tr> + + <tr> + <td>AWS_VAULT_KEYCHAIN_NAME</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + <tr> + <td>COLORTERM</td> + <td class="code"><pre>'truecolor'</pre></td> + </tr> + + <tr> + <td>CONTENT_LENGTH</td> + <td class="code"><pre>''</pre></td> + </tr> + + <tr> + <td>CONTENT_TYPE</td> + <td class="code"><pre>'text/plain'</pre></td> + </tr> + + <tr> + <td>DBUS_SESSION_BUS_ADDRESS</td> + <td class="code"><pre>'unix:path=/run/user/1000/bus,guid=c3ac961ccc4c263877782e00678e0c9e'</pre></td> + </tr> + + <tr> + <td>DBUS_STARTER_ADDRESS</td> + <td class="code"><pre>'unix:path=/run/user/1000/bus,guid=c3ac961ccc4c263877782e00678e0c9e'</pre></td> + </tr> + + <tr> + <td>DBUS_STARTER_BUS_TYPE</td> + <td class="code"><pre>'session'</pre></td> + </tr> + + <tr> + <td>DESKTOP_SESSION</td> + <td class="code"><pre>'ubuntu'</pre></td> + </tr> + + <tr> + <td>DISPLAY</td> + <td class="code"><pre>':0'</pre></td> + </tr> + + <tr> + <td>EDITOR</td> + <td class="code"><pre>'vim'</pre></td> + </tr> + + <tr> + <td>EMAIL</td> + <td class="code"><pre>'Juanjo Alvarez <juanjo@juanjoalvarez.net>'</pre></td> + </tr> + + <tr> + <td>GATEWAY_INTERFACE</td> + <td class="code"><pre>'CGI/1.1'</pre></td> + </tr> + + <tr> + <td>GDMSESSION</td> + <td class="code"><pre>'ubuntu'</pre></td> + </tr> + + <tr> + <td>GITLAB_TOKEN</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + <tr> + <td>GNOME_DESKTOP_SESSION_ID</td> + <td class="code"><pre>'this-is-deprecated'</pre></td> + </tr> + + <tr> + <td>GNOME_SETUP_DISPLAY</td> + <td class="code"><pre>':1'</pre></td> + </tr> + + <tr> + <td>GNOME_SHELL_SESSION_MODE</td> + <td class="code"><pre>'ubuntu'</pre></td> + </tr> + + <tr> + <td>GOROOT</td> + <td class="code"><pre>'/home/foobaruser/go'</pre></td> + </tr> + + <tr> + <td>GTK_MODULES</td> + <td class="code"><pre>'gail:atk-bridge'</pre></td> + </tr> + + <tr> + <td>HDIV_PHP_EXTENSION_PATH</td> + <td class="code"><pre>'/home/foobaruser/php/hdiv-php-extension/'</pre></td> + </tr> + + <tr> + <td>HOME</td> + <td class="code"><pre>'/home/foobaruser'</pre></td> + </tr> + + <tr> + <td>HTTP_ACCEPT</td> + <td class="code"><pre>'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7'</pre></td> + </tr> + + <tr> + <td>HTTP_ACCEPT_ENCODING</td> + <td class="code"><pre>'gzip, deflate, br, zstd'</pre></td> + </tr> + + <tr> + <td>HTTP_ACCEPT_LANGUAGE</td> + <td class="code"><pre>'en-US,en;q=0.9'</pre></td> + </tr> + + <tr> + <td>HTTP_CACHE_CONTROL</td> + <td class="code"><pre>'max-age=0'</pre></td> + </tr> + + <tr> + <td>HTTP_CONNECTION</td> + <td class="code"><pre>'keep-alive'</pre></td> + </tr> + + <tr> + <td>HTTP_COOKIE</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + <tr> + <td>HTTP_DNT</td> + <td class="code"><pre>'1'</pre></td> + </tr> + + <tr> + <td>HTTP_HOST</td> + <td class="code"><pre>'localhost:8000'</pre></td> + </tr> + + <tr> + <td>HTTP_SEC_CH_UA</td> + <td class="code"><pre>'"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"'</pre></td> + </tr> + + <tr> + <td>HTTP_SEC_CH_UA_MOBILE</td> + <td class="code"><pre>'?0'</pre></td> + </tr> + + <tr> + <td>HTTP_SEC_CH_UA_PLATFORM</td> + <td class="code"><pre>'"Linux"'</pre></td> + </tr> + + <tr> + <td>HTTP_SEC_FETCH_DEST</td> + <td class="code"><pre>'document'</pre></td> + </tr> + + <tr> + <td>HTTP_SEC_FETCH_MODE</td> + <td class="code"><pre>'navigate'</pre></td> + </tr> + + <tr> + <td>HTTP_SEC_FETCH_SITE</td> + <td class="code"><pre>'none'</pre></td> + </tr> + + <tr> + <td>HTTP_SEC_FETCH_USER</td> + <td class="code"><pre>'?1'</pre></td> + </tr> + + <tr> + <td>HTTP_UPGRADE_INSECURE_REQUESTS</td> + <td class="code"><pre>'1'</pre></td> + </tr> + + <tr> + <td>HTTP_USER_AGENT</td> + <td class="code"><pre>('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/131.0.0.0 Safari/537.36')</pre></td> + </tr> + + <tr> + <td>IM_CONFIG_CHECK_ENV</td> + <td class="code"><pre>'1'</pre></td> + </tr> + + <tr> + <td>IM_CONFIG_PHASE</td> + <td class="code"><pre>'1'</pre></td> + </tr> + + <tr> + <td>INVOCATION_ID</td> + <td class="code"><pre>'161a1822b4c24d129b1f0e3ef745a30f'</pre></td> + </tr> + + <tr> + <td>JOURNAL_STREAM</td> + <td class="code"><pre>'8:61973'</pre></td> + </tr> + + <tr> + <td>LANG</td> + <td class="code"><pre>'en_US.UTF-8'</pre></td> + </tr> + + <tr> + <td>LC_ADDRESS</td> + <td class="code"><pre>'es_ES.UTF-8'</pre></td> + </tr> + + <tr> + <td>LC_IDENTIFICATION</td> + <td class="code"><pre>'es_ES.UTF-8'</pre></td> + </tr> + + <tr> + <td>LC_MEASUREMENT</td> + <td class="code"><pre>'es_ES.UTF-8'</pre></td> + </tr> + + <tr> + <td>LC_MONETARY</td> + <td class="code"><pre>'es_ES.UTF-8'</pre></td> + </tr> + + <tr> + <td>LC_NAME</td> + <td class="code"><pre>'es_ES.UTF-8'</pre></td> + </tr> + + <tr> + <td>LC_NUMERIC</td> + <td class="code"><pre>'es_ES.UTF-8'</pre></td> + </tr> + + <tr> + <td>LC_PAPER</td> + <td class="code"><pre>'es_ES.UTF-8'</pre></td> + </tr> + + <tr> + <td>LC_TELEPHONE</td> + <td class="code"><pre>'es_ES.UTF-8'</pre></td> + </tr> + + <tr> + <td>LC_TIME</td> + <td class="code"><pre>'es_ES.UTF-8'</pre></td> + </tr> + + <tr> + <td>LOGNAME</td> + <td class="code"><pre>'foobaruser'</pre></td> + </tr> + + <tr> + <td>LS_COLORS</td> + <td class="code"><pre>''</pre></td> + </tr> + + <tr> + <td>LS_OPTIONS</td> + <td class="code"><pre>'-N --color=auto -h'</pre></td> + </tr> + + <tr> + <td>MANAGERPID</td> + <td class="code"><pre>'4414'</pre></td> + </tr> + + <tr> + <td>MANPATH</td> + <td class="code"><pre>':/opt/puppetlabs/puppet/share/man'</pre></td> + </tr> + + <tr> + <td>OMF_CONFIG</td> + <td class="code"><pre>'/home/foobaruser/.config/omf'</pre></td> + </tr> + + <tr> + <td>OMF_PATH</td> + <td class="code"><pre>'/home/foobaruser/.local/share/omf'</pre></td> + </tr> + + <tr> + <td>PATH</td> + <td class="code"><pre>'/home/foobaruser/.pyenv/versions/testsca/bin:/home/foobaruser/.pyenv/libexec:/home/foobaruser/.pyenv/plugins/python-build/bin:/home/foobaruser/.pyenv/plugins/pyenv-virtualenv/bin:/home/foobaruser/.pyenv/plugins/pyenv-update/bin:/home/foobaruser/.pyenv/plugins/pyenv-installer/bin:/home/foobaruser/.pyenv/plugins/pyenv-doctor/bin:/home/foobaruser/.pyenv/plugins/pyenv-virtualenv/shims:/home/foobaruser/.pyenv/shims:/home/foobaruser/.pyenv/bin:/home/foobaruser/pyenv/bin:/home/foobaruser/.local/bin:/home/foobaruser/sources/graalvm-ce-java11-20.1.0/bin:/home/foobaruser/.fzf/bin:/home/foobaruser/.cargo/bin:/home/foobaruser/.krew/bin:/home/foobaruser/.tfenv/bin:/home/foobaruser/dd/devtools/bin:/usr/local/bin:/home/foobaruser/.local/bin:/home/foobaruser/.krew/bin:/home/foobaruser/.pyenv/bin:/home/foobaruser/.tfenv/bin:/home/foobaruser/dd/devtools/bin:/usr/local/bin:/home/foobaruser/.yarn/bin:/home/foobaruser/.config/yarn/global/node_modules/.bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin:/opt/puppetlabs/bin:/home/foobaruser/bin:/home/foobaruser/sync/work/d/dmd2:/home/foobaruser/sources/nim/bin:/home/foobaruser/.nimble/bin'</pre></td> + </tr> + + <tr> + <td>PATH_INFO</td> + <td class="code"><pre>'/'</pre></td> + </tr> + + <tr> + <td>PWD</td> + <td class="code"><pre>'/home/foobaruser/sources/minimal-django-example'</pre></td> + </tr> + + <tr> + <td>PYENV_DIR</td> + <td class="code"><pre>'/home/foobaruser/sources/minimal-django-example'</pre></td> + </tr> + + <tr> + <td>PYENV_HOOK_PATH</td> + <td class="code"><pre>'/home/foobaruser/.pyenv/pyenv.d:/usr/etc/pyenv.d:/usr/local/etc/pyenv.d:/etc/pyenv.d:/usr/lib/pyenv/hooks:/home/foobaruser/.pyenv/plugins/pyenv-virtualenv/etc/pyenv.d:/home/foobaruser/.pyenv/plugins/pyenv-which-ext/etc/pyenv.d'</pre></td> + </tr> + + <tr> + <td>PYENV_ROOT</td> + <td class="code"><pre>'/home/foobaruser/.pyenv'</pre></td> + </tr> + + <tr> + <td>PYENV_SHELL</td> + <td class="code"><pre>'fish'</pre></td> + </tr> + + <tr> + <td>PYENV_VERSION</td> + <td class="code"><pre>'testsca'</pre></td> + </tr> + + <tr> + <td>PYENV_VIRTUALENV_INIT</td> + <td class="code"><pre>'1'</pre></td> + </tr> + + <tr> + <td>PYENV_VIRTUAL_ENV</td> + <td class="code"><pre>'/home/foobaruser/.pyenv/versions/3.12.5/envs/testsca'</pre></td> + </tr> + + <tr> + <td>QT_ACCESSIBILITY</td> + <td class="code"><pre>'1'</pre></td> + </tr> + + <tr> + <td>QT_IM_MODULE</td> + <td class="code"><pre>'ibus'</pre></td> + </tr> + + <tr> + <td>QUERY_STRING</td> + <td class="code"><pre>''</pre></td> + </tr> + + <tr> + <td>REMOTE_ADDR</td> + <td class="code"><pre>'127.0.0.1'</pre></td> + </tr> + + <tr> + <td>REMOTE_HOST</td> + <td class="code"><pre>''</pre></td> + </tr> + + <tr> + <td>REQUEST_METHOD</td> + <td class="code"><pre>'GET'</pre></td> + </tr> + + <tr> + <td>RSYNC_PASSWORD</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + <tr> + <td>RUN_MAIN</td> + <td class="code"><pre>'true'</pre></td> + </tr> + + <tr> + <td>SCRIPT_NAME</td> + <td class="code"><pre>''</pre></td> + </tr> + + <tr> + <td>SERVER_NAME</td> + <td class="code"><pre>'localhost'</pre></td> + </tr> + + <tr> + <td>SERVER_PORT</td> + <td class="code"><pre>'8000'</pre></td> + </tr> + + <tr> + <td>SERVER_PROTOCOL</td> + <td class="code"><pre>'HTTP/1.1'</pre></td> + </tr> + + <tr> + <td>SERVER_SOFTWARE</td> + <td class="code"><pre>'WSGIServer/0.2'</pre></td> + </tr> + + <tr> + <td>SESSION_MANAGER</td> + <td class="code"><pre>'local/foobaruser-ThinkPad-P15v-Gen-2i:@/tmp/.ICE-unix/5619,unix/foobaruser-ThinkPad-P15v-Gen-2i:/tmp/.ICE-unix/5619'</pre></td> + </tr> + + <tr> + <td>SHELL</td> + <td class="code"><pre>'/bin/bash'</pre></td> + </tr> + + <tr> + <td>SHLVL</td> + <td class="code"><pre>'1'</pre></td> + </tr> + + <tr> + <td>SSH_AGENT_LAUNCHER</td> + <td class="code"><pre>'gnome-keyring'</pre></td> + </tr> + + <tr> + <td>SSH_AUTH_SOCK</td> + <td class="code"><pre>'/run/user/1000/keyring/ssh'</pre></td> + </tr> + + <tr> + <td>SYSTEMD_EXEC_PID</td> + <td class="code"><pre>'5619'</pre></td> + </tr> + + <tr> + <td>TERM</td> + <td class="code"><pre>'xterm-256color'</pre></td> + </tr> + + <tr> + <td>TILIX_ID</td> + <td class="code"><pre>'62221076-149e-426c-bba3-cf14d3a9099a'</pre></td> + </tr> + + <tr> + <td>USER</td> + <td class="code"><pre>'foobaruser'</pre></td> + </tr> + + <tr> + <td>USERNAME</td> + <td class="code"><pre>'foobaruser'</pre></td> + </tr> + + <tr> + <td>VIRTUAL_ENV</td> + <td class="code"><pre>'/home/foobaruser/.pyenv/versions/3.12.5/envs/testsca'</pre></td> + </tr> + + <tr> + <td>VTE_VERSION</td> + <td class="code"><pre>'6800'</pre></td> + </tr> + + <tr> + <td>WAYLAND_DISPLAY</td> + <td class="code"><pre>'wayland-0'</pre></td> + </tr> + + <tr> + <td>XAUTHORITY</td> + <td class="code"><pre>'/run/user/1000/.mutter-Xwaylandauth.50H8Z2'</pre></td> + </tr> + + <tr> + <td>XDG_CONFIG_DIRS</td> + <td class="code"><pre>'/etc/xdg/xdg-ubuntu:/etc/xdg'</pre></td> + </tr> + + <tr> + <td>XDG_CURRENT_DESKTOP</td> + <td class="code"><pre>'ubuntu:GNOME'</pre></td> + </tr> + + <tr> + <td>XDG_DATA_DIRS</td> + <td class="code"><pre>'/usr/share/ubuntu:/home/foobaruser/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop'</pre></td> + </tr> + + <tr> + <td>XDG_MENU_PREFIX</td> + <td class="code"><pre>'gnome-'</pre></td> + </tr> + + <tr> + <td>XDG_RUNTIME_DIR</td> + <td class="code"><pre>'/run/user/1000'</pre></td> + </tr> + + <tr> + <td>XDG_SESSION_CLASS</td> + <td class="code"><pre>'user'</pre></td> + </tr> + + <tr> + <td>XDG_SESSION_DESKTOP</td> + <td class="code"><pre>'ubuntu'</pre></td> + </tr> + + <tr> + <td>XDG_SESSION_TYPE</td> + <td class="code"><pre>'wayland'</pre></td> + </tr> + + <tr> + <td>XMODIFIERS</td> + <td class="code"><pre>'@im=ibus'</pre></td> + </tr> + + <tr> + <td>wsgi.errors</td> + <td class="code"><pre><_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'></pre></td> + </tr> + + <tr> + <td>wsgi.file_wrapper</td> + <td class="code"><pre><class 'wsgiref.util.FileWrapper'></pre></td> + </tr> + + <tr> + <td>wsgi.input</td> + <td class="code"><pre><django.core.handlers.wsgi.LimitedStream object at 0x739fa53ad4b0></pre></td> + </tr> + + <tr> + <td>wsgi.multiprocess</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>wsgi.multithread</td> + <td class="code"><pre>True</pre></td> + </tr> + + <tr> + <td>wsgi.run_once</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>wsgi.url_scheme</td> + <td class="code"><pre>'http'</pre></td> + </tr> + + <tr> + <td>wsgi.version</td> + <td class="code"><pre>(1, 0)</pre></td> + </tr> + + </tbody> + </table> + + + <h3 id="settings-info">Settings</h3> + <h4>Using settings module <code></code></h4> + <table class="req"> + <thead> + <tr> + <th scope="col">Setting</th> + <th scope="col">Value</th> + </tr> + </thead> + <tbody> + + <tr> + <td>ABSOLUTE_URL_OVERRIDES</td> + <td class="code"><pre>{}</pre></td> + </tr> + + <tr> + <td>ADMINS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>ALLOWED_HOSTS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>APPEND_SLASH</td> + <td class="code"><pre>True</pre></td> + </tr> + + <tr> + <td>AUTHENTICATION_BACKENDS</td> + <td class="code"><pre>['django.contrib.auth.backends.ModelBackend']</pre></td> + </tr> + + <tr> + <td>AUTH_PASSWORD_VALIDATORS</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + <tr> + <td>AUTH_USER_MODEL</td> + <td class="code"><pre>'auth.User'</pre></td> + </tr> + + <tr> + <td>CACHES</td> + <td class="code"><pre>{'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}}</pre></td> + </tr> + + <tr> + <td>CACHE_MIDDLEWARE_ALIAS</td> + <td class="code"><pre>'default'</pre></td> + </tr> + + <tr> + <td>CACHE_MIDDLEWARE_KEY_PREFIX</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + <tr> + <td>CACHE_MIDDLEWARE_SECONDS</td> + <td class="code"><pre>600</pre></td> + </tr> + + <tr> + <td>CSRF_COOKIE_AGE</td> + <td class="code"><pre>31449600</pre></td> + </tr> + + <tr> + <td>CSRF_COOKIE_DOMAIN</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>CSRF_COOKIE_HTTPONLY</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>CSRF_COOKIE_NAME</td> + <td class="code"><pre>'csrftoken'</pre></td> + </tr> + + <tr> + <td>CSRF_COOKIE_PATH</td> + <td class="code"><pre>'/'</pre></td> + </tr> + + <tr> + <td>CSRF_COOKIE_SAMESITE</td> + <td class="code"><pre>'Lax'</pre></td> + </tr> + + <tr> + <td>CSRF_COOKIE_SECURE</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>CSRF_FAILURE_VIEW</td> + <td class="code"><pre>'django.views.csrf.csrf_failure'</pre></td> + </tr> + + <tr> + <td>CSRF_HEADER_NAME</td> + <td class="code"><pre>'HTTP_X_CSRFTOKEN'</pre></td> + </tr> + + <tr> + <td>CSRF_TRUSTED_ORIGINS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>CSRF_USE_SESSIONS</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>DATABASES</td> + <td class="code"><pre>{'default': {'ATOMIC_REQUESTS': False, + 'AUTOCOMMIT': True, + 'CONN_HEALTH_CHECKS': False, + 'CONN_MAX_AGE': 0, + 'ENGINE': 'django.db.backends.dummy', + 'HOST': '', + 'NAME': '', + 'OPTIONS': {}, + 'PASSWORD': '********************', + 'PORT': '', + 'TEST': {'CHARSET': None, + 'COLLATION': None, + 'MIGRATE': True, + 'MIRROR': None, + 'NAME': None}, + 'TIME_ZONE': None, + 'USER': ''}}</pre></td> + </tr> + + <tr> + <td>DATABASE_ROUTERS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>DATA_UPLOAD_MAX_MEMORY_SIZE</td> + <td class="code"><pre>2621440</pre></td> + </tr> + + <tr> + <td>DATA_UPLOAD_MAX_NUMBER_FIELDS</td> + <td class="code"><pre>1000</pre></td> + </tr> + + <tr> + <td>DATA_UPLOAD_MAX_NUMBER_FILES</td> + <td class="code"><pre>100</pre></td> + </tr> + + <tr> + <td>DATETIME_FORMAT</td> + <td class="code"><pre>'N j, Y, P'</pre></td> + </tr> + + <tr> + <td>DATETIME_INPUT_FORMATS</td> + <td class="code"><pre>['%Y-%m-%d %H:%M:%S', + '%Y-%m-%d %H:%M:%S.%f', + '%Y-%m-%d %H:%M', + '%m/%d/%Y %H:%M:%S', + '%m/%d/%Y %H:%M:%S.%f', + '%m/%d/%Y %H:%M', + '%m/%d/%y %H:%M:%S', + '%m/%d/%y %H:%M:%S.%f', + '%m/%d/%y %H:%M']</pre></td> + </tr> + + <tr> + <td>DATE_FORMAT</td> + <td class="code"><pre>'N j, Y'</pre></td> + </tr> + + <tr> + <td>DATE_INPUT_FORMATS</td> + <td class="code"><pre>['%Y-%m-%d', + '%m/%d/%Y', + '%m/%d/%y', + '%b %d %Y', + '%b %d, %Y', + '%d %b %Y', + '%d %b, %Y', + '%B %d %Y', + '%B %d, %Y', + '%d %B %Y', + '%d %B, %Y']</pre></td> + </tr> + + <tr> + <td>DEBUG</td> + <td class="code"><pre>True</pre></td> + </tr> + + <tr> + <td>DEBUG_PROPAGATE_EXCEPTIONS</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>DECIMAL_SEPARATOR</td> + <td class="code"><pre>'.'</pre></td> + </tr> + + <tr> + <td>DEFAULT_AUTO_FIELD</td> + <td class="code"><pre>'django.db.models.AutoField'</pre></td> + </tr> + + <tr> + <td>DEFAULT_CHARSET</td> + <td class="code"><pre>'utf-8'</pre></td> + </tr> + + <tr> + <td>DEFAULT_EXCEPTION_REPORTER</td> + <td class="code"><pre>'django.views.debug.ExceptionReporter'</pre></td> + </tr> + + <tr> + <td>DEFAULT_EXCEPTION_REPORTER_FILTER</td> + <td class="code"><pre>'django.views.debug.SafeExceptionReporterFilter'</pre></td> + </tr> + + <tr> + <td>DEFAULT_FROM_EMAIL</td> + <td class="code"><pre>'webmaster@localhost'</pre></td> + </tr> + + <tr> + <td>DEFAULT_INDEX_TABLESPACE</td> + <td class="code"><pre>''</pre></td> + </tr> + + <tr> + <td>DEFAULT_TABLESPACE</td> + <td class="code"><pre>''</pre></td> + </tr> + + <tr> + <td>DISALLOWED_USER_AGENTS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>EMAIL_BACKEND</td> + <td class="code"><pre>'django.core.mail.backends.smtp.EmailBackend'</pre></td> + </tr> + + <tr> + <td>EMAIL_HOST</td> + <td class="code"><pre>'localhost'</pre></td> + </tr> + + <tr> + <td>EMAIL_HOST_PASSWORD</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + <tr> + <td>EMAIL_HOST_USER</td> + <td class="code"><pre>''</pre></td> + </tr> + + <tr> + <td>EMAIL_PORT</td> + <td class="code"><pre>25</pre></td> + </tr> + + <tr> + <td>EMAIL_SSL_CERTFILE</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>EMAIL_SSL_KEYFILE</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + <tr> + <td>EMAIL_SUBJECT_PREFIX</td> + <td class="code"><pre>'[Django] '</pre></td> + </tr> + + <tr> + <td>EMAIL_TIMEOUT</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>EMAIL_USE_LOCALTIME</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>EMAIL_USE_SSL</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>EMAIL_USE_TLS</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>FILE_UPLOAD_DIRECTORY_PERMISSIONS</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>FILE_UPLOAD_HANDLERS</td> + <td class="code"><pre>['django.core.files.uploadhandler.MemoryFileUploadHandler', + 'django.core.files.uploadhandler.TemporaryFileUploadHandler']</pre></td> + </tr> + + <tr> + <td>FILE_UPLOAD_MAX_MEMORY_SIZE</td> + <td class="code"><pre>2621440</pre></td> + </tr> + + <tr> + <td>FILE_UPLOAD_PERMISSIONS</td> + <td class="code"><pre>420</pre></td> + </tr> + + <tr> + <td>FILE_UPLOAD_TEMP_DIR</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>FIRST_DAY_OF_WEEK</td> + <td class="code"><pre>0</pre></td> + </tr> + + <tr> + <td>FIXTURE_DIRS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>FORCE_SCRIPT_NAME</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>FORMAT_MODULE_PATH</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>FORMS_URLFIELD_ASSUME_HTTPS</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>FORM_RENDERER</td> + <td class="code"><pre>'django.forms.renderers.DjangoTemplates'</pre></td> + </tr> + + <tr> + <td>IGNORABLE_404_URLS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>INSTALLED_APPS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>INTERNAL_IPS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>LANGUAGES</td> + <td class="code"><pre>[('af', 'Afrikaans'), + ('ar', 'Arabic'), + ('ar-dz', 'Algerian Arabic'), + ('ast', 'Asturian'), + ('az', 'Azerbaijani'), + ('bg', 'Bulgarian'), + ('be', 'Belarusian'), + ('bn', 'Bengali'), + ('br', 'Breton'), + ('bs', 'Bosnian'), + ('ca', 'Catalan'), + ('ckb', 'Central Kurdish (Sorani)'), + ('cs', 'Czech'), + ('cy', 'Welsh'), + ('da', 'Danish'), + ('de', 'German'), + ('dsb', 'Lower Sorbian'), + ('el', 'Greek'), + ('en', 'English'), + ('en-au', 'Australian English'), + ('en-gb', 'British English'), + ('eo', 'Esperanto'), + ('es', 'Spanish'), + ('es-ar', 'Argentinian Spanish'), + ('es-co', 'Colombian Spanish'), + ('es-mx', 'Mexican Spanish'), + ('es-ni', 'Nicaraguan Spanish'), + ('es-ve', 'Venezuelan Spanish'), + ('et', 'Estonian'), + ('eu', 'Basque'), + ('fa', 'Persian'), + ('fi', 'Finnish'), + ('fr', 'French'), + ('fy', 'Frisian'), + ('ga', 'Irish'), + ('gd', 'Scottish Gaelic'), + ('gl', 'Galician'), + ('he', 'Hebrew'), + ('hi', 'Hindi'), + ('hr', 'Croatian'), + ('hsb', 'Upper Sorbian'), + ('hu', 'Hungarian'), + ('hy', 'Armenian'), + ('ia', 'Interlingua'), + ('id', 'Indonesian'), + ('ig', 'Igbo'), + ('io', 'Ido'), + ('is', 'Icelandic'), + ('it', 'Italian'), + ('ja', 'Japanese'), + ('ka', 'Georgian'), + ('kab', 'Kabyle'), + ('kk', 'Kazakh'), + ('km', 'Khmer'), + ('kn', 'Kannada'), + ('ko', 'Korean'), + ('ky', 'Kyrgyz'), + ('lb', 'Luxembourgish'), + ('lt', 'Lithuanian'), + ('lv', 'Latvian'), + ('mk', 'Macedonian'), + ('ml', 'Malayalam'), + ('mn', 'Mongolian'), + ('mr', 'Marathi'), + ('ms', 'Malay'), + ('my', 'Burmese'), + ('nb', 'Norwegian Bokmål'), + ('ne', 'Nepali'), + ('nl', 'Dutch'), + ('nn', 'Norwegian Nynorsk'), + ('os', 'Ossetic'), + ('pa', 'Punjabi'), + ('pl', 'Polish'), + ('pt', 'Portuguese'), + ('pt-br', 'Brazilian Portuguese'), + ('ro', 'Romanian'), + ('ru', 'Russian'), + ('sk', 'Slovak'), + ('sl', 'Slovenian'), + ('sq', 'Albanian'), + ('sr', 'Serbian'), + ('sr-latn', 'Serbian Latin'), + ('sv', 'Swedish'), + ('sw', 'Swahili'), + ('ta', 'Tamil'), + ('te', 'Telugu'), + ('tg', 'Tajik'), + ('th', 'Thai'), + ('tk', 'Turkmen'), + ('tr', 'Turkish'), + ('tt', 'Tatar'), + ('udm', 'Udmurt'), + ('ug', 'Uyghur'), + ('uk', 'Ukrainian'), + ('ur', 'Urdu'), + ('uz', 'Uzbek'), + ('vi', 'Vietnamese'), + ('zh-hans', 'Simplified Chinese'), + ('zh-hant', 'Traditional Chinese')]</pre></td> + </tr> + + <tr> + <td>LANGUAGES_BIDI</td> + <td class="code"><pre>['he', 'ar', 'ar-dz', 'ckb', 'fa', 'ug', 'ur']</pre></td> + </tr> + + <tr> + <td>LANGUAGE_CODE</td> + <td class="code"><pre>'en-us'</pre></td> + </tr> + + <tr> + <td>LANGUAGE_COOKIE_AGE</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>LANGUAGE_COOKIE_DOMAIN</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>LANGUAGE_COOKIE_HTTPONLY</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>LANGUAGE_COOKIE_NAME</td> + <td class="code"><pre>'django_language'</pre></td> + </tr> + + <tr> + <td>LANGUAGE_COOKIE_PATH</td> + <td class="code"><pre>'/'</pre></td> + </tr> + + <tr> + <td>LANGUAGE_COOKIE_SAMESITE</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>LANGUAGE_COOKIE_SECURE</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>LOCALE_PATHS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>LOGGING</td> + <td class="code"><pre>{}</pre></td> + </tr> + + <tr> + <td>LOGGING_CONFIG</td> + <td class="code"><pre>'logging.config.dictConfig'</pre></td> + </tr> + + <tr> + <td>LOGIN_REDIRECT_URL</td> + <td class="code"><pre>'/accounts/profile/'</pre></td> + </tr> + + <tr> + <td>LOGIN_URL</td> + <td class="code"><pre>'/accounts/login/'</pre></td> + </tr> + + <tr> + <td>LOGOUT_REDIRECT_URL</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>MANAGERS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>MEDIA_ROOT</td> + <td class="code"><pre>''</pre></td> + </tr> + + <tr> + <td>MEDIA_URL</td> + <td class="code"><pre>'/'</pre></td> + </tr> + + <tr> + <td>MESSAGE_STORAGE</td> + <td class="code"><pre>'django.contrib.messages.storage.fallback.FallbackStorage'</pre></td> + </tr> + + <tr> + <td>MIDDLEWARE</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>MIGRATION_MODULES</td> + <td class="code"><pre>{}</pre></td> + </tr> + + <tr> + <td>MONTH_DAY_FORMAT</td> + <td class="code"><pre>'F j'</pre></td> + </tr> + + <tr> + <td>NUMBER_GROUPING</td> + <td class="code"><pre>0</pre></td> + </tr> + + <tr> + <td>PASSWORD_HASHERS</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + <tr> + <td>PASSWORD_RESET_TIMEOUT</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + <tr> + <td>PREPEND_WWW</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>ROOT_URLCONF</td> + <td class="code"><pre>'__main__'</pre></td> + </tr> + + <tr> + <td>SECRET_KEY</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + <tr> + <td>SECRET_KEY_FALLBACKS</td> + <td class="code"><pre>'********************'</pre></td> + </tr> + + <tr> + <td>SECURE_CONTENT_TYPE_NOSNIFF</td> + <td class="code"><pre>True</pre></td> + </tr> + + <tr> + <td>SECURE_CROSS_ORIGIN_OPENER_POLICY</td> + <td class="code"><pre>'same-origin'</pre></td> + </tr> + + <tr> + <td>SECURE_HSTS_INCLUDE_SUBDOMAINS</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>SECURE_HSTS_PRELOAD</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>SECURE_HSTS_SECONDS</td> + <td class="code"><pre>0</pre></td> + </tr> + + <tr> + <td>SECURE_PROXY_SSL_HEADER</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>SECURE_REDIRECT_EXEMPT</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>SECURE_REFERRER_POLICY</td> + <td class="code"><pre>'same-origin'</pre></td> + </tr> + + <tr> + <td>SECURE_SSL_HOST</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>SECURE_SSL_REDIRECT</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>SERVER_EMAIL</td> + <td class="code"><pre>'root@localhost'</pre></td> + </tr> + + <tr> + <td>SESSION_CACHE_ALIAS</td> + <td class="code"><pre>'default'</pre></td> + </tr> + + <tr> + <td>SESSION_COOKIE_AGE</td> + <td class="code"><pre>1209600</pre></td> + </tr> + + <tr> + <td>SESSION_COOKIE_DOMAIN</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>SESSION_COOKIE_HTTPONLY</td> + <td class="code"><pre>True</pre></td> + </tr> + + <tr> + <td>SESSION_COOKIE_NAME</td> + <td class="code"><pre>'sessionid'</pre></td> + </tr> + + <tr> + <td>SESSION_COOKIE_PATH</td> + <td class="code"><pre>'/'</pre></td> + </tr> + + <tr> + <td>SESSION_COOKIE_SAMESITE</td> + <td class="code"><pre>'Lax'</pre></td> + </tr> + + <tr> + <td>SESSION_COOKIE_SECURE</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>SESSION_ENGINE</td> + <td class="code"><pre>'django.contrib.sessions.backends.db'</pre></td> + </tr> + + <tr> + <td>SESSION_EXPIRE_AT_BROWSER_CLOSE</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>SESSION_FILE_PATH</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>SESSION_SAVE_EVERY_REQUEST</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>SESSION_SERIALIZER</td> + <td class="code"><pre>'django.contrib.sessions.serializers.JSONSerializer'</pre></td> + </tr> + + <tr> + <td>SHORT_DATETIME_FORMAT</td> + <td class="code"><pre>'m/d/Y P'</pre></td> + </tr> + + <tr> + <td>SHORT_DATE_FORMAT</td> + <td class="code"><pre>'m/d/Y'</pre></td> + </tr> + + <tr> + <td>SIGNING_BACKEND</td> + <td class="code"><pre>'django.core.signing.TimestampSigner'</pre></td> + </tr> + + <tr> + <td>SILENCED_SYSTEM_CHECKS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>STATICFILES_DIRS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>STATICFILES_FINDERS</td> + <td class="code"><pre>['django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder']</pre></td> + </tr> + + <tr> + <td>STATIC_ROOT</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>STATIC_URL</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>STORAGES</td> + <td class="code"><pre>{'default': {'BACKEND': 'django.core.files.storage.FileSystemStorage'}, + 'staticfiles': {'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage'}}</pre></td> + </tr> + + <tr> + <td>TEMPLATES</td> + <td class="code"><pre>[{'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': ['templates']}]</pre></td> + </tr> + + <tr> + <td>TEST_NON_SERIALIZED_APPS</td> + <td class="code"><pre>[]</pre></td> + </tr> + + <tr> + <td>TEST_RUNNER</td> + <td class="code"><pre>'django.test.runner.DiscoverRunner'</pre></td> + </tr> + + <tr> + <td>THOUSAND_SEPARATOR</td> + <td class="code"><pre>','</pre></td> + </tr> + + <tr> + <td>TIME_FORMAT</td> + <td class="code"><pre>'P'</pre></td> + </tr> + + <tr> + <td>TIME_INPUT_FORMATS</td> + <td class="code"><pre>['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']</pre></td> + </tr> + + <tr> + <td>TIME_ZONE</td> + <td class="code"><pre>'America/Chicago'</pre></td> + </tr> + + <tr> + <td>USE_I18N</td> + <td class="code"><pre>True</pre></td> + </tr> + + <tr> + <td>USE_THOUSAND_SEPARATOR</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>USE_TZ</td> + <td class="code"><pre>True</pre></td> + </tr> + + <tr> + <td>USE_X_FORWARDED_HOST</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>USE_X_FORWARDED_PORT</td> + <td class="code"><pre>False</pre></td> + </tr> + + <tr> + <td>WSGI_APPLICATION</td> + <td class="code"><pre>None</pre></td> + </tr> + + <tr> + <td>X_FRAME_OPTIONS</td> + <td class="code"><pre>'DENY'</pre></td> + </tr> + + <tr> + <td>YEAR_MONTH_FORMAT</td> + <td class="code"><pre>'F Y'</pre></td> + </tr> + + </tbody> + </table> + + </div> +</main> + + +<footer id="explanation"> + <p> + You’re seeing this error because you have <code>DEBUG = True</code> in your + Django settings file. Change that to <code>False</code>, and Django will + display a standard page generated by the handler for this status code. + </p> +</footer> + +</body> +</html> diff --git a/tests/appsec/iast/fixtures/plain_stacktrace.txt b/tests/appsec/iast/fixtures/plain_stacktrace.txt new file mode 100644 index 00000000000..be648d208ae --- /dev/null +++ b/tests/appsec/iast/fixtures/plain_stacktrace.txt @@ -0,0 +1,35 @@ +Environment: + + +Request Method: GET +Request URL: http://localhost:8000/ + +Django Version: 5.1.5 +Python Version: 3.12.5 +Installed Applications: +[] +Installed Middleware: +[] + +Traceback (most recent call last): + File "/usr/local/lib/python3.9/site-packages/some_module.py", line 42, in process_data + result = complex_calculation(data) + File "/usr/local/lib/python3.9/site-packages/another_module.py", line 158, in complex_calculation + intermediate = perform_subtask(data_slice) + File "/usr/local/lib/python3.9/site-packages/subtask_module.py", line 27, in perform_subtask + processed = handle_special_case(data_slice) + File "/usr/local/lib/python3.9/site-packages/special_cases.py", line 84, in handle_special_case + return apply_algorithm(data_slice, params) + File "/usr/local/lib/python3.9/site-packages/algorithm_module.py", line 112, in apply_algorithm + step_result = execute_step(data, params) + File "/usr/local/lib/python3.9/site-packages/step_execution.py", line 55, in execute_step + temp = pre_process(data) + File "/usr/local/lib/python3.9/site-packages/pre_processing.py", line 33, in pre_process + validated_data = validate_input(data) + File "/usr/local/lib/python3.9/site-packages/validation.py", line 66, in validate_input + check_constraints(data) + File "/usr/local/lib/python3.9/site-packages/constraints.py", line 19, in check_constraints + raise ValueError("Constraint violation at step 9") +ValueError: Constraint violation at step 9 + +Lorem Ipsum Foobar diff --git a/tests/appsec/iast/taint_sinks/test_stacktrace_leak.py b/tests/appsec/iast/taint_sinks/test_stacktrace_leak.py new file mode 100644 index 00000000000..45c40f43df7 --- /dev/null +++ b/tests/appsec/iast/taint_sinks/test_stacktrace_leak.py @@ -0,0 +1,39 @@ +import os + +from ddtrace.appsec._iast.constants import VULN_STACKTRACE_LEAK +from ddtrace.appsec._iast.taint_sinks.stacktrace_leak import asm_check_stacktrace_leak +from tests.appsec.iast.taint_sinks.conftest import _get_span_report + + +def _load_html_django_stacktrace(): + return open(os.path.join(os.path.dirname(__file__), "../fixtures/django_debug_page.html")).read() + + +def _load_text_stacktrace(): + return open(os.path.join(os.path.dirname(__file__), "../fixtures/plain_stacktrace.txt")).read() + + +def test_asm_check_stacktrace_leak_html(iast_context_defaults): + asm_check_stacktrace_leak(_load_html_django_stacktrace()) + span_report = _get_span_report() + vulnerabilities = list(span_report.vulnerabilities) + vulnerabilities_types = [vuln.type for vuln in vulnerabilities] + assert len(vulnerabilities) == 1 + assert VULN_STACKTRACE_LEAK in vulnerabilities_types + assert ( + vulnerabilities[0].evidence.value + == 'Module: ".home.foobaruser.sources.minimal-django-example.app.py"\nException: IndexError' + ) + + +def test_asm_check_stacktrace_leak_text(iast_context_defaults): + asm_check_stacktrace_leak(_load_text_stacktrace()) + span_report = _get_span_report() + vulnerabilities = list(span_report.vulnerabilities) + vulnerabilities_types = [vuln.type for vuln in vulnerabilities] + assert len(vulnerabilities) == 1 + assert VULN_STACKTRACE_LEAK in vulnerabilities_types + assert ( + vulnerabilities[0].evidence.value + == 'Module: ".usr.local.lib.python3.9.site-packages.constraints.py"\nException: ValueError' + ) diff --git a/tests/appsec/integrations/django_tests/django_app/urls.py b/tests/appsec/integrations/django_tests/django_app/urls.py index be2d142baa2..c9dffbde8d8 100644 --- a/tests/appsec/integrations/django_tests/django_app/urls.py +++ b/tests/appsec/integrations/django_tests/django_app/urls.py @@ -81,4 +81,6 @@ def shutdown(request): handler("appsec/validate_querydict/$", views.validate_querydict, name="validate_querydict"), path("appsec/path-params/<int:year>/<str:month>/", views.path_params_view, name="path-params-view"), path("appsec/checkuser/<str:user_id>/", views.checkuser_view, name="checkuser"), + path("appsec/stacktrace_leak/", views.stacktrace_leak_view), + path("appsec/stacktrace_leak_500/", views.stacktrace_leak_500_view), ] diff --git a/tests/appsec/integrations/django_tests/django_app/views.py b/tests/appsec/integrations/django_tests/django_app/views.py index ef4fd78b138..74cc239cf34 100644 --- a/tests/appsec/integrations/django_tests/django_app/views.py +++ b/tests/appsec/integrations/django_tests/django_app/views.py @@ -273,3 +273,20 @@ def validate_querydict(request): return HttpResponse( "x=%s, all=%s, keys=%s, urlencode=%s" % (str(res), str(lres), str(keys), qd.urlencode()), status=200 ) + + +def stacktrace_leak_view(request): + from tests.appsec.iast.taint_sinks.test_stacktrace_leak import _load_html_django_stacktrace + + return HttpResponse(_load_html_django_stacktrace()) + + +def stacktrace_leak_500_view(request): + try: + raise Exception("FooBar Exception") + except Exception: + import sys + + from django.views.debug import technical_500_response + + return technical_500_response(request, *sys.exc_info()) diff --git a/tests/appsec/integrations/django_tests/test_django_appsec_iast.py b/tests/appsec/integrations/django_tests/test_django_appsec_iast.py index d2c52337482..e51b3349ef5 100644 --- a/tests/appsec/integrations/django_tests/test_django_appsec_iast.py +++ b/tests/appsec/integrations/django_tests/test_django_appsec_iast.py @@ -3,6 +3,7 @@ import pytest +from ddtrace.appsec._asm_request_context import start_context from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._patch_modules import patch_iast @@ -11,6 +12,8 @@ from ddtrace.appsec._iast.constants import VULN_HEADER_INJECTION from ddtrace.appsec._iast.constants import VULN_INSECURE_COOKIE from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION +from ddtrace.appsec._iast.constants import VULN_STACKTRACE_LEAK +from ddtrace.ext import SpanTypes from ddtrace.internal.compat import urlencode from tests.appsec.iast.iast_utils import get_line_and_hash from tests.utils import override_env @@ -886,3 +889,69 @@ def test_django_insecure_cookie_special_characters(client, test_spans, tracer): assert "line" not in vulnerability["location"].keys() assert vulnerability["location"]["spanId"] assert vulnerability["hash"] + + +@pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") +def test_django_stacktrace_leak(client, test_spans, tracer): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + oce.reconfigure() + root_span, _ = _aux_appsec_get_root_span( + client, + test_spans, + tracer, + url="/appsec/stacktrace_leak/", + ) + + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_STACKTRACE_LEAK + assert vulnerability["evidence"] == { + "valueParts": [ + {"value": 'Module: ".home.foobaruser.sources.minimal-django-example.app.py"\nException: IndexError'} + ] + } + assert vulnerability["hash"] + + +@pytest.fixture +def debug_mode(): + from django.conf import settings + + original_debug = settings.DEBUG + settings.DEBUG = True + yield + settings.DEBUG = original_debug + + +@pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") +def test_django_stacktrace_from_technical_500_response(client, test_spans, tracer, debug_mode): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + with tracer.trace("test", span_type=SpanTypes.WEB, service="test") as span: + start_context(span) + oce.reconfigure() + root_span, response = _aux_appsec_get_root_span( + client, + test_spans, + tracer, + url="/appsec/stacktrace_leak_500/", + content_type="text/html", + ) + + assert response.status_code == 500, "Expected a 500 status code" + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_STACKTRACE_LEAK + assert vulnerability["evidence"] == { + "valueParts": [ + {"value": "Module: tests.appsec.integrations.django_tests.django_app.views\nException: Exception"} + ] + } + assert vulnerability["hash"] diff --git a/tests/conftest.py b/tests/conftest.py index 5ee2933f187..abd0f0dc25e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -131,7 +131,8 @@ def auto_enable_crashtracking(): def enable_crashtracking(auto_enable_crashtracking): if auto_enable_crashtracking: crashtracking.start() - assert crashtracking.is_started() + # JJJ + # assert crashtracking.is_started() yield diff --git a/tests/contrib/fastapi/test_fastapi_appsec_iast.py b/tests/contrib/fastapi/test_fastapi_appsec_iast.py index b9d663f1d9a..e6b5d6a40b8 100644 --- a/tests/contrib/fastapi/test_fastapi_appsec_iast.py +++ b/tests/contrib/fastapi/test_fastapi_appsec_iast.py @@ -26,9 +26,11 @@ from ddtrace.appsec._iast.constants import VULN_NO_HTTPONLY_COOKIE from ddtrace.appsec._iast.constants import VULN_NO_SAMESITE_COOKIE from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION +from ddtrace.appsec._iast.constants import VULN_STACKTRACE_LEAK from ddtrace.contrib.internal.fastapi.patch import patch as patch_fastapi from ddtrace.contrib.internal.sqlite3.patch import patch as patch_sqlite_sqli from tests.appsec.iast.iast_utils import get_line_and_hash +from tests.appsec.iast.taint_sinks.test_stacktrace_leak import _load_text_stacktrace from tests.utils import override_env from tests.utils import override_global_config @@ -901,3 +903,28 @@ async def header_injection_inline_response(request: Request): assert len(loaded["vulnerabilities"]) == 1 vulnerability = loaded["vulnerabilities"][0] assert vulnerability["type"] == VULN_HEADER_INJECTION + + +def test_fastapi_stacktrace_leak(fastapi_application, client, tracer, test_spans): + @fastapi_application.get("/stacktrace_leak/", response_class=PlainTextResponse) + async def stacktrace_leak_inline_response(request: Request): + return PlainTextResponse( + content=_load_text_stacktrace(), + ) + + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + _aux_appsec_prepare_tracer(tracer) + resp = client.get( + "/stacktrace_leak/", + ) + assert resp.status_code == 200 + + span = test_spans.pop_traces()[0][0] + assert span.get_metric(IAST.ENABLED) == 1.0 + + iast_tag = span.get_tag(IAST.JSON) + assert iast_tag is not None + loaded = json.loads(iast_tag) + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_STACKTRACE_LEAK diff --git a/tests/contrib/flask/test_flask_appsec_iast.py b/tests/contrib/flask/test_flask_appsec_iast.py index bedf2b58bdc..cbae3c6d8d1 100644 --- a/tests/contrib/flask/test_flask_appsec_iast.py +++ b/tests/contrib/flask/test_flask_appsec_iast.py @@ -1,5 +1,6 @@ import json import sys +import traceback from flask import request from importlib_metadata import version @@ -15,6 +16,7 @@ from ddtrace.appsec._iast.constants import VULN_NO_HTTPONLY_COOKIE from ddtrace.appsec._iast.constants import VULN_NO_SAMESITE_COOKIE from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION +from ddtrace.appsec._iast.constants import VULN_STACKTRACE_LEAK from ddtrace.appsec._iast.taint_sinks.header_injection import patch as patch_header_injection from ddtrace.contrib.internal.sqlite3.patch import patch as patch_sqlite_sqli from tests.appsec.iast.iast_utils import get_line_and_hash @@ -1482,6 +1484,105 @@ def cookie_secure(): loaded = root_span.get_tag(IAST.JSON) assert loaded is None + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_stacktrace_leak(self): + @self.app.route("/stacktrace_leak/") + def stacktrace_leak(): + from flask import Response + + return Response( + """Traceback (most recent call last): + File "/usr/local/lib/python3.9/site-packages/some_module.py", line 42, in process_data + result = complex_calculation(data) + File "/usr/local/lib/python3.9/site-packages/another_module.py", line 158, in complex_calculation + intermediate = perform_subtask(data_slice) + File "/usr/local/lib/python3.9/site-packages/subtask_module.py", line 27, in perform_subtask + processed = handle_special_case(data_slice) + File "/usr/local/lib/python3.9/site-packages/special_cases.py", line 84, in handle_special_case + return apply_algorithm(data_slice, params) + File "/usr/local/lib/python3.9/site-packages/algorithm_module.py", line 112, in apply_algorithm + step_result = execute_step(data, params) + File "/usr/local/lib/python3.9/site-packages/step_execution.py", line 55, in execute_step + temp = pre_process(data) + File "/usr/local/lib/python3.9/site-packages/pre_processing.py", line 33, in pre_process + validated_data = validate_input(data) + File "/usr/local/lib/python3.9/site-packages/validation.py", line 66, in validate_input + check_constraints(data) + File "/usr/local/lib/python3.9/site-packages/constraints.py", line 19, in check_constraints + raise ValueError("Constraint violation at step 9") +ValueError: Constraint violation at step 9 + +Lorem Ipsum Foobar +""" + ) + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.get("/stacktrace_leak/") + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_STACKTRACE_LEAK + assert vulnerability["evidence"] == { + "valueParts": [ + {"value": 'Module: ".usr.local.lib.python3.9.site-packages.constraints.py"\nException: ValueError'} + ] + } + + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_stacktrace_leak_from_debug_page(self): + try: + from werkzeug.debug.tbtools import DebugTraceback + except ImportError: + return # this version of werkzeug does not have the DebugTraceback + + @self.app.route("/stacktrace_leak_debug/") + def stacktrace_leak(): + from flask import Response + + try: + raise ValueError() + except ValueError as exc: + dt = DebugTraceback( + exc, + traceback.TracebackException.from_exception(exc), + ) + + # Render the debugger HTML + html = dt.render_debugger_html(evalex=False, secret="test_secret", evalex_trusted=False) + return Response(html, mimetype="text/html") + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.get("/stacktrace_leak_debug/") + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_STACKTRACE_LEAK + assert "valueParts" in vulnerability["evidence"] + assert "tests.contrib.flask.test_flask_appsec_iast" in vulnerability["evidence"]["valueParts"][0]["value"] + assert "Exception: ValueError" in vulnerability["evidence"]["valueParts"][0]["value"] + class FlaskAppSecIASTDisabledTestCase(BaseFlaskTestCase): @pytest.fixture(autouse=True)