Skip to content

update Dockerfile and middleware for crypt4gh - #33

Open
vschnei wants to merge 3 commits into
mainfrom
update_middleware
Open

update Dockerfile and middleware for crypt4gh#33
vschnei wants to merge 3 commits into
mainfrom
update_middleware

Conversation

@vschnei

@vschnei vschnei commented Jan 5, 2026

Copy link
Copy Markdown

Description

This PR fixes issues with the Crypt4GH middleware that prevented it from working correctly in environments with read-only container mounts.

Changes Made

Dockerfile:

  • Changed base image from python:3.12 to lvarin/crypt4gh:1.6 for crypt4gh support
  • Updated working directory

decrypt.py:

  • Added .c4gh file extension removal after successful decryption
  • Implemented fallback to copy2 when move fails on read-only filesystems
  • Added file permissions (0644 for files, 0755 for directories)
  • Skip non-.c4gh files during decryption process
  • Fixed rm -R command (was incorrectly using -P flag)

middleware.py:

  • Inherit from AbstractMiddleware for integration
  • Filter inputs to only process .c4gh files

Dependencies:

  • Updated to Poetry 2.2.1 format. Poetry 2.x introduces the groups field for dependency organization.
  • Updated poetry.lock with new dependency groups format

Related Issues

Fixes #32

Summary by Sourcery

Update Crypt4GH middleware and container image to work correctly with read-only mounts and ensure decrypted outputs are usable by downstream components.

New Features:

  • Expose TES service URLs from application config into requests handled by the Crypt4GH middleware.

Bug Fixes:

  • Fix Crypt4GH decryption to skip non-.c4gh inputs, remove the .c4gh extension on output, and log failures more clearly.
  • Handle read-only source paths during file moves by falling back to copying files when moves fail.
  • Correct file removal to use recursive deletion flags appropriate for directories instead of the previous incorrect option.
  • Ensure decrypted output directory and files have appropriate permissions for access by subsequent containers.
  • Restrict Crypt4GH middleware to only track and process .c4gh input paths, avoiding unintended files.

Enhancements:

  • Refine Crypt4GH middleware to inherit from the shared AbstractMiddleware base for better integration with the surrounding framework.

Build:

  • Switch the container base image to lvarin/crypt4gh:1.6 and simplify the Dockerfile working directory setup.

Chores:

  • Add and update Poetry dependency metadata, including the packaging dependency and updated lockfile format.

…nce decryption logic, and adjust permissions
@sourcery-ai

sourcery-ai Bot commented Jan 5, 2026

Copy link
Copy Markdown

Reviewer's Guide

Updates the Crypt4GH middleware to operate correctly with read-only container mounts by switching to a crypt4gh-enabled base image, hardening file handling and permissions in the decryption workflow, and integrating the middleware with the AbstractMiddleware interface while filtering to .c4gh inputs and updating dependency management to Poetry 2.x format.

Sequence diagram for CryptMiddleware apply_middleware flow with .c4gh filtering

sequenceDiagram
    actor Client
    participant FlaskApp
    participant CryptMiddleware
    participant Config as FocaConfig

    Client->>FlaskApp: HTTP request with JSON inputs
    FlaskApp->>CryptMiddleware: apply_middleware(request)

    CryptMiddleware->>CryptMiddleware: _set_original_input_paths(request)
    loop for each input_body in request.json[inputs]
        CryptMiddleware->>CryptMiddleware: validate path not starting with VOLUME_PATH
        alt path endswith .c4gh
            CryptMiddleware->>CryptMiddleware: append path to original_input_paths
        else non .c4gh input
            CryptMiddleware->>CryptMiddleware: skip path
        end
    end

    CryptMiddleware->>CryptMiddleware: _change_executor_paths(request)
    CryptMiddleware->>CryptMiddleware: _add_volume(request)
    CryptMiddleware->>CryptMiddleware: _add_decryption_executor(request)

    FlaskApp->>Config: read foca.custom.tes.service_list
    Config-->>FlaskApp: tes service urls
    FlaskApp->>CryptMiddleware: pass tes service urls
    CryptMiddleware->>CryptMiddleware: tes_urls = unique list of urls
    CryptMiddleware->>FlaskApp: set request.json[tes_urls]

    CryptMiddleware-->>FlaskApp: modified request
    FlaskApp-->>Client: response (after downstream processing)
Loading

Class diagram for updated CryptMiddleware integration

classDiagram
    class AbstractMiddleware {
    }

    class CryptMiddleware {
        - list original_input_paths
        - list tes_urls
        + CryptMiddleware()
        + apply_middleware(request)
        - _set_original_input_paths(request)
        - _change_executor_paths(request)
        - _add_volume(request)
        - _add_decryption_executor(request)
    }

    CryptMiddleware --|> AbstractMiddleware
Loading

File-Level Changes

Change Details Files
Harden decryption workflow to support read-only mounts and improve output handling.
  • Skip non-.c4gh files when decrypting to avoid unnecessary errors and processing.
  • Use NamedTemporaryFile with delete disabled, flush the decrypted data, then move it to a new file path without the .c4gh extension.
  • Adjust move_files to fall back to shutil.copy2 when shutil.move fails (e.g., read-only source paths) and log the behavior.
  • Fix remove_files to call rm -R instead of the incorrect -P flag when deleting directory contents.
  • After decryption, set directory permissions to 0755 and file permissions to 0644 for all outputs, and add explicit success/error logging around the decryption process.
crypt4gh_middleware/decrypt.py
Integrate Crypt4GH middleware with the common AbstractMiddleware interface and limit processing to .c4gh inputs.
  • Change CryptMiddleware to inherit from AbstractMiddleware to align with the pro_tes middleware integration model.
  • In _set_original_input_paths, guard against missing path keys, disallow VOLUME_PATH in inputs, and only store input paths that end with .c4gh.
  • In apply_middleware, ensure TES URLs are deep-copied from configuration, deduplicated, stored on the instance, and injected into the request JSON.
crypt4gh_middleware/middleware.py
Update container runtime to use a crypt4gh-capable base image and adjust filesystem layout.
  • Change Docker base image from python:3.12 to lvarin/crypt4gh:1.6 so crypt4gh tooling is available by default.
  • Set the working directory to /home and remove the manual requirements installation that is now handled by the new base image or build process.
Dockerfile
Align project dependencies with Poetry 2.2.1 and add required runtime packaging dependency.
  • Add packaging>=24.2 to the main dependencies to satisfy runtime requirements (likely for version or environment handling).
  • Regenerate poetry.lock in the Poetry 2.x format with dependency groups to reflect the new configuration.
pyproject.toml
poetry.lock

Assessment against linked issues

Issue Objective Addressed Explanation
#32 Ensure Crypt4GH decryption works when input files are on read-only mounts and decrypted files are written to a writable location.
#32 Process only .c4gh input files and remove the .c4gh extension from decrypted output files.
#32 Set correct permissions on decrypted outputs (0644 for files and 0755 for directories) so that subsequent containers can read them.

Possibly linked issues

  • update crypt4GH decryption #32: PR directly addresses all described Crypt4GH issues: read-only mounts, .c4gh filtering, extension removal, and permissions.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@vschnei
vschnei requested a review from uniqueg January 5, 2026 10:08
@vschnei vschnei self-assigned this Jan 5, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In decrypt_files, NamedTemporaryFile(delete=False) combined with multiple exception paths means temporary files are never cleaned up on failure (e.g., ValueError), so consider wrapping the move/decrypt in a try/finally that unlinks f_out.name on error.
  • In _set_original_input_paths, path = input_body.get("path") can be None, which will cause path.startswith(...) and str(path).lower().endswith(...) to fail; add a type/None check (or default to empty string) before calling string methods.
  • The updated Dockerfile switches to lvarin/crypt4gh:1.6 but no longer copies or installs this project’s code into the image, so the built container will not contain or run the middleware unless you reintroduce the appropriate COPY/install steps.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `decrypt_files`, `NamedTemporaryFile(delete=False)` combined with multiple exception paths means temporary files are never cleaned up on failure (e.g., `ValueError`), so consider wrapping the move/decrypt in a `try/finally` that unlinks `f_out.name` on error.
- In `_set_original_input_paths`, `path = input_body.get("path")` can be `None`, which will cause `path.startswith(...)` and `str(path).lower().endswith(...)` to fail; add a type/None check (or default to empty string) before calling string methods.
- The updated `Dockerfile` switches to `lvarin/crypt4gh:1.6` but no longer copies or installs this project’s code into the image, so the built container will not contain or run the middleware unless you reintroduce the appropriate `COPY`/install steps.

## Individual Comments

### Comment 1
<location> `crypt4gh_middleware/decrypt.py:62` </location>
<code_context>
-        with open(file_path, "rb") as f_in, NamedTemporaryFile() as f_out:
+        if not file_path.name.endswith('.c4gh'):
+            continue
+        with open(file_path, "rb") as f_in, NamedTemporaryFile(delete=False) as f_out:
             try:
                 decrypt(keys=key_tuples, infile=f_in, outfile=f_out)  # Checks for magic
</code_context>

<issue_to_address>
**issue (bug_risk):** Temporary files are never cleaned up on decryption failure when using `delete=False`.

Using `NamedTemporaryFile(delete=False)` means any exception in `decrypt()` will leave the temp file on disk. Please either (a) wrap decrypt + move in try/finally and unlink `f_out.name` when the move doesn’t occur, or (b) keep `delete=True` and reopen the temp path for the move, so we don’t accumulate orphaned temp files in long‑running processes.
</issue_to_address>

### Comment 2
<location> `crypt4gh_middleware/decrypt.py:120` </location>
<code_context>
         raise ValueError(f"Could not remove files: {directory} is not a directory.")
     for file in directory.iterdir():
-        subprocess.run(["rm", "-P", str(file)], check=True)
+        subprocess.run(["rm", "-R", str(file)], check=True)
         logger.debug(f"Removed {file.name}")

</code_context>

<issue_to_address>
**🚨 issue (security):** Switching from `rm -P` to `rm -R` removes the secure-wipe behavior and conflicts with the function description.

The docstring still says files are "rewritten" before removal, but `rm -R` only deletes recursively and does not securely overwrite data like `rm -P`. If secure deletion is required, this is a security regression. Either restore secure wiping (e.g., overwrite files while still handling directories recursively) or update the docstring and function naming/description to remove the secure-wipe guarantee.
</issue_to_address>

### Comment 3
<location> `crypt4gh_middleware/middleware.py:80-81` </location>
<code_context>
         """
         for input_body in request.json["inputs"]:
-            if input_body["path"].startswith(VOLUME_PATH):
+            path = input_body.get("path")
+            if path.startswith(VOLUME_PATH):
                 raise PathNotAllowedException(f"{VOLUME_PATH} is not allowed in input path.")
-            self.original_input_paths.append(input_body["path"])
</code_context>

<issue_to_address>
**issue:** `input_body.get("path")` can be `None`, causing an `AttributeError` on `.startswith`.

Using `get()` means `path` can now be `None`, so `path.startswith(...)` will fail if the key is missing. Either ensure the payload always includes a non-empty `"path"` (and document/enforce that), or guard against `None` before calling `.startswith`. Reverting to direct indexing is also fine if the key is truly mandatory.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread crypt4gh_middleware/decrypt.py
Comment thread crypt4gh_middleware/decrypt.py Outdated
Comment thread crypt4gh_middleware/middleware.py
@vschnei vschnei changed the title feat: update Dockerfile and middleware for crypt4gh update Dockerfile and middleware for crypt4gh Jan 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

update crypt4GH decryption

1 participant