This commit safely handles the dynamic table names in database migration queries
by leveraging `sqlalchemy.select` and `sqlalchemy.table` in `app/utils/db_migrate.py`.
It addresses the `# noqa: S608` exception that was in place for string interpolation
SQL queries which are a known security anti-pattern.
Additionally, this commit includes the latest updates to `app/views/base.py`
from the `main` branch to handle backward compatibility across Starlette
versions (<1.0 vs 1.0+) when invoking `Jinja2Templates.TemplateResponse`,
resolving previous merge conflicts in the PR.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
🚨 Severity: CRITICAL
💡 Vulnerability: The generic file hashing utility `app/utils/file_operations.py:hash_file` was vulnerable to path traversal. An attacker controlling the `filepath` argument could read arbitrary files on the system by passing relative paths like `../../../etc/passwd` or providing absolute paths directly.
🎯 Impact: This could lead to Arbitrary File Read and potential information disclosure.
🔧 Fix: Used `pathlib.Path.resolve()` to resolve both the target file path and the allowed base directory (`settings.workdir`). Added a strict check to ensure the resolved target path is strictly within the allowed boundary using `filepath_obj.relative_to(workdir_obj)`, catching the `ValueError` raised when the path is out of bounds. This safely blocks both relative traversal attacks and arbitrary absolute paths, without breaking legitimate relative application paths.
✅ Verification: Ran the test suite `pytest tests/test_path_traversal_security.py -v` successfully, which explicitly checks for `FileNotFoundError` upon traversal attempts.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Modified `is_private_ip` in `app/utils/network.py` to fail securely by returning True (blocking the request) when a hostname cannot be resolved. The previous implementation failed open, creating a risk for Server-Side Request Forgery (SSRF) and DNS rebinding attacks.
Updated corresponding tests to expect the secure behavior and correctly appended the security finding to the Sentinel journal.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
🚨 Severity: HIGH
💡 Vulnerability: User-provided IMAP `host` in `_test_imap_connection` and `pull_inbox` was not validated against private IPs, creating an SSRF risk.
🎯 Impact: Attackers could abuse the endpoints to port-scan or interact with internal/private network services.
🔧 Fix: Integrated `is_private_ip` from `app.utils.network` to block connections resolving to private, loopback, link-local, or reserved IPs.
✅ Verification: Ran `test_imap_tasks.py` and `test_api_imap_accounts.py` successfully. Checked `ruff` output and diffs. Removed all scratch files from the commit.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
🚨 Severity: HIGH
💡 Vulnerability: User-provided IMAP `host` in `_test_imap_connection` and `pull_inbox` was not validated against private IPs, creating an SSRF risk.
🎯 Impact: Attackers could abuse the endpoints to port-scan or interact with internal/private network services.
🔧 Fix: Integrated `is_private_ip` from `app.utils.network` to block connections resolving to private, loopback, link-local, or reserved IPs.
✅ Verification: Ran `test_imap_tasks.py` and `test_api_imap_accounts.py` successfully. Checked `ruff` output and diffs.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
The transition to a newer FastAPI/Starlette version changed the signature of `Jinja2Templates.TemplateResponse` from `(name, context)` to `(request, name, context)`.
The `app/views/base.py:template_response_with_version` wrapper naively forwarded positional arguments `*args` to `original_template_response`. This caused the template name (`"files.html"`) to be passed as the `request` parameter, and the context dictionary to be passed as the `name` parameter. This resulted in Jinja2 attempting to cache the template using a dictionary as the cache key, which triggered a `TypeError: unhashable type: 'dict'`.
This commit updates the wrapper to automatically translate the legacy positional arguments `(name: str, context: dict)` into the explicit keyword arguments `request=context.get("request"), name=name, context=context` required by modern Starlette, preventing template rendering crashes across the application and restoring passing CI test suites.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
The `preview_migration` function in `app/utils/db_migrate.py` used string
interpolation to dynamically execute a COUNT query on the source database
(`f"SELECT COUNT(*) FROM {quoted_table}"`).
While the table name was quoted via the dialect's identifier preparer and
validated with a regex, string interpolation for raw SQL should be avoided
as it represents an anti-pattern and a theoretical risk for SQL injection
if validation controls are ever bypassed or modified.
This commit replaces the raw string interpolation with safe, parameterized
SQLAlchemy Core query construction `select(func.count()).select_from(table(table_name))`,
which automatically handles table quoting and execution safely. It also removes
the unused `text` import to keep the code clean.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Modified `is_private_ip` in `app/utils/network.py` to fail securely by returning True (blocking the request) when a hostname cannot be resolved. The previous implementation failed open, creating a risk for Server-Side Request Forgery (SSRF) and DNS rebinding attacks.
Updated corresponding tests to expect the secure behavior.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
- Extract all OAuth registration into _setup_social_providers() with a
_register_oauth_client() helper that clears the authlib _clients cache
so credentials can change without a restart
- Add refresh_social_providers() public function called after every
settings reload (lifespan startup + settings_sync live reload)
- Fix connections page linked status to use _get_effective() (DB-aware)
instead of the stale startup-time SOCIAL_PROVIDERS dict
- Fix oauth_configured template variable similarly
- Add tests: DB-driven linked status, stale-provider clearing,
register_oauth_client cache-clear, refresh function coverage
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/ef15910f-fd25-469a-814b-9e1fb40659c9
- Add `qr_login_enabled` boolean config field (default True) to app/config.py
- Add `qr_login_enabled` entry to settings metadata in app/utils/settings_service.py
- Fix app/views/settings.py to use `qr_login_enabled` directly instead of
deriving enablement status from qr_login_challenge_ttl_seconds (integer TTL)
- Fix admin_connections.html: remove hardcoded `disabled` attribute from the
Mobile Phone Upload toggle and wire up onchange handler so toggling actually
persists the setting via toggleSetting('qr_login_enabled', this.checked)
- Gate all three QR auth API endpoints on settings.qr_login_enabled so the
feature is actually disabled when the toggle is turned off
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/8421cdb2-d92d-4b83-9cda-c44983d35173
- Add PUT /{key} endpoint to settings API with SettingValueUpdate body model (only
requires value, key comes from URL path) — fixes 405 Method Not Allowed errors
from the admin Connections wizard which used PUT to save settings
- Fix grey toggles on /admin/connections: they appeared grey because all saves were
silently failing with 405; now saves succeed and toggles reflect actual state
- Add social_auth_google_use_global_credentials config field and auth.py logic to
reuse google_drive_client_id/google_drive_client_secret for Google Sign-In
- Add social_auth_microsoft_use_global_credentials config field and auth.py logic to
reuse onedrive_client_id/onedrive_client_secret for Microsoft Sign-In
- Also apply consistent both-field check for Dropbox global credentials fallback
- Add settings metadata entries for the two new boolean settings
- Add Google and Microsoft settings_keys to admin_connections service definitions
- Add JS visibility toggle logic for Google/Microsoft credential fields in admin UI
- Add 6 new unit/integration tests for PUT endpoint and SettingValueUpdate model
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/ac66041a-2cbd-4d90-8f8e-3588c629d4d8
- Add 3 new config fields for browser SDK sample rates:
sentry_js_traces_sample_rate (default 0.0),
sentry_js_replay_session_sample_rate (default 0.0),
sentry_js_replay_on_error_sample_rate (default 0.1)
- Expose Sentry config to Jinja2 templates via _inject_global_context;
empty-string DSN normalized to None so {% if sentry_dsn %} guard works
- Load Sentry Browser SDK bundle.tracing.replay.min.js from the official
Sentry CDN in base.html when SENTRY_DSN is configured, with Sentry.init()
for error capture, browser tracing and session replay
- Register new JS settings fields in SETTING_METADATA so they appear on the
admin Settings → Observability page
- Update .env.demo with commented-out examples for SENTRY_JS_* variables
- Update docs/ConfigurationGuide.md and docs/SentrySetup.md with full
browser SDK documentation, env-specific examples and troubleshooting
- Add TestSentryJsTemplateContext (5 tests) and TestSentryJsConfig (4 tests)
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/4a945567-67df-4264-aaed-75eb2e236e9c
- /files/<id> → new summary page with navigation cards
- /files/<id>/detail → document detail with metadata, preview, text
- /files/<id>/process → processing pipeline status and history
- /files/<id>/annotations → comments & annotations with EmbedPDF viewer
- /files/<id>/comments → redirects to /annotations
- Added embed-pdf-viewer as git submodule for PDF annotation viewer
- Updated all navigation links across templates
- Updated all tests to use new URL structure
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/12276514-bd3d-4e3e-84d9-5977d1f82b19
The `_test_webdav_connection` function previously used `urllib.request.urlopen`
to verify connection credentials. This triggers a Bandit B310 warning because
`urllib` supports multiple schemes (like file://, ftp://) and implicitly follows
redirects.
Although scheme checking and a basic `is_private_ip` validation were implemented,
using `urllib.request` remains risky because a public URL could return an
HTTP redirect to a private IP (e.g., 127.0.0.1) which `urllib` would blindly follow,
causing an SSRF (Server-Side Request Forgery) bypass.
This commit replaces `urllib.request` with `httpx.request` using explicitly
`follow_redirects=False`. This eliminates the B310 vulnerability, ensures
requests only hit the specified URL without following potentially malicious
redirects, and standardizes the application on `httpx` for safer HTTP connections.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
- Add DocumentComment and DocumentAnnotation models to app/models.py
- Create migration 041_add_document_comments_and_annotations
- Add API endpoints for CRUD operations on comments and annotations
- Add threaded comment support with parent_id relationships
- Add @mention extraction from comment body text
- Add resolve/unresolve comment thread endpoint
- Add mentionable users endpoint (GET /api/users/mentionable)
- Add 43 unit tests covering all endpoints and edge cases
- Add 29 i18n translation keys to en.json
- Update API documentation in docs/API.md
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/3894af37-0f19-457b-8811-f1feb18b17ef
Resolve all merge conflicts between our automation feature branch and
current main (v0.163.0, 920 commits ahead).
Conflicts resolved:
- app/api/__init__.py: add automation_router alongside main's new routers
(classification_rules, qr_auth, sessions, system_reset)
- app/config.py: add main's new settings (dropbox_use_global_credentials,
factory_reset_on_startup, enable_factory_reset)
- app/models.py: add main's new models (ClassificationRuleModel, UserSession,
QRLoginChallenge, SharePoint integration type)
- app/utils/settings_service.py: merge automation_hooks_enabled with main's
new metadata entries
- docs/API.md: merge automation API docs with main's classification rules docs
- docs/ConfigurationGuide.md: add factory reset settings
- tests/conftest.py: import both AutomationHook and new main models
Migration renumbered:
- 037_add_automation_hooks → 040_add_automation_hooks
- down_revision: 039_add_classification_rules (was 036_add_document_translation_fields)
- Chain: 036 → 037 → 038 → 039 → 040 (automation hooks)
For all non-automation files with conflicts, main's version was taken since
our branch did not modify those files (conflicts were from a stale prior merge).
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/cb62f012-3b69-4415-835e-3857ce3e9f45
Added tests for POST /api/dropbox/list-folders (7 tests) and
POST /api/onedrive/list-folders (7 tests) covering success, subfolder
navigation, empty directories, auth errors, API errors, path
normalization, and alphabetical sorting.
Added view tests for system credentials toggle visibility in Dropbox
setup wizard.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Added POST /api/dropbox/list-folders and POST /api/onedrive/list-folders
endpoints that accept an OAuth access_token and return folder listings.
After successful OAuth authorization in the callback pages, users now
see an interactive folder browser to select the target folder for their
integration. The selected folder is saved to the integration config.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
When system-wide Dropbox, Google Drive, or OneDrive app credentials are
configured by the admin, user-mode OAuth wizards now default to using
them. A toggle lets users switch to custom credentials if needed. This
removes the need for end users to register their own cloud provider apps.
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>