Skip to content

Closes #22654: Redact install paths from debug tracebacks - #22655

Merged
jeremystretch merged 4 commits into
mainfrom
22654-configtemplate-debug-path-exposure
Jul 14, 2026
Merged

jeremystretch merged 4 commits into
mainfrom
22654-configtemplate-debug-path-exposure

Conversation

@bctiemann

@bctiemann bctiemann commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor

Closes: #22654

Summary

  • ConfigTemplate.format_render_error() now strips the absolute install-path prefix from all File "..." lines in the Jinja2 traceback before returning it, so internal filesystem layout is not disclosed to users (CWE-209).

Root cause

ConfigTemplate.format_render_error() returned traceback.format_exception(exc) verbatim when debug=True. The output includes File "/abs/install/path/..." entries for every Python frame in the traceback, revealing the absolute filesystem layout of the NetBox installation to whoever can trigger a render error (CWE-209).

Fix

Path redaction — after calling traceback.format_exception(), apply re.sub with a pattern anchored to re.escape(install_root) (computed as os.path.dirname(settings.BASE_DIR) + os.sep) to replace the absolute prefix with an empty string. The result is a traceback with paths relative to the repo root, which is safe to show. When the venv lives outside the repo, its root is stripped separately as well.

Tests

Added test_format_render_error_debug_redacts_install_path and test_format_render_error_non_debug_returns_concise_message to ConfigTemplateDebugTestCase in extras/tests/test_models.py. Existing test_render_jinja2_* and ConfigTemplateTestCase tests continue to pass.

bctiemann and others added 2 commits July 9, 2026 11:43
…bug field to superusers

- `format_render_error()` now strips the absolute install-path prefix (everything up to but not including the repo root) from all `File "..."` lines in the traceback using `re.sub`, so internal filesystem layout is not exposed (CWE-209).
- `ConfigTemplateForm.__init__` removes the `debug` field and its fieldset entry when the requesting user is not a superuser; `ConfigTemplateEditView` injects `request` via `functools.partial` before the base view instantiates the form.
- `ConfigTemplateBulkEditForm.__init__` applies the same superuser gate; `ConfigTemplateBulkEditView.post()` injects `request` the same way.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion

- ConfigTemplateSerializer.validate_debug() rejects debug=True from non-superusers
  with a 400 ValidationError, closing the API bypass left by the form-only gate.
- Add ConfigTemplateFormSuperuserGateTestCase (test_forms.py) covering both model
  form and bulk edit form: superuser sees debug, non-superuser does not.
- Add two tests to ConfigTemplateDebugTestCase (test_models.py): path redaction
  assertion (assertNotIn(install_root, result)) and concise-message assertion.
- Add two API tests to ConfigTemplateTestCase (test_api.py): non-superuser PATCH
  debug=True yields 400; superuser PATCH debug=True yields 200.
- Improve comments: note venv/stdlib path behavior in format_render_error(),
  document the field-absent-on-save behavior and InlineFields caveat in the form.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bctiemann

Copy link
Copy Markdown
Contributor Author

@claude /review and report only on areas of potential concern.

@github-actions

github-actions Bot commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @bctiemann's task in 3m 43s —— View job


PR Review: Redact install paths from debug tracebacks; restrict debug field to superusers

Tasks:

  • Understand request (code review, report only concerns)
  • Read changed files
  • Analyze implementation for security, correctness, and design issues
  • Post findings

Areas of Potential Concern

1. Incomplete Path Redaction — Venv and Stdlib Paths Remain Visible

extras/models/configs.py:332

install_root = os.path.dirname(settings.BASE_DIR) + os.sep

The regex strips only paths under install_root (the parent of BASE_DIR). Traceback frames from two common sources are left verbatim:

  • Python stdlib — e.g. File "/usr/lib/python3.12/abc.py" — reveals the OS Python path, which is low-risk but still information disclosure.
  • Virtualenv outside the repo — e.g. File "/home/deploy/.venv/netbox/lib/python3.12/site-packages/jinja2/..." — this reveals the deployment user's home directory path (e.g. /home/deploy/) and is deployment-specific. This is the common deployment pattern for NetBox (the docs and AGENTS.md both use ~/.venv/netbox).

The comment in the code says venv paths are "under install_root (including the venv)", but a venv at ~/.venv/netbox is outside install_root. The claim in the comment is only correct when the venv happens to be inside the repo directory. In the typical case, the venv path disclosure is only partially mitigated.

Fix this →


2. self.form Mutation Is Fragile

extras/views.py:1225-1231

def get(self, request, *args, **kwargs):
    self.form = functools.partial(forms.ConfigTemplateForm, request=request)
    return super().get(request, *args, **kwargs)

def post(self, request, *args, **kwargs):
    self.form = functools.partial(forms.ConfigTemplateForm, request=request)
    return super().post(request, *args, **kwargs)

The pattern overwrites the class-level attribute form = forms.ConfigTemplateForm with an instance variable. This works because Django instantiates a new view object per request, so there's no race condition. However:

  • The class attribute form = forms.ConfigTemplateForm (line 1223) remains as a silent fallback — if either get() or post() is bypassed (e.g. a dispatch() override in a mixin or subclass), the un-partial'd form class is used, silently dropping the superuser gate.
  • The more idiomatic Django CBV approach would be to override get_form_class() (if the base view supports it) or implement a get_form() override, making it impossible to call the base view logic without the guard being applied.

This is not a bug in the current code but is a maintenance risk if the class hierarchy changes.


3. API Test Gap: Non-superuser Setting debug=False Is Not Covered

extras/tests/test_api.py:1287-1307

There's a test that a non-superuser setting debug=True returns 400, and a test that a superuser setting debug=True returns 200. Missing from coverage:

  • Non-superuser PATCHing debug=False should succeed (permitted). There's no test for this path.
  • Non-superuser PATCHing other fields on a template that already has debug=True in the DB should preserve debug=True (i.e., validate_debug should not fire when debug is absent from the payload). This is also untested.

These are the "allow" paths for non-superusers and are worth explicitly testing to guard against regressions in validate_debug.


4. Fieldset Filter Has a Known Blind Spot — Comment Should Be Stronger

extras/forms/model_forms.py:863-864

# Note: the fieldset filter is a top-level string scan; InlineFields/TabbedGroups wrappers
# around 'debug' would not be caught, but none exist in the current fieldset definition.

This is correctly documented, but the code's correctness now depends on a non-obvious constraint ("none exist in the current fieldset definition"). If a future contributor wraps the debug field in an InlineFields or TabbedGroups, the field will silently reappear for non-superusers in the fieldset rendering while still being absent from self.fields. This would cause a template rendering error rather than a security bypass (the field is deleted from self.fields before the fieldset renders), but it's a fragility worth noting.


Summary: The core security intent (redact install paths; gate debug writes to superusers) is sound. The main practical gap is #1 — venv paths outside install_root can still disclose the deployment user's home directory when debug tracebacks are shown. Items #2–#4 are code-quality and test-coverage observations rather than vulnerabilities.

…h tests

- Also strip sys.prefix when running inside a venv whose root is outside install_root
  (typical production pattern: ~/.venv/netbox/), so the deployment user home directory
  is not disclosed via tracebacks. The check sys.prefix != sys.base_prefix avoids
  stripping system Python paths when not in a venv.
- Update test_format_render_error_debug_redacts_install_path to assert venv_root is
  also absent from the redacted traceback when running inside a venv.
- Add test_debug_false_allowed_for_non_superuser: validate_debug must not block
  non-superusers from setting debug=False.
- Add test_debug_absent_from_payload_non_superuser: validate_debug must not fire
  when debug is absent from the PATCH payload, even if debug=True in the DB.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bctiemann

Copy link
Copy Markdown
Contributor Author

Addressed items 1 and 3. 2 and 4 are minor/below the bar for this fix.

@bctiemann
bctiemann requested review from a team and jeremystretch and removed request for a team July 9, 2026 17:21

@jeremystretch jeremystretch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The debug field is now restricted to superusers

This cannot be included in the scope of a bug fix, as it represents a change to intended functionality. It needs to be a separate FR. Please revert the related changes so that the PR is limited to the sanitization of file paths.

Restricting the debug field to superusers changes existing behavior for
any user who currently has edit permission on ConfigTemplate, which is
out of scope for a CWE-209 path-disclosure fix. Only the traceback
path-redaction change remains.
@bctiemann bctiemann changed the title Closes #22654: Redact install paths from debug tracebacks; restrict debug field to superusers Closes #22654: Redact install paths from debug tracebacks Jul 10, 2026
@bctiemann

Copy link
Copy Markdown
Contributor Author

Much simpler change now. Also opened #22664 to track the superuser gate.

@bctiemann
bctiemann requested a review from jeremystretch July 10, 2026 17:44
@jeremystretch
jeremystretch merged commit 16875c7 into main Jul 14, 2026
12 checks passed
@jeremystretch
jeremystretch deleted the 22654-configtemplate-debug-path-exposure branch July 14, 2026 19:44
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.

ConfigTemplate debug mode exposes server filesystem paths in rendered error output

2 participants