98 lines
5.3 KiB
Markdown
98 lines
5.3 KiB
Markdown
# Security Production Readiness Report
|
|
|
|
Date: 2026-03-02
|
|
Repository: /Users/bedas/Developer/GitHub/dcm
|
|
Assessment type: Static code and configuration review with targeted local security test execution
|
|
|
|
## Verdict
|
|
Not production ready.
|
|
|
|
Reason: one blocking, code-level access-control and data-disclosure issue was found.
|
|
|
|
## Preflight Results
|
|
- `command -v git` -> pass (`/usr/bin/git`)
|
|
- `git rev-parse --is-inside-work-tree` -> pass (`true`)
|
|
- `git status --short` -> clean before analysis
|
|
|
|
## Validation Commands And Outcomes
|
|
- `/Users/bedas/Developer/Python/global_venv/bin/python -m unittest backend/tests/test_security_controls.py` -> pass (32 tests)
|
|
- `/Users/bedas/Developer/Python/global_venv/bin/python -m unittest backend/tests/test_upload_request_size_middleware.py` -> pass (3 tests)
|
|
- `/Users/bedas/Developer/Python/global_venv/bin/python -m unittest backend/tests/test_app_settings_provider_resilience.py` -> pass (6 tests)
|
|
|
|
## Blocking Security Findings
|
|
|
|
### High: Non-global catalog presets are exposed to all authenticated users
|
|
- Severity: High (blocking)
|
|
- Why this is blocking:
|
|
- The settings model supports `global_shared` scope for predefined paths and tags, but the user-accessible discovery endpoints return all predefined entries without filtering by this scope.
|
|
- This breaks intended discoverability boundaries and leaks admin-curated non-global taxonomy metadata to standard users.
|
|
- Impact:
|
|
- Information disclosure across role boundaries for internal path and tag catalogs.
|
|
- Reduced separation between admin-only and user-visible metadata.
|
|
- Exploit path:
|
|
- Any authenticated non-admin user calls `GET /api/v1/documents/paths` and `GET /api/v1/documents/tags`.
|
|
- Endpoint responses include every predefined path or tag value regardless of `global_shared` state.
|
|
- Evidence:
|
|
- `backend/app/api/routes_documents.py:399-403` and `backend/app/api/routes_documents.py:423-427` include all predefined tags and paths.
|
|
- `backend/app/schemas/settings.py:145` and `backend/app/schemas/settings.py:159` define global discoverability scope.
|
|
- `backend/app/services/app_settings.py:709-710`, `backend/app/services/app_settings.py:730`, `backend/app/services/app_settings.py:758-759`, and `backend/app/services/app_settings.py:779` preserve `global_shared` state in normalized settings.
|
|
- Required remediation:
|
|
- Filter predefined entries returned by user-facing discovery endpoints by role and `global_shared`.
|
|
- Keep full catalog visibility for admins only.
|
|
- Add regression tests for non-admin path/tag discovery with mixed `global_shared` values.
|
|
|
|
## MUST KNOW User-Dependent Risks (Not Blocking Per Request)
|
|
|
|
These items are deployment, environment, or proxy dependent and are therefore not marked blocking per request requirements.
|
|
|
|
### High: Development-first runtime defaults can be promoted to production if not overridden
|
|
- Evidence:
|
|
- `.env.example:5` (`APP_ENV=development`)
|
|
- `.env.example:36-38` (`PROVIDER_BASE_URL_ALLOW_HTTP=true`, `PROVIDER_BASE_URL_ALLOW_PRIVATE_NETWORK=true`, empty allowlist)
|
|
- `.env.example:14-16` (`redis://`, `REDIS_SECURITY_MODE=compat`, `REDIS_TLS_MODE=allow_insecure`)
|
|
- `.env.example:40-41` (`PUBLIC_BASE_URL` on HTTP, local CORS defaults)
|
|
- `docker-compose.yml:56-57`, `docker-compose.yml:64-66`, `docker-compose.yml:101-102`, `docker-compose.yml:106-107`
|
|
- Risk:
|
|
- Weak outbound provider constraints, plaintext internal transport defaults, and non-production environment posture can persist in live deployments.
|
|
|
|
### Medium: Login throttle IP identity depends on proxy trust model
|
|
- Evidence:
|
|
- `backend/app/api/routes_auth.py:32-35` uses `request.client.host` only.
|
|
- Risk:
|
|
- Behind reverse proxies, all clients may collapse to proxy IP, increasing lockout abuse and reducing attribution quality.
|
|
|
|
### Medium: API documentation endpoints are exposed by default
|
|
- Evidence:
|
|
- `backend/app/main.py:37` creates `FastAPI(...)` with default docs behavior.
|
|
- README explicitly references `/docs` as available.
|
|
- Risk:
|
|
- Public endpoint inventory and schema visibility in exposed deployments.
|
|
|
|
### Medium: Bearer token is stored in browser `sessionStorage`
|
|
- Evidence:
|
|
- `frontend/src/lib/api.ts:41`, `frontend/src/lib/api.ts:61-67`, `frontend/src/lib/api.ts:84-94`
|
|
- Risk:
|
|
- Any successful frontend XSS can exfiltrate active session tokens.
|
|
|
|
### Low: Typesense transport defaults to HTTP on internal network
|
|
- Evidence:
|
|
- `docker-compose.yml:66-67`, `docker-compose.yml:107-108`
|
|
- Risk:
|
|
- Acceptable for isolated local networks, but not suitable for untrusted or cross-host network links.
|
|
|
|
## Security Controls Confirmed Present
|
|
- Login brute-force throttling with lockout and Retry-After handling.
|
|
- Owner-scoped access checks for non-admin document operations.
|
|
- Provider base URL validation with allowlist and DNS revalidation hooks.
|
|
- Upload request size and archive extraction guardrails.
|
|
- Processing log redaction and metadata-only persistence defaults.
|
|
|
|
## Coverage Limits
|
|
- No dependency CVE audit was executed in this run.
|
|
- This report is based on repository code, configuration templates, and available local tests.
|
|
|
|
## Production Decision
|
|
Current state is not production ready because of the blocking catalog discoverability exposure.
|
|
|
|
After remediating that issue, production readiness still depends on strict deployment choices for environment variables, proxy trust configuration, TLS, and frontend hardening.
|