Merge pull request #131 from Zeecka/sentry_workers

fix 109
This commit is contained in:
Zeecka
2026-01-03 00:55:51 +01:00
committed by GitHub
5 changed files with 106 additions and 99 deletions
+1
View File
@@ -161,6 +161,7 @@ class SubprocessAnalyzer(ABC):
self.update_result(result) self.update_result(result)
except Exception as e: except Exception as e:
self.update_result({"status": "error", "error": str(e)}) self.update_result({"status": "error", "error": str(e)})
raise
def is_error(self, returncode: int, stdout: str, stderr: str, zip_exist: bool) -> bool: def is_error(self, returncode: int, stdout: str, stderr: str, zip_exist: bool) -> bool:
"""Check if the result is an error.""" """Check if the result is an error."""
+54 -57
View File
@@ -20,73 +20,70 @@ class DecomposerAnalyzer(SubprocessAnalyzer):
def get_results(self, _: Optional[str] = None) -> dict[str, Any]: def get_results(self, _: Optional[str] = None) -> dict[str, Any]:
"""Analyze an image submission using bits decomposition.""" """Analyze an image submission using bits decomposition."""
try: img = Image.open(self.input_img)
img = Image.open(self.input_img) converted = False
converted = False
if img.mode == "P": if img.mode == "P":
img = img.convert("RGB") img = img.convert("RGB")
converted = True converted = True
img_np = np.array(img) img_np = np.array(img)
# Handle grayscale, RGB, RGBA, etc. # Handle grayscale, RGB, RGBA, etc.
if len(img_np.shape) == 2: if len(img_np.shape) == 2:
channels = 1 channels = 1
else: else:
channels = img_np.shape[2] channels = img_np.shape[2]
if channels > 1: if channels > 1:
channel_names = ["Red", "Green", "Blue", "Alpha"] channel_names = ["Red", "Green", "Blue", "Alpha"]
else: else:
channel_names = ["Grayscale"] channel_names = ["Grayscale"]
image_json = {} image_json = {}
# Add Superimposed RGB bit planes # Add Superimposed RGB bit planes
if channels >= 3: if channels >= 3:
superimposed_json = [] superimposed_json = []
for bit in range(8): for bit in range(8):
bit_mask = 1 << bit bit_mask = 1 << bit
rgb_planes = [((img_np[..., c] & bit_mask) >> bit) * 255 for c in range(3)] rgb_planes = [((img_np[..., c] & bit_mask) >> bit) * 255 for c in range(3)]
rgb_image = np.stack(rgb_planes, axis=-1).astype(np.uint8) rgb_image = np.stack(rgb_planes, axis=-1).astype(np.uint8)
rgb_img = Image.fromarray(rgb_image, mode="RGB") rgb_img = Image.fromarray(rgb_image, mode="RGB")
img_name = f"superimposed_bit_{bit}.png" img_name = f"superimposed_bit_{bit}.png"
out_path = self.output_dir / img_name out_path = self.output_dir / img_name
dl_path = Path(self.output_dir.name) / img_name dl_path = Path(self.output_dir.name) / img_name
superimposed_json.append("/image/" + str(dl_path)) superimposed_json.append("/image/" + str(dl_path))
rgb_img.save(out_path) rgb_img.save(out_path)
image_json["Superimposed"] = superimposed_json image_json["Superimposed"] = superimposed_json
# Process individual channels # Process individual channels
for c in range(channels): for c in range(channels):
channel_data = img_np[..., c] if channels > 1 else img_np channel_data = img_np[..., c] if channels > 1 else img_np
channel_label = channel_names[c] channel_label = channel_names[c]
channel_json = [] channel_json = []
for bit in range(8): for bit in range(8):
bit_mask = 1 << bit bit_mask = 1 << bit
bit_plane = ((channel_data & bit_mask) >> bit) * 255 bit_plane = ((channel_data & bit_mask) >> bit) * 255
bit_img = Image.fromarray(bit_plane.astype(np.uint8), mode="L") bit_img = Image.fromarray(bit_plane.astype(np.uint8), mode="L")
img_name = f"{channel_label}_bit_{bit}.png" img_name = f"{channel_label}_bit_{bit}.png"
out_path = self.output_dir / img_name out_path = self.output_dir / img_name
dl_path = Path(self.output_dir.name) / img_name dl_path = Path(self.output_dir.name) / img_name
channel_json.append("/image/" + str(dl_path)) channel_json.append("/image/" + str(dl_path))
bit_img.save(out_path) bit_img.save(out_path)
image_json[channel_label] = channel_json image_json[channel_label] = channel_json
output = { output = {
"status": "ok", "status": "ok",
"images": image_json, "images": image_json,
} }
if converted: if converted:
output["note"] = ( output["note"] = (
"Image contains a color palette and was converted to RGB for processing." "Image contains a color palette and was converted to RGB for processing."
) )
return output return output
except Exception as e:
return {"status": "error", "error": str(e)}
def analyze_decomposer(input_img: Path, output_dir: Path) -> None: def analyze_decomposer(input_img: Path, output_dir: Path) -> None:
+33 -39
View File
@@ -115,53 +115,47 @@ class ResizeAnalyzer(SubprocessAnalyzer):
""" """
logs = [] logs = []
try: with self.input_img.open("rb") as image:
with self.input_img.open("rb") as image: img_bytes = bytearray(image.read())
img_bytes = bytearray(image.read()) try:
try: self.png = PNG.from_bytearray(img_bytes)
self.png = PNG.from_bytearray(img_bytes) except ValueError:
except ValueError:
return {
"status": "error",
"error": "IHDR chunk is not the first chunk, or PNG has invalid structure.",
}
# Already correct
if self.png.crc == self.png.compute_crc():
return {
"status": "ok",
"note": f"PNG is already valid with dimensions "
f"{self.png.width}x{self.png.height} and crc 0x{self.png.crc:08x}.",
}
saved_img_urls = []
# No match found
if not (db_matches := self._lookup_crc()) and not (
db_matches := self._search_height_crc()
):
return { return {
"status": "error", "status": "error",
"error": "Failure: No matching dimensions found.", "error": "IHDR chunk is not the first chunk, or PNG has invalid structure.",
} }
# Match found, saving image(s) # Already correct
logs.append(f"Target CRC found: 0x{self.png.crc:08x}") if self.png.crc == self.png.compute_crc():
self.output_dir.mkdir(parents=True, exist_ok=True)
for png_match in db_matches:
img_name = self._write_recovered_png(img_bytes, png_match)
logs.append(f"Image saved: {img_name}")
# Append the formatted URL to our list
saved_img_urls.append("/image/" + str(Path(self.output_dir.name) / img_name))
return { return {
"status": "ok", "status": "ok",
"output": logs, "note": f"PNG is already valid with dimensions "
"png_images": saved_img_urls, f"{self.png.width}x{self.png.height} and crc 0x{self.png.crc:08x}.",
} }
except Exception as e: saved_img_urls = []
return {"status": "error", "error": str(e)}
# No match found
if not (db_matches := self._lookup_crc()) and not (db_matches := self._search_height_crc()):
return {
"status": "error",
"error": "Failure: No matching dimensions found.",
}
# Match found, saving image(s)
logs.append(f"Target CRC found: 0x{self.png.crc:08x}")
self.output_dir.mkdir(parents=True, exist_ok=True)
for png_match in db_matches:
img_name = self._write_recovered_png(img_bytes, png_match)
logs.append(f"Image saved: {img_name}")
# Append the formatted URL to our list
saved_img_urls.append("/image/" + str(Path(self.output_dir.name) / img_name))
return {
"status": "ok",
"output": logs,
"png_images": saved_img_urls,
}
def analyze_image_resize(input_img: Path, output_dir: Path) -> None: def analyze_image_resize(input_img: Path, output_dir: Path) -> None:
+1
View File
@@ -44,6 +44,7 @@ class OpenStegoAnalyzer(SubprocessAnalyzer):
self.update_result(result) self.update_result(result)
except Exception as e: except Exception as e:
self.update_result({"status": "error", "error": str(e)}) self.update_result({"status": "error", "error": str(e)})
raise
def process_output(self, stdout: str, stderr: str) -> str | list[str] | dict[str, str]: def process_output(self, stdout: str, stderr: str) -> str | list[str] | dict[str, str]:
"""Process the stdout/stderr.""" """Process the stdout/stderr."""
+17 -3
View File
@@ -77,12 +77,26 @@ def analyze_image(submission_hash: str) -> None:
def run_analyzer(analyzer_func: Any, *args: Any) -> None: def run_analyzer(analyzer_func: Any, *args: Any) -> None:
"""Run an analyzer function in a separate thread.""" """Run an analyzer function in a separate thread."""
analyzer_name = analyzer_func.__name__.replace("analyze_", "")
try: try:
analyzer_func(*args) analyzer_func(*args)
except Exception as e: except Exception as e:
sentry_sdk.capture_exception(e) print(f"Error in {analyzer_name}: {e}")
print(f"Error in {analyzer_func.__name__}: {e}") with sentry_sdk.push_scope() as scope:
sentry_sdk.capture_exception(e) scope.set_tag("analyzer", analyzer_name)
scope.set_tag("submission_hash", submission_hash)
scope.set_context(
"analyzer_info",
{
"tool": analyzer_name,
"image_path": str(img_path),
"result_path": str(result_path),
"filename": submission.filename,
"deep_analysis": submission.deep_analysis,
},
)
scope.fingerprint = ["analyzer-error", analyzer_name]
sentry_sdk.capture_exception(e)
analyzers = [ analyzers = [
(analyze_binwalk, img_path, result_path), (analyze_binwalk, img_path, result_path),