feat(paperless): implement Paperless-ngx proxy service and routes#362
Conversation
Add the Paperless-ngx proxy backend for EPIC-08. The server now proxies all document browsing and binary requests through /api/paperless/* endpoints, keeping the API token server-side at all times. Fixes #354 Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
steilerDev
left a comment
There was a problem hiding this comment.
[product-owner]
Requirements Coverage Review for Story 8.1 (#354)
I have systematically verified each acceptance criterion against the PR diff. The implementation is thorough and well-structured, with strong test coverage (73 tests). However, there are two gaps that need to be addressed before I can accept this story.
Acceptance Criteria Evaluation
| # | Criterion | Verdict | Notes |
|---|---|---|---|
| 1 | Accepts PAPERLESS_URL and PAPERLESS_TOKEN env vars |
PASS | Implementation uses PAPERLESS_API_TOKEN (not PAPERLESS_TOKEN), which is consistent with the architecture (ADR-015, API Contract, CLAUDE.md). The issue text used shorthand; the architecture is the source of truth. |
| 2 | Fastify plugin registers API client with token auth | PASS | paperlessService.ts uses Authorization: Token <token> header. Routes registered as a Fastify plugin via app.register(paperlessRoutes) in app.ts. |
| 3 | GET /api/paperless/status returns connection status and Paperless-ngx instance version |
PARTIAL FAIL | The endpoint correctly returns { configured, reachable, error }, which matches the API Contract. However, the acceptance criterion explicitly calls for "the Paperless-ngx instance version" in the response. The API Contract and shared types do not include a version field. See finding below. |
| 4 | GET /api/paperless/documents with search, tag filtering, and pagination |
PASS | Supports query, tags (comma-separated IDs), correspondent, documentType, page, pageSize, sortBy, sortOrder. Pagination metadata returned. |
| 5 | GET /api/paperless/documents/:id returns single document metadata |
PASS | Returns title, created date, correspondent (resolved name), document type (resolved name), tags, content. |
| 6 | GET /api/paperless/documents/:id/thumbnail proxies thumbnail image |
PASS | Implemented at /documents/:id/thumb (matching the API Contract and ADR-015 specification of thumb). Binary passthrough with content-type forwarding. |
| 7 | GET /api/paperless/documents/:id/preview proxies document preview |
PASS | Binary passthrough with content-type and content-disposition forwarding. |
| 8 | GET /api/paperless/tags returns tag list |
PASS | Returns all tags with id, name, color (mapped from Paperless colour IDs), and documentCount. |
| 9 | All proxy endpoints require authentication | PASS | Every route handler checks request.user and throws UnauthorizedError(). Integration tests verify 401 for all endpoints. |
| 10 | Not configured returns 503 PAPERLESS_NOT_CONFIGURED | PASS | requirePaperless() helper throws 503 PAPERLESS_NOT_CONFIGURED when paperlessEnabled is false. Status endpoint returns 200 with configured: false instead (correct per API Contract). |
| 11 | Unreachable/error returns 502 without leaking internals | PASS | Network errors produce PAPERLESS_UNREACHABLE (502), upstream non-ok responses produce PAPERLESS_ERROR (502). Error messages describe the failure category without exposing internal details. |
| 12 | .env.example updated with new variables |
FAIL | The .env.example file was NOT updated. Only docker-compose.yml was updated with commented examples. The criterion and the issue notes both specify .env.example should be updated. |
Findings Requiring Changes
1. (Medium) .env.example not updated
The .env.example file at the repo root has no mention of PAPERLESS_URL or PAPERLESS_API_TOKEN. Acceptance criterion 12 explicitly requires this. Please add the variables (commented out) to .env.example, similar to how the OIDC variables are documented there:
# ─── Paperless-ngx (optional) ─────────────────────────
# PAPERLESS_URL=http://paperless:8000
# PAPERLESS_API_TOKEN=your-paperless-api-token
2. (Low) Status endpoint does not return Paperless-ngx instance version
Acceptance criterion 3 states the status endpoint should return "the Paperless-ngx instance version". The current implementation returns { configured, reachable, error } with no version field. The API Contract and shared PaperlessStatusResponse type also lack a version field. This appears to be a deliberate architectural decision (the status probe hits /api/documents/?page_size=1 rather than a version-specific endpoint).
I am classifying this as low severity because the API Contract is the authoritative design, and the implementation matches it. However, the acceptance criterion text is not fully satisfied. The resolution should be one of:
- (a) Update the status endpoint to also retrieve and return the Paperless-ngx instance version (e.g., from its
/api/root endpoint), OR - (b) Update the acceptance criterion on issue #354 to remove the version requirement, acknowledging the architecture deliberately omits it.
Either path is acceptable; please pick one.
Positive Observations
- Strong test coverage: 42 unit tests + 31 integration tests covering happy paths, error paths, auth enforcement, and edge cases
- N+1 optimization for correspondent/document type resolution is well-implemented
- Binary passthrough correctly forwards content-type and content-disposition headers
- API version pinning (version=5) per ADR-015
- Clean separation between service layer and route handlers
Verdict: REQUEST CHANGES
Two items must be resolved before this story can be accepted. Finding 1 is a concrete gap (missing file update). Finding 2 needs a decision on which artifact to align (implementation or acceptance criterion).
steilerDev
left a comment
There was a problem hiding this comment.
[security-engineer]
Security Review — PR #362: Paperless-ngx Proxy Service Backend Foundation
Verdict: Request Changes — 1 Medium finding requires attention before merge. All other findings are Low/Informational.
Note: GitHub prevents self-review, so posting as a comment. The findings below carry the same weight as a formal request-changes review.
Summary
The implementation is architecturally sound from a security standpoint. The Paperless-ngx API token is properly kept server-side and never returned to the browser. All 6 endpoints enforce session authentication correctly — the preValidation hook in auth.ts enforces auth globally, and each handler also explicitly checks request.user (belt and suspenders). Query parameters are constructed via URLSearchParams, preventing URL injection. The binary passthrough path is appropriately limited.
One Medium finding requires a fix: the PAPERLESS_URL environment variable is accepted without any format or scheme validation, creating an SSRF vector. Five additional Low/Informational findings are documented below.
[MEDIUM] SSRF via Unvalidated PAPERLESS_URL Environment Variable
OWASP Category: A10 — Server-Side Request Forgery (SSRF)
Severity: Medium
Description:
PAPERLESS_URL is accepted as a raw string with no URL validation in config.ts. Any string value (including internal network addresses, file paths, or scheme variants like file://, ftp://, or http://169.254.169.254/) is accepted without check and later concatenated directly into all upstream fetch URLs:
// paperlessService.ts
response = await fetch(`${baseUrl}${path}`, { headers: makeHeaders(token) });This means an operator with .env file access can redirect all proxy requests to internal metadata services, cloud IMDS endpoints, or other internal services. In a typical self-hosted Docker Compose setup this is an operator-level concern (not an end-user concern), but it still represents an uncontrolled SSRF surface that should be bounded.
Affected Files:
server/src/plugins/config.ts:108—paperlessUrlaccepted without URL validationserver/src/services/paperlessService.ts:91—fetch(`${baseUrl}${path}`, ...)— unvalidated base URL
Proof of Concept:
Set PAPERLESS_URL=http://169.254.169.254/latest/meta-data/ in .env. Any authenticated Cornerstone user triggering GET /api/paperless/status will cause the server to fetch from the AWS IMDS endpoint. The response is not returned to the user but proves the SSRF path. Setting PAPERLESS_URL=file:///etc/passwd would depend on Node.js fetch behavior (likely rejected, but not guaranteed across runtimes).
Remediation:
Add URL validation in loadConfig using the built-in URL constructor with an allowlist of permitted schemes:
// In loadConfig():
if (paperlessUrl) {
try {
const parsed = new URL(paperlessUrl);
if (!['http:', 'https:'].includes(parsed.protocol)) {
errors.push(`PAPERLESS_URL must use http or https scheme, got: ${parsed.protocol}`);
}
} catch {
errors.push(`PAPERLESS_URL is not a valid URL: ${paperlessUrl}`);
}
}This ensures only http:// and https:// URLs are accepted at startup, preventing file/ftp/data scheme SSRF and malformed URL injection.
Risk if Unaddressed:
An operator with .env write access could redirect proxy traffic to internal network services. In this self-hosted deployment model the attacker must already have server access, which limits blast radius significantly — but this finding still violates SSRF hygiene and should be fixed.
[LOW] getStatus Error Field Leaks Internal Connectivity Errors to Authenticated Clients
OWASP Category: A05 — Security Misconfiguration (information exposure)
Description:
When Paperless-ngx is configured but unreachable, getStatus() returns the raw exception message in the error field of the 200 response:
// paperlessService.ts:262-263
const message = err instanceof Error ? err.message : String(err);
return { configured: true, reachable: false, error: message };This means network-level error strings like ECONNREFUSED connect ECONNREFUSED 10.0.0.5:8000, DNS resolution failures, or TLS certificate errors (which may include internal hostnames or IP addresses) are returned in the API response body to any authenticated user.
Affected Files:
server/src/services/paperlessService.ts:262-263
Remediation:
Sanitize or categorize the error before returning it. The simplest approach is to return a fixed string like "Cannot connect to Paperless-ngx" without the internal network detail. Alternatively, document this as accepted risk — in a self-hosted admin tool, surfacing connectivity details to authenticated users is a reasonable UX tradeoff.
[LOW] Binary Content-Type Passthrough Allows Arbitrary MIME Types
OWASP Category: A05 — Security Misconfiguration
Severity: Low
Description:
The thumb and preview endpoints forward the upstream content-type header from Paperless-ngx without any validation:
// paperless.ts:167, 204
const contentType = upstream.headers.get('content-type') ?? 'image/webp';
reply.header('content-type', contentType);If Paperless-ngx returns an unexpected content-type (e.g., text/html, application/javascript, or image/svg+xml), Cornerstone will faithfully forward it. An image/svg+xml document containing embedded <script> tags could be executed by the browser if rendered inline, which would constitute a stored XSS vector depending on how the frontend displays the preview.
Affected Files:
server/src/routes/paperless.ts:165-173— thumb endpoint, unvalidated content-type passthroughserver/src/routes/paperless.ts:202-210— preview endpoint, unvalidated content-type passthrough
Remediation:
Allowlist the permitted content types and override to a safe fallback if the upstream returns something unexpected:
const ALLOWED_THUMB_TYPES = new Set(['image/webp', 'image/jpeg', 'image/png', 'image/gif']);
const ALLOWED_PREVIEW_TYPES = new Set(['application/pdf', 'image/tiff', 'image/jpeg', 'image/png']);
// For thumb:
const upstreamType = upstream.headers.get('content-type') ?? '';
const contentType = ALLOWED_THUMB_TYPES.has(upstreamType) ? upstreamType : 'image/webp';
// For preview:
const upstreamType = upstream.headers.get('content-type') ?? '';
const contentType = ALLOWED_PREVIEW_TYPES.has(upstreamType) ? upstreamType : 'application/pdf';Additionally, set X-Content-Type-Options: nosniff on binary responses (this is also an open general finding for the application).
Risk if Unaddressed:
Low in practice — Paperless-ngx is a trusted backend. If the Paperless instance were compromised or misconfigured, a malicious SVG served through the thumb endpoint could execute JavaScript in the Cornerstone frontend's origin.
[LOW] tags Query Parameter Lacks Format Validation
OWASP Category: A03 — Injection (low severity)
Severity: Low
Description:
The tags query parameter is typed as { type: 'string', maxLength: 200 }. Any string up to 200 characters is accepted and forwarded to Paperless-ngx. URLSearchParams.set() correctly URL-encodes the value, so there is no HTTP header injection risk. However, non-numeric values are forwarded to Paperless-ngx and surface as PAPERLESS_ERROR 502, which may expose information about the upstream API error message.
Affected Files:
server/src/routes/paperless.ts:27—tags: { type: 'string', maxLength: 200 }lacks pattern validation
Remediation:
Add a pattern constraint to enforce the expected comma-separated integer format:
tags: { type: 'string', maxLength: 200, pattern: '^\\d+(,\\d+)*$' },[INFORMATIONAL] Positive Finding: Token Isolation
The Paperless-ngx API token is correctly kept server-side at all times. It is:
- Not returned in any API response body
- Excluded from the startup config log (
config.ts:144-161) - Not passed through route handlers to the frontend
- Stored only in environment variables, not in the database
This is the correct implementation pattern. No action needed.
[INFORMATIONAL] Positive Finding: URL Parameter Construction
All Paperless-ngx query strings are built using URLSearchParams, which correctly percent-encodes all values. The sortBy field is passed through a sortFieldMap allowlist before use. The id path parameter is validated as an integer by the AJV schema before it reaches the service. These patterns collectively prevent URL injection against the Paperless-ngx upstream.
Review Checklist
- No SQL/command injection vectors in new code
- Authentication enforced on all 6 new endpoints
- No Paperless API token exposed in responses, logs, or errors
- User input validated with AJV schemas on document list endpoint
- Integer type enforcement on
:idpath parameter - No new dependencies introduced (uses Node.js built-in fetch)
- No hardcoded credentials or secrets
- PAPERLESS_URL not validated for scheme/format — MEDIUM finding above
- Binary content-type not allowlisted — LOW finding above
-
tagsparameter has no format constraint — LOW finding above
steilerDev
left a comment
There was a problem hiding this comment.
[product-architect]
Architecture Review: Story 8.1 — Paperless-ngx Proxy Service
Summary
This PR implements the Paperless-ngx proxy backend foundation as specified in the EPIC-08 API contract and ADR-015. I have verified the implementation against the wiki API Contract, ADR-015, existing route/service patterns, shared types, and the config plugin. All CI checks are passing.
API Contract Compliance
All 6 proxy endpoints match the wiki API Contract specification:
| Endpoint | Contract Match | Notes |
|---|---|---|
GET /api/paperless/status |
Yes | Always returns 200 OK with {configured, reachable, error} shape |
GET /api/paperless/documents |
Yes | Pagination, filtering (query, tags, correspondent, documentType), sorting all match contract |
GET /api/paperless/documents/:id |
Yes | Returns {document: PaperlessDocument} with full content |
GET /api/paperless/documents/:id/thumb |
Yes | Binary passthrough with forwarded content-type, default image/webp |
GET /api/paperless/documents/:id/preview |
Yes | Binary passthrough with forwarded content-type, default application/pdf |
GET /api/paperless/tags |
Yes | Returns {tags: PaperlessTag[]} sorted by ID |
Error codes match the contract: PAPERLESS_NOT_CONFIGURED (503), PAPERLESS_UNREACHABLE (502), PAPERLESS_ERROR (502), NOT_FOUND (404). The status endpoint correctly returns 200 in all cases (not 503 when unconfigured), per the contract.
ADR-015 Compliance
- Server-side proxy pattern: Confirmed. API token stays server-side; browser never sees it.
- API version 5: Confirmed.
Accept: application/json; version=5header is set viamakeHeaders(). ThefetchBinary()function correctly omits the Accept header for binary requests. - Environment variable configuration: Confirmed.
PAPERLESS_URLandPAPERLESS_API_TOKENare read by the config plugin;paperlessEnabledderived flag is used in routes. - No caching (Phase 1): Confirmed. Every request makes a live upstream call.
- Curated endpoint subset: Confirmed. Only the 5 proxy endpoints + status are exposed.
Architecture & Pattern Compliance
- Route structure: Follows the established Fastify plugin pattern (register with prefix in
app.ts). - Authentication: Uses
if (!request.user) throw new UnauthorizedError()consistent with all other routes. - Error handling: Uses
AppErrorwith typed error codes registered in@cornerstone/shared. Error codes are registered inshared/src/types/errors.ts. - Service layer separation: Clean separation between
paperlessService.ts(HTTP client logic) andpaperless.ts(route handlers). The service is pure (no Fastify dependency), routes are thin wrappers. - Config plugin integration:
paperlessEnabled,paperlessUrl,paperlessApiTokenproperly declared inAppConfiginterface and config plugin. - Shared types: All response types (
PaperlessStatusResponse,PaperlessDocumentListResponse,PaperlessDocumentDetailResponse,PaperlessTagListResponse) are correctly imported from@cornerstone/shared. - N+1 optimization: Unique correspondent and document type IDs are collected and resolved once per list request, with parallel
Promise.allresolution.
Test Coverage
73 tests total: 42 unit tests + 31 integration tests. Coverage appears comprehensive:
- All 6 endpoints tested for auth (401), not-configured (503), success (200), and upstream errors (502)
- Binary endpoints tested for content-type forwarding and fallback defaults
- Service tests cover: network errors, 404 mapping, non-ok responses, pagination, sorting, filtering, search hits, N+1 optimization, tag color mapping, null/minimal document fields
expect.assertions()used correctly in try/catch error assertion tests
Code Quality
- TypeScript strict mode compatible; no
anytypes - ESM imports with
.jsextensions throughout - Proper use of
typeimports (import type) - camelCase for TypeScript, snake_case for Paperless-ngx raw interfaces
- Fastify JSON schema validation on query parameters and URL params
additionalProperties: falseon querystring schema prevents unexpected parameters
Finding (Low Severity)
Duplicate ListDocumentsQuery interface in service: paperlessService.ts (line 270) defines a local ListDocumentsQuery interface that duplicates PaperlessDocumentListQuery from @cornerstone/shared. The local version has a wider sortBy: string type vs. the shared type's strict union. The service should import and use PaperlessDocumentListQuery from @cornerstone/shared instead to maintain a single source of truth. This is non-blocking since the route-level Fastify schema validation already constrains the values, but it should be cleaned up in a follow-up commit.
Verdict
Approved. The implementation is well-structured, fully compliant with the API contract and ADR-015, follows established patterns, and has thorough test coverage. The duplicate interface finding is low severity and can be addressed in a follow-up.
- config.ts: validate PAPERLESS_URL scheme (http/https only) to prevent SSRF attacks via file://, ftp://, and other non-HTTP schemes - paperless.ts: add MIME type allowlist for thumb/preview binary endpoints; disallowed upstream content-types fall back to application/octet-stream - paperless.ts: add pattern constraint '^\\d+(,\\d+)*$' to tags query param to enforce comma-separated integer format - paperlessService.ts: sanitize IP addresses and hostname:port from error messages in getStatus() to prevent information disclosure - paperlessService.ts: remove duplicate local ListDocumentsQuery interface; use shared PaperlessDocumentListQuery instead - .env.example: add PAPERLESS_URL and PAPERLESS_API_TOKEN (commented out) - Tests updated: config.test.ts, paperless.test.ts, paperlessService.test.ts cover all new validation logic, sanitization, and allowlist behavior Fixes #354 Co-Authored-By: Claude dev-team-lead (Sonnet 4.6) <noreply@anthropic.com>
steilerDev
left a comment
There was a problem hiding this comment.
[product-owner]
Re-Review (Round 2) -- Story 8.1 (#354)
Note: GitHub prevents self-review approval. This comment carries the same weight as a formal approval.
Both round 1 findings have been addressed.
Finding 1 (Medium): .env.example not updated -- RESOLVED
Commit 31921e6 adds a Paperless-ngx section to .env.example with PAPERLESS_URL and PAPERLESS_API_TOKEN commented out. The format matches the existing OIDC variable section. Acceptance criterion 12 is now satisfied.
Finding 2 (Low): Status endpoint missing version -- ACCEPTED
The API Contract and shared PaperlessStatusResponse type deliberately omit a version field. The implementation returns { configured, reachable, error }, which matches the architectural design exactly. Since the API Contract is the source of truth and the omission is intentional (the status probe hits /api/documents/?page_size=1 rather than a version endpoint), I accept this as a deliberate scope decision. I will update the acceptance criterion text on issue #354 to align with the actual design.
Acceptance Criteria Summary
| # | Criterion | Verdict |
|---|---|---|
| 1 | PAPERLESS_URL and PAPERLESS_API_TOKEN env vars |
PASS |
| 2 | Fastify plugin with token auth | PASS |
| 3 | GET /api/paperless/status returns connection status |
PASS |
| 4 | GET /api/paperless/documents with search/filter/pagination |
PASS |
| 5 | GET /api/paperless/documents/:id returns document metadata |
PASS |
| 6 | GET /api/paperless/documents/:id/thumb proxies thumbnail |
PASS |
| 7 | GET /api/paperless/documents/:id/preview proxies preview |
PASS |
| 8 | GET /api/paperless/tags returns tag list |
PASS |
| 9 | All proxy endpoints require authentication | PASS |
| 10 | Not configured returns 503 PAPERLESS_NOT_CONFIGURED |
PASS |
| 11 | Unreachable returns 502 without leaking internals | PASS |
| 12 | .env.example updated |
PASS |
Additional Observations
The fix commit also addresses security findings (SSRF prevention via URL scheme validation, MIME type allowlisting, tags parameter pattern validation, error message sanitization) and an architecture finding (duplicate ListDocumentsQuery interface removed). These improvements strengthen the implementation beyond the original acceptance criteria.
CI checks (Quality Gates, Docker, E2E Smoke Tests) are all passing.
Verdict: APPROVED -- All 12 acceptance criteria are satisfied. Story 8.1 is accepted.
steilerDev
left a comment
There was a problem hiding this comment.
[security-engineer] Round 2 remediation verification — all four findings from the initial review are correctly resolved.
Verification Summary
[MEDIUM] SSRF via Unvalidated PAPERLESS_URL — RESOLVED
Fix location: server/src/plugins/config.ts (lines 138–157 in the diff)
The implementation is correct and complete:
new URL(paperlessUrlRaw)correctly rejects malformed URLs (e.g.,not-a-url) with a caughtTypeError- The
allowedSchemes = ['http:', 'https:']set rejectsfile:,ftp:, and any other non-HTTP scheme - The protocol is stripped of its trailing colon in the error message (
parsed.protocol.replace(':', '')) — consistent with test expectations - Validation errors feed into the existing
errors[]accumulator, so startup fails fast with all config errors collected - 7 tests cover: http acceptance, https acceptance, file:// rejection, ftp:// rejection, invalid URL rejection, and disabled-when-partial-config behavior
No issues found.
[LOW] Binary Content-Type Passthrough — RESOLVED
Fix location: server/src/routes/paperless.ts (BINARY_CONTENT_TYPE_ALLOWLIST + sanitizeBinaryContentType())
The implementation is correct:
- Allowlist:
{ image/webp, image/png, image/jpeg, image/gif, application/pdf }— covers all expected Paperless-ngx output types contentType.split(';')[0].trim().toLowerCase()correctly strips MIME parameters before matching (e.g.,image/webp; charset=utf-8→image/webp)- Disallowed types (including
text/html,image/svg+xml,application/javascript) fall back toapplication/octet-stream— a safe, non-executable MIME type - Applied consistently to both
/thumb(fallback:image/webp) and/preview(fallback:application/pdf) content-dispositionis forwarded as-is, which is acceptable — Node.js rejects CRLF header injection at the transport layer, and Paperless-ngx is a trusted configured upstream
No issues found.
[LOW] getStatus Error Information Disclosure — RESOLVED
Fix location: server/src/services/paperlessService.ts (sanitizeErrorMessage())
The implementation is correct:
- Regex 1: IPv4 addresses with optional port (
\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}(?::\d+)?) →<host> - Regex 2:
hostname:portpatterns ([a-zA-Z0-9](?:[a-zA-Z0-9\-._]*[a-zA-Z0-9])?:\d{1,5}) →<host> - Verified that HTTP status text like
"returned 502: Bad Gateway"is NOT incorrectly matched — the regex requires the colon to be directly adjacent to the hostname token, not preceded by a space - Error type information (e.g.,
ECONNREFUSED,ENOTFOUND) is preserved after sanitization - IPv6 addresses are not fully sanitized (e.g.,
::1would not be caught), but this is an edge case with no practical impact in typical Docker network configurations - Tests confirm IP address redaction, hostname:port redaction, and preservation of the error type
No new issues introduced.
[LOW] tags Parameter Missing Format Validation — RESOLVED
Fix location: server/src/routes/paperless.ts (listDocumentsSchema)
tags: { type: 'string', maxLength: 200, pattern: '^\\d+(,\\d+)*$' },The pattern is correct:
^\d+$— single integer^(\d+)(,\d+)*$— comma-separated integers with no trailing comma- Rejects whitespace, letters, SQL fragments, and other injection attempts at the Fastify schema layer (400 before reaching service code)
- Tests confirm rejection of
abc,1 OR 1=1(URL-decoded), and acceptance of5,12,20
No issues found.
Security Checklist
- No SQL/command/XSS injection vectors in new code
- Authentication enforced on all new endpoints (explicit
!request.userchecks on all 6 routes) - No sensitive data exposed in logs, errors, or client responses (API token flows server-side only; status errors sanitized)
- User input validated and sanitized at API boundaries (schema-layer validation,
URLSearchParamsencoding for query forwarding) - No new dependencies with known CVEs
- No hardcoded credentials or secrets
- CORS configuration unchanged
- Error responses do not leak internal details (schema errors are by design; proxy errors are now sanitized)
All four round-1 findings are correctly and thoroughly remediated. The Security Audit wiki page has been updated to mark all four findings as Resolved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
🎉 This PR is included in version 1.10.0-beta.72 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.11.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
paperlessService.ts— HTTP client service that proxies all requests to Paperless-ngx, resolves tag/correspondent/document type IDs to names, and handles binary passthrough for thumbnails and previewspaperless.tsroute plugin — 6 authenticated endpoints under/api/paperless/*(status, document list/detail, thumb, preview, tags) with 503 when not configured and 502 on upstream errorsapp.tsand add commented Paperless env var examples todocker-compose.ymlFixes #354
Implementation Details
fetch(no additional HTTP client dependency)Accept: application/json; version=5per ADR-015arrayBuffer()and forwardscontent-typeandcontent-dispositionheaders from upstreamTest plan
paperlessService.test.ts)paperless.test.ts)PAPERLESS_NOT_CONFIGUREDwhen env vars missingCo-Authored-By: Claude dev-team-lead (Sonnet 4.6) noreply@anthropic.com