Skip to content

✨(buildpack) add PaaS deployment support, tested with Scalingo#2293

Open
zeylos wants to merge 1 commit into
suitenumerique:mainfrom
zeylos:paas
Open

✨(buildpack) add PaaS deployment support, tested with Scalingo#2293
zeylos wants to merge 1 commit into
suitenumerique:mainfrom
zeylos:paas

Conversation

@zeylos
Copy link
Copy Markdown

@zeylos zeylos commented May 13, 2026

Replaces #1020

Tested successfuly on Scalingo

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Review Change Stack

Walkthrough

Adds Scalingo buildpack deployment support: Procfile entries, post-compile and post-frontend buildpack scripts (frontend relocation, optional SSRF-protected custom logo download/validation, theme JSON injection, asset moves), a start script launching uvicorn, y-provider, and Nginx with shutdown handling, an ERB Nginx template for proxying/API/collaboration/media with auth_request, Django DATABASE_URL support via dj-database-url, a frontend build script update, and extensive Scalingo deployment documentation and README updates.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • lunika
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding PaaS deployment support tested with Scalingo, which aligns perfectly with the changeset's primary objective.
Description check ✅ Passed The description is directly related to the changeset, explaining the purpose of the changes (creating deploy/ directory, adding documentation, following similar changes from another PR) and confirming successful testing on Scalingo.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bin/buildpack_postcompile.sh`:
- Around line 8-9: The first rm command includes the redundant path
"src/frontend/apps/e2e" which is already removed by the subsequent "rm -rf
src/frontend/apps"; delete "src/frontend/apps/e2e" from the first rm list or
remove the entire first rm line so only the "rm -rf src/frontend/apps" remains,
keeping the other entries (docker, docs, env.d, gitlint) intact.

In `@bin/buildpack_postfrontend.sh`:
- Around line 19-20: The HOSTNAME extraction and SSRF check around
THEME_CUSTOMIZATION_LOGO_URL is insufficient and can be bypassed; replace the
fragile sed-based extraction with a proper URL parser to get the scheme, host
(without user:pass), and port, then perform DNS resolution of that host and
validate the resolved IP(s) instead of just the hostname; explicitly block all
private/special ranges (IPv4: 0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8,
169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16; IPv6: ::1, fe80::/10, fc00::/7,
etc.), handle multiple A/AAAA results and deny if any resolved address is in a
disallowed range, and prefer an allowlist of trusted hosts or use a
proxy/validation service; ensure malformed URLs or ones containing credentials
are rejected up-front by the parser rather than relying on sed.
- Around line 51-56: Validate THEME_CUSTOMIZATION_JSON before writing it to
impress/configuration/theme/default.json: parse the string with a JSON validator
(e.g., jq -e . or python -c 'import json,sys;json.loads(sys.stdin.read())') and
check the exit status; only create the directory and write the file if
validation succeeds, otherwise emit an error message (include the validation
tool's error) and exit non‑zero. Ensure you reference the environment variable
THEME_CUSTOMIZATION_JSON, the destination file
impress/configuration/theme/default.json, and the validation step before the
mkdir/echo steps.
- Around line 34-41: The SVG check is too weak: stop only reading the first 100
bytes (HEADER) and replace it with a full-file safe validation of TMP_FILE—read
the entire file, normalize case and strip XML/HTML comments, then verify the
root element is an actual <svg ...> tag (not just an occurrence inside
text/comment) and that no disallowed constructs exist (no <script> tags, no on*
event attributes, and no javascript: URIs); implement these checks using
TMP_FILE and the IS_SVG flag (or a new VALID_SVG variable) and fail with the
same error message if any check fails, and document that for production you
should use a dedicated sanitizer library.

In `@bin/buildpack_start.sh`:
- Around line 3-4: The uvicorn start command currently relies on the default
port; update the uvicorn invocation (the line launching
impress.asgi:application) to explicitly set the port (use --port or -p, e.g.,
--port=8000) so the server binding is unambiguous and easier to
configure/override in deployments.
- Around line 15-18: Add simple error reporting around the wait -n/ pkill -P $$
shutdown logic: after wait -n capture the exited child's PID and exit code (use
shell builtins like $!/$? or inspect /proc if needed), log a message identifying
the PID and its exit status (e.g., "service PID X exited with code Y") and
optionally the command (from /proc/<pid>/cmdline), then run pkill -P $$ to
terminate others; ensure the script exits with the same non-zero code so callers
know which service failed.
- Around line 12-13: The SIGTERM trap uses incorrect pkill syntax and expands $$
too early; update the trap command in buildpack_start.sh so it calls pkill with
the signal flag (pkill -SIGTERM -P $$) and wrap the whole trap string in single
quotes to defer expansion until the signal is handled (i.e., change the trap
line that currently references pkill and $$ to use single-quoted 'pkill -SIGTERM
-P $$').

In `@deploy/paas/servers.conf.erb`:
- Around line 87-98: The /media-auth location is publicly exposed; change it to
an internal, exact-match authentication handler by converting the block
referenced as "location /media-auth" to use exact matching ("location =
/media-auth") and add the "internal;" directive so it can only be invoked via
auth_request; keep the existing headers and proxy_pass (proxy_pass
http://backend_server/api/v1.0/documents/media-auth/) but ensure the location is
no longer externally reachable.

In `@docs/installation/README.md`:
- Line 12: Replace the non-descriptive link text "here" with a descriptive
phrase that explains the target (e.g., "Compose installation instructions" or
"Install with Docker Compose") for the links pointing to
/docs/installation/compose.md; update both occurrences (the "here" at line 12
and the similar one at line 17) in README.md so the markdown link text is
meaningful and accessible while keeping the same URL.
- Line 15: Add a blank line above the "## Scalingo" heading in
docs/installation/README.md to satisfy markdownlint MD022; locate the heading
line containing "## Scalingo" and insert a single empty line immediately before
it so the heading is preceded by a blank line.

In `@docs/installation/scalingo.md`:
- Line 95: Replace the raw URL in the sentence "For email notifications see
https://doc.scalingo.com/platform/app/sending-emails:" with a Markdown link
(e.g., [Scalingo: Sending
emails](https://doc.scalingo.com/platform/app/sending-emails)) so the text reads
as a descriptive clickable link; update the trailing colon as needed to match
surrounding sentence punctuation.
- Around line 167-169: The one-line scalingo env-set with
THEME_CUSTOMIZATION_JSON is fragile due to shell escaping; add a safer
file-based option: instruct users to save the JSON to a file (e.g., theme.json)
and set the env var using command substitution (scalingo env-set
THEME_CUSTOMIZATION_JSON="$(cat theme.json)") so multiline/complex JSON is
preserved; mention THEME_CUSTOMIZATION_JSON and the alternative steps (create
theme.json, run scalingo env-set with cat) in the docs near the existing
example.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3f5c83d6-e0d9-4965-a864-91c94bbdf27b

📥 Commits

Reviewing files that changed from the base of the PR and between abd03d1 and 6da76d5.

⛔ Files ignored due to path filters (1)
  • src/backend/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • Procfile
  • bin/buildpack_postcompile.sh
  • bin/buildpack_postfrontend.sh
  • bin/buildpack_start.sh
  • deploy/paas/servers.conf.erb
  • docs/installation/README.md
  • docs/installation/scalingo.md
  • src/backend/impress/settings.py
  • src/backend/pyproject.toml
  • src/frontend/package.json

Comment thread bin/buildpack_postcompile.sh Outdated
Comment thread bin/buildpack_postfrontend.sh
Comment thread bin/buildpack_postfrontend.sh
Comment thread bin/buildpack_postfrontend.sh
Comment thread bin/buildpack_start.sh
Comment thread deploy/paas/servers.conf.erb
Comment thread docs/installation/README.md
Comment thread docs/installation/README.md
Comment thread docs/installation/scalingo.md Outdated
Comment thread docs/installation/scalingo.md
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/backend/impress/settings.py (1)

86-110: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Critical: Database configuration fallback logic is broken.

The conditional uses values.DatabaseURLValue(...) in a ternary expression at class definition time. This creates a Value object (which is always truthy), not the resolved environment variable value. Django-configurations only resolves Value objects during setup, after this ternary has already evaluated.

Impact: The condition is always True, so dj_database_url.config() is always called and the DB_* fallback is never used. When DATABASE_URL is not set, config() returns {}, causing Django to fail with an invalid database configuration.

The class docstring (lines 65-68) explicitly mentions DB_* variables as the expected configuration method, confirming the fallback is not just optional.

🔧 Recommended fix using post_setup
     # Database
+    DATABASE_URL = values.DatabaseURLValue(
+        None, environ_name="DATABASE_URL", environ_prefix=None
+    )
     DATABASES = {
-        "default": dj_database_url.config()
-        if values.DatabaseURLValue(
-            None, environ_name="DATABASE_URL", environ_prefix=None
-        )
-        else {
+        "default": {
             "ENGINE": values.Value(
                 "django.db.backends.postgresql",
                 environ_name="DB_ENGINE",
                 environ_prefix=None,
             ),
             "NAME": values.Value(
                 "impress", environ_name="DB_NAME", environ_prefix=None
             ),
             "USER": values.Value("dinum", environ_name="DB_USER", environ_prefix=None),
             "PASSWORD": SecretFileValue(
                 "pass", environ_name="DB_PASSWORD", environ_prefix=None
             ),
             "HOST": values.Value(
                 "localhost", environ_name="DB_HOST", environ_prefix=None
             ),
             "PORT": values.Value(5432, environ_name="DB_PORT", environ_prefix=None),
             # Psycopg pool can be configured in the post_setup method
         }
     }

Then in post_setup (around line 1136, before the Sentry block):

     `@classmethod`
     def post_setup(cls):
         """Post setup configuration.
         This is the place where you can configure settings that require other
         settings to be loaded.
         """
         super().post_setup()
 
+        # Override database config with DATABASE_URL if provided
+        if cls.DATABASE_URL:
+            cls.DATABASES["default"] = dj_database_url.config()
+
         # The SENTRY_DSN setting should be available to activate sentry for an environment
         if cls.SENTRY_DSN is not None:

Alternative simpler approach using dj-database-url's built-in default:

     # Database
     DATABASES = {
-        "default": dj_database_url.config()
-        if values.DatabaseURLValue(
-            None, environ_name="DATABASE_URL", environ_prefix=None
-        )
-        else {
+        "default": dj_database_url.config(
+            default={
                 "ENGINE": values.Value(
                     "django.db.backends.postgresql",
                     environ_name="DB_ENGINE",
                     environ_prefix=None,
                 ),
                 # ... rest of config
-        }
+            }
+        )
     }

However, this approach has a caveat: the default dict is only used when DATABASE_URL is completely absent. The values.Value objects inside it are still evaluated even when not used, so all DB_* environment variables must be valid. The post_setup approach is more robust.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/impress/settings.py` around lines 86 - 110, The DATABASES ternary
currently evaluates values.DatabaseURLValue at class-definition time (always
truthy), causing dj_database_url.config() to be called unconditionally and the
DB_* fallback to be ignored; fix by moving resolution to the class post_setup
method: in post_setup call dj_database_url.config() only after reading the
resolved DATABASE_URL from the configuration (or self.values) and if it's
empty/None, construct DATABASES from the existing values.Value/SecretFileValue
DB_* entries (or assign the dict of DB_* defaults to self.DATABASES), otherwise
set self.DATABASES to the dj_database_url.config() result; reference symbols:
DATABASES, post_setup, dj_database_url.config, values.DatabaseURLValue,
SecretFileValue.
♻️ Duplicate comments (7)
bin/buildpack_start.sh (2)

12-13: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix signal handling: incorrect pkill syntax and improper variable expansion.

Two critical issues:

  1. Incorrect pkill syntax: should be pkill -SIGTERM -P $$ (signal flag requires dash)
  2. Improper variable expansion: double quotes cause $$ to expand at trap-set time, not when SIGTERM is received

As per static analysis hint from Shellcheck, use single quotes to defer expansion until the signal is actually received.

🐛 Fix signal handling
-trap "pkill SIGTERM -P $$" SIGTERM
+trap 'pkill -SIGTERM -P $$' SIGTERM
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/buildpack_start.sh` around lines 12 - 13, The trap invocation uses
incorrect pkill syntax and expands $$ at trap-setup time; update the trap
command (the line using trap and pkill) to call pkill with the signal flag
(e.g., -SIGTERM) and ensure $$ is expanded only when the signal is delivered by
wrapping the trap string in single quotes so the PID is evaluated at signal
time; modify the trap that currently runs pkill SIGTERM -P $$ to use the correct
signal flag and single-quoted command.

3-4: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider making the port explicit for clarity.

The uvicorn command relies on the default port (8000). Making it explicit would improve clarity and prevent confusion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/buildpack_start.sh` around lines 3 - 4, The uvicorn start command
currently relies on the default port; update the startup invocation (the uvicorn
--app-dir=... --host=0.0.0.0 --timeout-graceful-shutdown=300
--limit-max-requests=20000 --lifespan=off impress.asgi:application) to
explicitly specify the port (use --port or -p with the intended port number,
e.g., --port=8000) so the bind port is clear and unambiguous when launching the
ASGI app.
deploy/paas/servers.conf.erb (1)

87-98: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make /media-auth internal-only to prevent direct external access.

The location should only be used internally via auth_request directives. Add internal; directive and use exact matching to align with Nginx best practices for authentication handlers.

🔒 Make auth endpoint internal
-    location /media-auth {
+    location = /media-auth {
+        internal;
         proxy_pass http://backend_server/api/v1.0/documents/media-auth/;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/paas/servers.conf.erb` around lines 87 - 98, The /media-auth location
must be made internal and exact-matching for auth_request usage: change the
location block identifier from "location /media-auth" to an exact match (e.g.,
"location = /media-auth") and add the "internal;" directive inside the block so
only internal requests (like auth_request) can reach the proxy_pass; keep the
existing proxy_* settings and proxy_pass target unchanged.
bin/buildpack_postfrontend.sh (3)

51-56: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Consider validating JSON structure before writing.

The script writes $THEME_CUSTOMIZATION_JSON directly without validating it's well-formed JSON. Malformed JSON may cause runtime failures with unclear error messages.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/buildpack_postfrontend.sh` around lines 51 - 56, The script writes
THEME_CUSTOMIZATION_JSON directly which may be malformed; add JSON validation
before writing by testing the variable with a JSON validator (e.g., jq -e '.' or
python -c 'import json,sys; json.load(sys.stdin)') and if validation fails, log
an error via the same prefix (e.g., "[custom-theme] ERROR: ..."), do not
create/overwrite impress/configuration/theme/default.json, and exit non-zero;
update the conditional block around THEME_CUSTOMIZATION_JSON to run the
validator, write the file only on success, and include the failing validator's
message in the error log.

34-41: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

SVG validation is weak and can be bypassed.

Even after fixing the quote issue, the validation only checks the first 100 bytes and has no XSS prevention (missing checks for <script> tags or event handlers).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/buildpack_postfrontend.sh` around lines 34 - 41, The current SVG check
using HEADER/IS_SVG and only reading head -c 100 of TMP_FILE is insufficient and
can be bypassed; update the validation in buildpack_postfrontend.sh to read and
parse the entire TMP_FILE (not just the first 100 bytes) and validate it as
well-formed XML/SVG (use a safe XML validator like xmllint or an SVG-aware
parser invoked from the script) and then explicitly scan the full content for
disallowed constructs (reject if <script> tags or any “on...” event attributes
like onclick/onload are present, and reject external entity references) so the
functions/variables HEADER, IS_SVG and TMP_FILE are replaced/augmented by a
full-file parse + explicit checks before accepting the file.

19-20: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

SSRF protection is incomplete and can be bypassed.

The current IP blocking has several gaps: missing 169.254.0.0/16, limited IPv6 coverage, missing full 0.0.0.0/8 range, and no DNS rebinding prevention. Additionally, the hostname extraction could fail for URLs with authentication credentials.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/buildpack_postfrontend.sh` around lines 19 - 20, The HOSTNAME extraction
from THEME_CUSTOMIZATION_LOGO_URL using sed and the subsequent regex (HOSTNAME
and THEME_CUSTOMIZATION_LOGO_URL) is unsafe and incomplete for SSRF protection:
replace the fragile sed extraction with a proper URL parse (strip userinfo and
require http/https scheme) and obtain the canonical host (reject URLs with
embedded credentials); then resolve the host to IP(s) and block any address in
private or non-routable ranges including 0.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12,
192.168.0.0/16, 169.254.0.0/16 (link-local), ::1 and ::/128, fc00::/7 (ULA) and
fe80::/10 (IPv6 link-local), and other non-routable blocks; also reject raw IP
literals in those ranges and implement a DNS-rebinding check by re-resolving and
re-checking IPs immediately before use; if any check fails, log "[custom-logo]
ERROR: SSRF blocked: $HOSTNAME" to stderr and exit 1 as before.
bin/buildpack_postcompile.sh (1)

8-9: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Redundant removal of src/frontend/apps/e2e.

Line 8 removes src/frontend/apps/e2e, but line 9 removes the parent directory src/frontend/apps entirely, making the first removal redundant.

♻️ Simplify by removing redundant path
-rm -rf docker docs env.d gitlint src/frontend/apps/e2e
+rm -rf docker docs env.d gitlint
 rm -rf src/frontend/apps
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/buildpack_postcompile.sh` around lines 8 - 9, Remove the redundant
deletion: the first command that runs "rm -rf docker docs env.d gitlint
src/frontend/apps/e2e" is unnecessary because the subsequent "rm -rf
src/frontend/apps" removes the parent directory; delete the earlier
"src/frontend/apps/e2e" removal (i.e., remove or simplify the first rm -rf line)
so only the broader "rm -rf src/frontend/apps" remains.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bin/buildpack_postfrontend.sh`:
- Around line 38-39: The regex checks use literal quote characters and thus
never match; update the two conditionals that set IS_SVG so they look for raw
tags instead of quoted strings: replace the pattern [[ "$HEADER" =~ ^.*"<svg".*$
]] with [[ "$HEADER" =~ <svg ]] && IS_SVG=true, and replace [[ "$HEADER" =~
^.*"<?xml".*"<svg".*$ ]] with a pattern that matches an XML prolog followed by
an svg tag (for example [[ "$HEADER" =~ \<\?xml.*<svg ]] && IS_SVG=true), so the
script correctly detects <svg and <?xml without surrounding literal quotes.

In `@deploy/paas/servers.conf.erb`:
- Around line 104-106: The location block matching UUIDs currently uses
try_files with a literal "[id]" instead of a capture; replace the try_files
usage with a rewrite that captures the UUID from the regex (use parentheses in
the pattern) and rewrites to /docs/$1/index.html, then add "break" to stop
further processing if needed; update the location block that contains the regex
and remove the incorrect try_files line, using a rewrite directive referencing
$1 so the UUID path maps to the correct index.html.

---

Outside diff comments:
In `@src/backend/impress/settings.py`:
- Around line 86-110: The DATABASES ternary currently evaluates
values.DatabaseURLValue at class-definition time (always truthy), causing
dj_database_url.config() to be called unconditionally and the DB_* fallback to
be ignored; fix by moving resolution to the class post_setup method: in
post_setup call dj_database_url.config() only after reading the resolved
DATABASE_URL from the configuration (or self.values) and if it's empty/None,
construct DATABASES from the existing values.Value/SecretFileValue DB_* entries
(or assign the dict of DB_* defaults to self.DATABASES), otherwise set
self.DATABASES to the dj_database_url.config() result; reference symbols:
DATABASES, post_setup, dj_database_url.config, values.DatabaseURLValue,
SecretFileValue.

---

Duplicate comments:
In `@bin/buildpack_postcompile.sh`:
- Around line 8-9: Remove the redundant deletion: the first command that runs
"rm -rf docker docs env.d gitlint src/frontend/apps/e2e" is unnecessary because
the subsequent "rm -rf src/frontend/apps" removes the parent directory; delete
the earlier "src/frontend/apps/e2e" removal (i.e., remove or simplify the first
rm -rf line) so only the broader "rm -rf src/frontend/apps" remains.

In `@bin/buildpack_postfrontend.sh`:
- Around line 51-56: The script writes THEME_CUSTOMIZATION_JSON directly which
may be malformed; add JSON validation before writing by testing the variable
with a JSON validator (e.g., jq -e '.' or python -c 'import json,sys;
json.load(sys.stdin)') and if validation fails, log an error via the same prefix
(e.g., "[custom-theme] ERROR: ..."), do not create/overwrite
impress/configuration/theme/default.json, and exit non-zero; update the
conditional block around THEME_CUSTOMIZATION_JSON to run the validator, write
the file only on success, and include the failing validator's message in the
error log.
- Around line 34-41: The current SVG check using HEADER/IS_SVG and only reading
head -c 100 of TMP_FILE is insufficient and can be bypassed; update the
validation in buildpack_postfrontend.sh to read and parse the entire TMP_FILE
(not just the first 100 bytes) and validate it as well-formed XML/SVG (use a
safe XML validator like xmllint or an SVG-aware parser invoked from the script)
and then explicitly scan the full content for disallowed constructs (reject if
<script> tags or any “on...” event attributes like onclick/onload are present,
and reject external entity references) so the functions/variables HEADER, IS_SVG
and TMP_FILE are replaced/augmented by a full-file parse + explicit checks
before accepting the file.
- Around line 19-20: The HOSTNAME extraction from THEME_CUSTOMIZATION_LOGO_URL
using sed and the subsequent regex (HOSTNAME and THEME_CUSTOMIZATION_LOGO_URL)
is unsafe and incomplete for SSRF protection: replace the fragile sed extraction
with a proper URL parse (strip userinfo and require http/https scheme) and
obtain the canonical host (reject URLs with embedded credentials); then resolve
the host to IP(s) and block any address in private or non-routable ranges
including 0.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16
(link-local), ::1 and ::/128, fc00::/7 (ULA) and fe80::/10 (IPv6 link-local),
and other non-routable blocks; also reject raw IP literals in those ranges and
implement a DNS-rebinding check by re-resolving and re-checking IPs immediately
before use; if any check fails, log "[custom-logo] ERROR: SSRF blocked:
$HOSTNAME" to stderr and exit 1 as before.

In `@bin/buildpack_start.sh`:
- Around line 12-13: The trap invocation uses incorrect pkill syntax and expands
$$ at trap-setup time; update the trap command (the line using trap and pkill)
to call pkill with the signal flag (e.g., -SIGTERM) and ensure $$ is expanded
only when the signal is delivered by wrapping the trap string in single quotes
so the PID is evaluated at signal time; modify the trap that currently runs
pkill SIGTERM -P $$ to use the correct signal flag and single-quoted command.
- Around line 3-4: The uvicorn start command currently relies on the default
port; update the startup invocation (the uvicorn --app-dir=... --host=0.0.0.0
--timeout-graceful-shutdown=300 --limit-max-requests=20000 --lifespan=off
impress.asgi:application) to explicitly specify the port (use --port or -p with
the intended port number, e.g., --port=8000) so the bind port is clear and
unambiguous when launching the ASGI app.

In `@deploy/paas/servers.conf.erb`:
- Around line 87-98: The /media-auth location must be made internal and
exact-matching for auth_request usage: change the location block identifier from
"location /media-auth" to an exact match (e.g., "location = /media-auth") and
add the "internal;" directive inside the block so only internal requests (like
auth_request) can reach the proxy_pass; keep the existing proxy_* settings and
proxy_pass target unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cadeebbe-95a7-46ae-b4b0-4c88c995a906

📥 Commits

Reviewing files that changed from the base of the PR and between 6da76d5 and 63dab2f.

⛔ Files ignored due to path filters (1)
  • src/backend/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • CHANGELOG.md
  • Procfile
  • bin/buildpack_postcompile.sh
  • bin/buildpack_postfrontend.sh
  • bin/buildpack_start.sh
  • deploy/paas/servers.conf.erb
  • docs/installation/README.md
  • docs/installation/scalingo.md
  • src/backend/impress/settings.py
  • src/backend/pyproject.toml
  • src/frontend/package.json

Comment thread bin/buildpack_postfrontend.sh
Comment thread deploy/paas/servers.conf.erb
add PaaS deployment scripts, tested on Scalingo
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (7)
bin/buildpack_postfrontend.sh (4)

38-39: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Regex patterns contain literal quote characters that will never match valid SVG files.

The regex checks on these lines include literal double-quote characters ("<svg" and "<?xml"), which will never match actual SVG content. Valid SVG files contain <svg or <?xml without surrounding quotes, so the validation will always fail with "not a valid SVG file" error.

🐛 Proposed fix to remove literal quotes from regex patterns
-    [[ "$HEADER" =~ ^.*"<svg".*$ ]] && IS_SVG=true
-    [[ "$HEADER" =~ ^.*"<?xml".*"<svg".*$ ]] && IS_SVG=true
+    [[ "$HEADER" =~ <svg ]] && IS_SVG=true
+    [[ "$HEADER" =~ \<\?xml.*<svg ]] && IS_SVG=true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/buildpack_postfrontend.sh` around lines 38 - 39, The regex checks for SVG
detection use literal double-quote characters and therefore never match; update
the two conditional expressions that inspect HEADER and set IS_SVG to remove the
quoted patterns so they match real tags (match <svg and <?xml followed by <svg)
— locate the conditionals that reference HEADER and IS_SVG and change the
regexes to look for <svg and <?xml.*<svg without the surrounding quote
characters so valid SVG content is correctly detected.

51-56: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Consider validating JSON structure before writing.

The script directly echoes $THEME_CUSTOMIZATION_JSON to the file without validating it's well-formed JSON. If malformed JSON is provided, the application may fail at runtime with unclear error messages.

✅ Add JSON validation
 # Inject custom theme JSON
 if [ -n "$THEME_CUSTOMIZATION_JSON" ]; then
     echo "[custom-theme] INFO: Deploying the custom theme from THEME_CUSTOMIZATION_JSON."
     mkdir -p impress/configuration/theme
-    echo "$THEME_CUSTOMIZATION_JSON" >impress/configuration/theme/default.json
+    
+    # Validate JSON before writing
+    if echo "$THEME_CUSTOMIZATION_JSON" | python -m json.tool >/dev/null 2>&1; then
+        echo "$THEME_CUSTOMIZATION_JSON" >impress/configuration/theme/default.json
+    else
+        echo "[custom-theme] ERROR: THEME_CUSTOMIZATION_JSON is not valid JSON" >&2
+        exit 1
+    fi
+    
     echo "[custom-theme] INFO: Custom theme deployed successfully."
 fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/buildpack_postfrontend.sh` around lines 51 - 56, Validate
THEME_CUSTOMIZATION_JSON before writing it to
impress/configuration/theme/default.json: parse the contents (e.g., via jq -e or
a small python -c "import json,sys; json.load(sys.stdin)") and only create the
directory and write the file if the parse succeeds; if validation fails, log an
error like "[custom-theme] ERROR: invalid JSON in THEME_CUSTOMIZATION_JSON" and
exit non‑zero. Ensure the script checks availability of the chosen validator (jq
or python) and falls back or errors clearly, and reference
THEME_CUSTOMIZATION_JSON and the target path
impress/configuration/theme/default.json when implementing the checks.

34-41: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

SVG validation is weak and can be bypassed.

The current validation only checks the first 100 bytes for an <svg substring, which can be easily bypassed:

  • Large whitespace or comments before the <svg> tag can push it beyond byte 100
  • No validation that the SVG is safe (could contain <script> tags or event handlers)

A malicious actor could upload a file with <svg in a comment within the first 100 bytes, followed by malicious content.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/buildpack_postfrontend.sh` around lines 34 - 41, The SVG validation in
buildpack_postfrontend.sh (variables TMP_FILE, HEADER, IS_SVG) is too
weak—change it to validate the entire file and to reject unsafe content: read
the full TMP_FILE rather than only the first 100 bytes, verify it is a
well-formed XML/SVG (e.g., run an XML validator like xmllint against TMP_FILE
and ensure the root element is svg), and additionally scan TMP_FILE for
disallowed constructs (e.g., <script> tags, javascript: URIs, and inline event
handlers like attributes starting with "on") and fail if any are present; update
the error messages to reflect these specific checks and remove the
HEADER/100-byte check logic.

19-20: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

SSRF protection is incomplete and can be bypassed.

The current IP blocking has several gaps:

  • Only blocks 172.16-31.* (missing 172.32-255.*)
  • Missing 169.254.0.0/16 (link-local addresses)
  • Missing IPv6 private ranges
  • DNS rebinding attacks aren't prevented (DNS resolution happens after this check)

Additionally, the hostname extraction via sed could fail for malformed URLs or URLs with authentication credentials.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/buildpack_postfrontend.sh` around lines 19 - 20, The current HOSTNAME
extraction from THEME_CUSTOMIZATION_LOGO_URL using sed and the single-regex SSRF
check is unsafe; replace it by robustly parsing THEME_CUSTOMIZATION_LOGO_URL
(reject URLs with embedded credentials or malformed schemes), extract the host
(handle IPv6 literals and brackets), then perform DNS resolution on the hostname
and validate every returned IP (and any literal IP in the URL) against a
comprehensive denylist: 0.0.0.0/8, 127.0.0.0/8, 10.0.0.0/8, 169.254.0.0/16,
172.16.0.0/12, 192.168.0.0/16, link‑local and reserved IPv6 ranges (::1,
fe80::/10, fc00::/7, ::/128, ff00::/8, etc.); if parsing, resolution, or any IP
check fails, log an error including HOSTNAME and offending IP(s) and exit
non‑zero to block the request (update the HOSTNAME assignment and the SSRF check
logic accordingly).
deploy/paas/servers.conf.erb (2)

87-98: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make /media-auth internal-only to prevent direct external access to the auth subrequest endpoint.

The location at line 87 is currently exposed publicly but should only be used internally via auth_request directives. Add the internal; directive and use exact matching (location =) to align with Nginx best practices for authentication handlers.

Suggested fix
-    location /media-auth {
+    location = /media-auth {
+        internal;
         proxy_pass http://backend_server/api/v1.0/documents/media-auth/;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/paas/servers.conf.erb` around lines 87 - 98, The /media-auth location
is publicly exposed; change its declaration from "location /media-auth" to an
exact-match internal location by replacing it with "location = /media-auth {
internal; ... }" so Nginx only allows internal auth_request subrequests to reach
the proxy_pass to backend_server/api/v1.0/documents/media-auth/; keep the
existing proxy_pass, proxy_set_header and proxy_pass_request_body/off lines
intact inside the modified block and verify any auth_request directives target
the exact "/media-auth" path.

104-106: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix broken UUID path rewrite - literal [id] is not a capture group.

The regex on line 104 matches document UUIDs, but line 105 uses the literal string [id] instead of a captured variable. This will attempt to serve a file literally named /docs/[id]/index.html, which doesn't exist.

Nginx regex captures require parentheses and $1, $2, etc. references. However, location ~ with try_files cannot reference captures. You need to use a rewrite directive instead.

🐛 Fix UUID path rewrite
     location ~ "^/docs/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$" {
-        try_files $uri /docs/[id]/index.html;
+        rewrite ^(/docs/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/?$ $1/index.html break;
+        try_files $uri =404;
     }

Alternatively, if the intent is SPA fallback for all doc UUID paths:

     location ~ "^/docs/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$" {
-        try_files $uri /docs/[id]/index.html;
+        try_files $uri $uri/index.html /index.html =404;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/paas/servers.conf.erb` around lines 104 - 106, The current location
block using "location ~
\"^/docs/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$\""
incorrectly uses try_files with a literal "[id]"; change this to capture the
UUID with parentheses (e.g. replace the bracketed pattern with a capture group)
and use a rewrite directive (not try_files) to rewrite to the captured value
like "/docs/$1/index.html" (or use rewrite ... last) because try_files cannot
reference regex captures; update the block containing the location regex and
try_files accordingly.
bin/buildpack_start.sh (1)

13-13: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix signal handling: incorrect pkill syntax and improper variable expansion.

Two critical issues with the SIGTERM handler:

  1. Incorrect pkill syntax: pkill SIGTERM -P $$ should be pkill -SIGTERM -P $$ (signal flag requires a dash).
  2. Improper variable expansion: The double quotes cause $$ to expand immediately when the trap is set, not when SIGTERM is received. Use single quotes to defer expansion.
🐛 Fix signal handling
 # if the current shell is killed, also terminate all its children
-trap "pkill SIGTERM -P $$" SIGTERM
+trap 'pkill -SIGTERM -P $$' SIGTERM
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/buildpack_start.sh` at line 13, The trap handler currently uses incorrect
pkill syntax and causes $$ to expand at trap definition time; update the SIGTERM
trap invocation (the trap command that runs pkill) to use the correct signal
flag and defer PID expansion by wrapping the handler in single quotes, e.g.
invoke pkill with -SIGTERM and pass the parent PID via $$ evaluated at signal
time (use 'pkill -SIGTERM -P $$' as the trap body) so pkill receives the proper
signal flag and the PID is expanded when the trap runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/installation/README.md`:
- Around line 15-16: Add a single blank line after the "## Scalingo" markdown
heading so the heading is separated from the following paragraph; update the
section where the heading "Scalingo" appears to insert one empty line
immediately after that header to comply with Markdown heading spacing rules.

In `@src/backend/impress/settings.py`:
- Around line 87-90: The conditional currently tests a values.DatabaseURLValue
descriptor which is always truthy, causing dj_database_url.config() to be called
even when DATABASE_URL is unset; change the logic to call
dj_database_url.config(default=None) and use the returned value (which will be
None if DATABASE_URL is not set) with the or fallback to the explicit
configuration so the fallback executes when DATABASE_URL is absent; update the
expression around dj_database_url.config() and values.DatabaseURLValue to rely
on the config() return value rather than the descriptor's truthiness.

---

Duplicate comments:
In `@bin/buildpack_postfrontend.sh`:
- Around line 38-39: The regex checks for SVG detection use literal double-quote
characters and therefore never match; update the two conditional expressions
that inspect HEADER and set IS_SVG to remove the quoted patterns so they match
real tags (match <svg and <?xml followed by <svg) — locate the conditionals that
reference HEADER and IS_SVG and change the regexes to look for <svg and
<?xml.*<svg without the surrounding quote characters so valid SVG content is
correctly detected.
- Around line 51-56: Validate THEME_CUSTOMIZATION_JSON before writing it to
impress/configuration/theme/default.json: parse the contents (e.g., via jq -e or
a small python -c "import json,sys; json.load(sys.stdin)") and only create the
directory and write the file if the parse succeeds; if validation fails, log an
error like "[custom-theme] ERROR: invalid JSON in THEME_CUSTOMIZATION_JSON" and
exit non‑zero. Ensure the script checks availability of the chosen validator (jq
or python) and falls back or errors clearly, and reference
THEME_CUSTOMIZATION_JSON and the target path
impress/configuration/theme/default.json when implementing the checks.
- Around line 34-41: The SVG validation in buildpack_postfrontend.sh (variables
TMP_FILE, HEADER, IS_SVG) is too weak—change it to validate the entire file and
to reject unsafe content: read the full TMP_FILE rather than only the first 100
bytes, verify it is a well-formed XML/SVG (e.g., run an XML validator like
xmllint against TMP_FILE and ensure the root element is svg), and additionally
scan TMP_FILE for disallowed constructs (e.g., <script> tags, javascript: URIs,
and inline event handlers like attributes starting with "on") and fail if any
are present; update the error messages to reflect these specific checks and
remove the HEADER/100-byte check logic.
- Around line 19-20: The current HOSTNAME extraction from
THEME_CUSTOMIZATION_LOGO_URL using sed and the single-regex SSRF check is
unsafe; replace it by robustly parsing THEME_CUSTOMIZATION_LOGO_URL (reject URLs
with embedded credentials or malformed schemes), extract the host (handle IPv6
literals and brackets), then perform DNS resolution on the hostname and validate
every returned IP (and any literal IP in the URL) against a comprehensive
denylist: 0.0.0.0/8, 127.0.0.0/8, 10.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12,
192.168.0.0/16, link‑local and reserved IPv6 ranges (::1, fe80::/10, fc00::/7,
::/128, ff00::/8, etc.); if parsing, resolution, or any IP check fails, log an
error including HOSTNAME and offending IP(s) and exit non‑zero to block the
request (update the HOSTNAME assignment and the SSRF check logic accordingly).

In `@bin/buildpack_start.sh`:
- Line 13: The trap handler currently uses incorrect pkill syntax and causes $$
to expand at trap definition time; update the SIGTERM trap invocation (the trap
command that runs pkill) to use the correct signal flag and defer PID expansion
by wrapping the handler in single quotes, e.g. invoke pkill with -SIGTERM and
pass the parent PID via $$ evaluated at signal time (use 'pkill -SIGTERM -P $$'
as the trap body) so pkill receives the proper signal flag and the PID is
expanded when the trap runs.

In `@deploy/paas/servers.conf.erb`:
- Around line 87-98: The /media-auth location is publicly exposed; change its
declaration from "location /media-auth" to an exact-match internal location by
replacing it with "location = /media-auth { internal; ... }" so Nginx only
allows internal auth_request subrequests to reach the proxy_pass to
backend_server/api/v1.0/documents/media-auth/; keep the existing proxy_pass,
proxy_set_header and proxy_pass_request_body/off lines intact inside the
modified block and verify any auth_request directives target the exact
"/media-auth" path.
- Around line 104-106: The current location block using "location ~
\"^/docs/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$\""
incorrectly uses try_files with a literal "[id]"; change this to capture the
UUID with parentheses (e.g. replace the bracketed pattern with a capture group)
and use a rewrite directive (not try_files) to rewrite to the captured value
like "/docs/$1/index.html" (or use rewrite ... last) because try_files cannot
reference regex captures; update the block containing the location regex and
try_files accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: aa91f6ba-215e-4eec-b80b-f7bde05eb344

📥 Commits

Reviewing files that changed from the base of the PR and between 63dab2f and 2d14677.

⛔ Files ignored due to path filters (1)
  • src/backend/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • CHANGELOG.md
  • Procfile
  • bin/buildpack_postcompile.sh
  • bin/buildpack_postfrontend.sh
  • bin/buildpack_start.sh
  • deploy/paas/servers.conf.erb
  • docs/installation/README.md
  • docs/installation/scalingo.md
  • src/backend/impress/settings.py
  • src/backend/pyproject.toml
  • src/frontend/package.json

Comment thread docs/installation/README.md
Comment thread src/backend/impress/settings.py
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.

2 participants