mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-11-10 16:31:05 +00:00
Fix: Blacklist certain OCR user arguments that paperless itself controls/uses
This commit is contained in:
1 parent
a139bdff46
commit
bf106498cf
6 files changed
+89
-1
No files matched your search
@@ -1157,6 +1157,9 @@ they use underscores instead of dashes.
|
||||
be changed by trusted users. This applies to the `AppConfig` permission as well,
|
||||
which allows setting these arguments from the UI.
|
||||
|
||||
Arguments paperless manages itself (`input_file_or_options`, `output_file`
|
||||
and `sidecar`) are rejected by the API and ignored if set here.
|
||||
|
||||
Specify arguments as a JSON dictionary. Keep note of lower case
|
||||
booleans and double quoted parameter names and strings. Examples:
|
||||
|
||||
|
||||
@@ -287,6 +287,28 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(config.user_args, user_args)
|
||||
self.assertEqual(config.barcode_tag_mapping, barcode_tag_mapping)
|
||||
|
||||
def test_api_update_config_rejects_reserved_user_args(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- API request to update app config with user_args which set an
|
||||
argument the parser manages itself
|
||||
WHEN:
|
||||
- API is called
|
||||
THEN:
|
||||
- Request is rejected with a 400 naming the argument
|
||||
- Config is not updated
|
||||
"""
|
||||
response = self.client.patch(
|
||||
f"{self.ENDPOINT}1/",
|
||||
json.dumps({"user_args": json.dumps({"sidecar": "/tmp/x.py"})}),
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn("sidecar", str(response.data["user_args"]))
|
||||
config = ApplicationConfiguration.objects.first()
|
||||
assert config is not None
|
||||
self.assertIsNone(config.user_args)
|
||||
|
||||
def test_api_update_config_rejects_invalid_json_objects(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
|
||||
@@ -12,6 +12,17 @@ from paperless.models import ModeChoices
|
||||
from paperless.models import OutputTypeChoices
|
||||
from paperless.models import RemoteOCRMode
|
||||
|
||||
# OCRmyPDF arguments the parser sets itself, pointing into its own temporary
|
||||
# directory. The parser reads these files back after OCR, so user supplied
|
||||
# values can only break consumption (or write to arbitrary paths), never help.
|
||||
OCR_RESERVED_USER_ARGS: frozenset[str] = frozenset(
|
||||
{
|
||||
"input_file_or_options",
|
||||
"output_file",
|
||||
"sidecar",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class BaseConfig:
|
||||
|
||||
@@ -21,6 +21,7 @@ from documents.parsers import make_thumbnail_from_pdf
|
||||
from documents.utils import copy_file_with_basic_stats
|
||||
from documents.utils import maybe_override_pixel_limit
|
||||
from documents.utils import remove_alpha_channel
|
||||
from paperless.config import OCR_RESERVED_USER_ARGS
|
||||
from paperless.config import OcrConfig
|
||||
from paperless.models import CleanChoices
|
||||
from paperless.models import ModeChoices
|
||||
@@ -351,7 +352,22 @@ class RasterisedDocumentParser:
|
||||
|
||||
if self.settings.user_args is not None:
|
||||
try:
|
||||
ocrmypdf_args = {**ocrmypdf_args, **self.settings.user_args}
|
||||
reserved = sorted(
|
||||
OCR_RESERVED_USER_ARGS & self.settings.user_args.keys(),
|
||||
)
|
||||
if reserved:
|
||||
self.log.warning(
|
||||
f"Ignoring OCR user arguments managed by paperless: "
|
||||
f"{', '.join(reserved)}",
|
||||
)
|
||||
ocrmypdf_args = {
|
||||
**ocrmypdf_args,
|
||||
**{
|
||||
k: v
|
||||
for k, v in self.settings.user_args.items()
|
||||
if k not in OCR_RESERVED_USER_ARGS
|
||||
},
|
||||
}
|
||||
except Exception as e:
|
||||
self.log.warning(
|
||||
f"There is an issue with PAPERLESS_OCR_USER_ARGS, so "
|
||||
|
||||
@@ -19,6 +19,7 @@ from PIL import Image
|
||||
from rest_framework import serializers
|
||||
from rest_framework.authtoken.serializers import AuthTokenSerializer
|
||||
|
||||
from paperless.config import OCR_RESERVED_USER_ARGS
|
||||
from paperless.models import ApplicationConfiguration
|
||||
from paperless.network import validate_outbound_http_url
|
||||
from paperless.validators import reject_dangerous_svg
|
||||
@@ -251,6 +252,13 @@ class ApplicationConfigurationSerializer(
|
||||
|
||||
def validate_user_args(self, value):
|
||||
self._require_json_object("user_args", value)
|
||||
if value is not None:
|
||||
reserved = sorted(OCR_RESERVED_USER_ARGS & value.keys())
|
||||
if reserved:
|
||||
raise serializers.ValidationError(
|
||||
f"user_args may not set arguments managed by paperless: "
|
||||
f"{', '.join(reserved)}.",
|
||||
)
|
||||
return value
|
||||
|
||||
def validate_barcode_tag_mapping(self, value):
|
||||
|
||||
@@ -243,6 +243,34 @@ class TestParserSettingsFromDb(DirectoriesMixin, FileSystemAssertsMixin, TestCas
|
||||
"UseDeviceIndependentColor",
|
||||
)
|
||||
|
||||
def test_ocr_user_args_cannot_override_reserved(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- User arguments which attempt to override arguments the parser
|
||||
manages itself, alongside a legitimate argument
|
||||
WHEN:
|
||||
- OCR parameters are constructed
|
||||
THEN:
|
||||
- Reserved arguments keep the parser supplied values
|
||||
- The legitimate argument is still applied
|
||||
"""
|
||||
instance = ApplicationConfiguration.objects.all().first()
|
||||
assert instance is not None
|
||||
instance.user_args = {
|
||||
"sidecar": "/tmp/evil.py",
|
||||
"output_file": "/tmp/out.pdf",
|
||||
"input_file_or_options": "/etc/passwd",
|
||||
"unpaper_args": "--pre-rotate 90",
|
||||
}
|
||||
instance.save()
|
||||
|
||||
params = self.get_params()
|
||||
|
||||
self.assertEqual(params["sidecar"], "sidecar.txt")
|
||||
self.assertEqual(params["output_file"], "output.pdf")
|
||||
self.assertEqual(params["input_file_or_options"], "input.pdf")
|
||||
self.assertEqual(params["unpaper_args"], "--pre-rotate 90")
|
||||
|
||||
def test_ocr_user_args(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
|
||||
Reference in new issue
Block a user