Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
4d8652d
Add files via upload
Kosztyk Dec 2, 2025
a30e543
Add files via upload
Kosztyk Dec 2, 2025
e67c1b3
Add files via upload
Kosztyk Dec 2, 2025
4c28300
Configure ClamAV REST API and change port mapping
Kosztyk Dec 2, 2025
4697b15
Add files via upload
Kosztyk Dec 4, 2025
718f87e
Add files via upload
Kosztyk Dec 4, 2025
179abac
Add files via upload
Kosztyk Dec 4, 2025
6b2c737
Add files via upload
Kosztyk Dec 4, 2025
48d1ce5
Add files via upload
Kosztyk Dec 4, 2025
f2f48c9
Add files via upload
Kosztyk Dec 4, 2025
d9bb395
Add files via upload
Kosztyk Dec 4, 2025
dff8926
Add files via upload
Kosztyk Dec 4, 2025
e629fca
Add files via upload
Kosztyk Dec 4, 2025
5519374
Add files via upload
Kosztyk Dec 4, 2025
6fe7899
Add files via upload
Kosztyk Dec 4, 2025
d4be44c
Add files via upload
Kosztyk Dec 4, 2025
3420464
Add files via upload
Kosztyk Dec 4, 2025
8ec2a22
Update package.json
Kosztyk Dec 4, 2025
55fa5ae
Add files via upload
Kosztyk Dec 4, 2025
4fd9862
Add files via upload
Kosztyk Dec 4, 2025
1cdbd9e
Add files via upload
Kosztyk Dec 4, 2025
60d4a48
Update README.md
Kosztyk Dec 5, 2025
97ddf01
Add files via upload
Kosztyk Dec 8, 2025
96e7123
Add files via upload
Kosztyk Dec 8, 2025
5083935
Add files via upload
Kosztyk Dec 8, 2025
3df62fc
Update package.json
Kosztyk Dec 26, 2025
09e0e84
Merge branch 'main' into main
Kosztyk Dec 26, 2025
9c30944
Merge branch 'main' into main
Kosztyk Dec 27, 2025
61894a7
Merge branch 'C4illin:main' into main
Kosztyk Jan 4, 2026
da67096
Merge branch 'main' into main
Kosztyk Jan 5, 2026
5e19e84
Add files via upload
Kosztyk Jan 5, 2026
2d5c8a0
Add files via upload
Kosztyk Jan 5, 2026
e4624a8
Delete src/helpers/erugo.ts.crdownload
Kosztyk Jan 5, 2026
3bcb511
Delete src/helpers/env.ts.crdownload
Kosztyk Jan 5, 2026
dbd1805
Add files via upload
Kosztyk Jan 5, 2026
86e67d4
Add files via upload
Kosztyk Jan 5, 2026
8eda62f
Add files via upload
Kosztyk Jan 5, 2026
2d954a4
Add files via upload
Kosztyk Jan 5, 2026
a5fbe90
Change resetAntivirusEnabledToDefault to private function
Kosztyk Jan 11, 2026
ddb7292
Merge pull request #1 from C4illin/main
Kosztyk Jan 11, 2026
928655c
Update script.js
Kosztyk Jan 11, 2026
58fafe8
Update theme-init.js
Kosztyk Jan 11, 2026
089bc8e
Update avToggle.ts
Kosztyk Jan 11, 2026
1c8e084
Refactor JSON parsing and share URL extraction
Kosztyk Jan 11, 2026
89c5b04
Update results.tsx
Kosztyk Jan 11, 2026
9cd47ce
Refactor ResultsArticle to handle optional num_files
Kosztyk Jan 11, 2026
5b25ed8
Refactor sendFileToErugo function and types
Kosztyk Jan 11, 2026
5a0f024
Fix download attributes in results.tsx
Kosztyk Jan 11, 2026
f3a40ea
Refactor button and link attributes for accessibility
Kosztyk Jan 11, 2026
51e4911
Add files via upload
Kosztyk Jan 11, 2026
2075e51
Add files via upload
Kosztyk Jan 11, 2026
fdb0c0d
Add files via upload
Kosztyk Jan 11, 2026
2b7491a
Update results.tsx
Kosztyk Jan 12, 2026
3e03af3
Add files via upload
Kosztyk Jan 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,32 @@ services:
# - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
- TZ=Europe/Stockholm # set your timezone, defaults to UTC
# - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 2, 2025

Choose a reason for hiding this comment

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

P1: Convertx injects CLAMAV_URL pointing to a non-existent Docker hostname (clam_av_api), so every server-side ClamAV fetch fails and uploads are never actually scanned, bypassing the antivirus workflow.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 19:

<comment>Convertx injects `CLAMAV_URL` pointing to a non-existent Docker hostname (`clam_av_api`), so every server-side ClamAV fetch fails and uploads are never actually scanned, bypassing the antivirus workflow.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
       - TZ=Europe/Stockholm # set your timezone, defaults to UTC
       # - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
+      - CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
     ports:
-      - 3000:3000
</file context>
Suggested change
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
+ - CLAMAV_URL=http://clamav-rest-api:3000/api/v1/scan
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P1: Service hostname mismatch: clam_av_api should be clamav-rest-api to match the actual service name defined in this compose file. Docker Compose uses service names for internal DNS resolution.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 19:

<comment>Service hostname mismatch: `clam_av_api` should be `clamav-rest-api` to match the actual service name defined in this compose file. Docker Compose uses service names for internal DNS resolution.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
       - TZ=Europe/Stockholm # set your timezone, defaults to UTC
       # - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
+      - CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
     ports:
-      - 3000:3000
</file context>
Suggested change
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
- CLAMAV_URL=http://clamav-rest-api:3000/api/v1/scan
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P1: Hostname mismatch: clam_av_api should be clamav-rest-api to match the service name. Docker Compose uses service names as DNS hostnames within the network.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 19:

<comment>Hostname mismatch: `clam_av_api` should be `clamav-rest-api` to match the service name. Docker Compose uses service names as DNS hostnames within the network.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
       - TZ=Europe/Stockholm # set your timezone, defaults to UTC
       # - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
+      - CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
     ports:
-      - 3000:3000
</file context>
Suggested change
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
- CLAMAV_URL=http://clamav-rest-api:3000/api/v1/scan
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P1: Service name mismatch: clam_av_api doesn't match the defined service name clamav-rest-api. Docker Compose uses service names for DNS resolution, so this URL will fail to resolve.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 19:

<comment>Service name mismatch: `clam_av_api` doesn&#39;t match the defined service name `clamav-rest-api`. Docker Compose uses service names for DNS resolution, so this URL will fail to resolve.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
       - TZ=Europe/Stockholm # set your timezone, defaults to UTC
       # - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
+      - CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
     ports:
-      - 3000:3000
</file context>
Suggested change
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
- CLAMAV_URL=http://clamav-rest-api:3000/api/v1/scan
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P1: Hostname mismatch: clam_av_api does not match the service name clamav-rest-api. Docker Compose uses service names as DNS hostnames, so this URL will fail to resolve.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 19:

<comment>Hostname mismatch: `clam_av_api` does not match the service name `clamav-rest-api`. Docker Compose uses service names as DNS hostnames, so this URL will fail to resolve.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
       - TZ=Europe/Stockholm # set your timezone, defaults to UTC
       # - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
+      - CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
     ports:
-      - 3000:3000
</file context>
Suggested change
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
- CLAMAV_URL=http://clamav-rest-api:3000/api/v1/scan
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P1: Service name mismatch: CLAMAV_URL references clam_av_api but the service is named clamav-rest-api. The antivirus scanning will fail to connect. Use http://clamav-rest-api:3000/api/v1/scan instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 19:

<comment>Service name mismatch: `CLAMAV_URL` references `clam_av_api` but the service is named `clamav-rest-api`. The antivirus scanning will fail to connect. Use `http://clamav-rest-api:3000/api/v1/scan` instead.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
       - TZ=Europe/Stockholm # set your timezone, defaults to UTC
       # - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
+      - CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
     ports:
-      - 3000:3000
</file context>
Suggested change
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
- CLAMAV_URL=http://clamav-rest-api:3000/api/v1/scan
Fix with Cubic

ports:
- 3000:3000
- 8080:3000

clamav-rest-api:
image: benzino77/clamav-rest-api:latest
container_name: clamav-rest-api
restart: unless-stopped
environment:
- NODE_ENV=production
# field name expected in the multipart form
- APP_FORM_KEY=FILES
# talk to your existing ClamAV daemon
- CLAMD_IP=CLAMAV_server_IP
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 2, 2025

Choose a reason for hiding this comment

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

P1: CLAMD_IP is left as the placeholder CLAMAV_server_IP even though the ClamAV daemon service defined below is named clamav. The REST API container will fail to connect to the daemon, so scans will always fail.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 32:

<comment>`CLAMD_IP` is left as the placeholder `CLAMAV_server_IP` even though the ClamAV daemon service defined below is named `clamav`. The REST API container will fail to connect to the daemon, so scans will always fail.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # field name expected in the multipart form
+      - APP_FORM_KEY=FILES
+      # talk to your existing ClamAV daemon
+      - CLAMD_IP=CLAMAV_server_IP
+      - CLAMD_PORT=3310
+      # max allowed file size (here: 250 MB)
</file context>
Suggested change
- CLAMD_IP=CLAMAV_server_IP
- CLAMD_IP=clamav
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P1: Placeholder value not configured: CLAMAV_server_IP should be clamav to reference the ClamAV daemon service defined in this compose file. Docker Compose provides DNS resolution using service names.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 32:

<comment>Placeholder value not configured: `CLAMAV_server_IP` should be `clamav` to reference the ClamAV daemon service defined in this compose file. Docker Compose provides DNS resolution using service names.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # field name expected in the multipart form
+      - APP_FORM_KEY=FILES
+      # talk to your existing ClamAV daemon
+      - CLAMD_IP=CLAMAV_server_IP
+      - CLAMD_PORT=3310
+      # max allowed file size (here: 250 MB)
</file context>
Suggested change
- CLAMD_IP=CLAMAV_server_IP
- CLAMD_IP=clamav
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P1: Placeholder value: CLAMAV_server_IP should be clamav to reference the ClamAV daemon service defined in this compose file.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 32:

<comment>Placeholder value: `CLAMAV_server_IP` should be `clamav` to reference the ClamAV daemon service defined in this compose file.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # field name expected in the multipart form
+      - APP_FORM_KEY=FILES
+      # talk to your existing ClamAV daemon
+      - CLAMD_IP=CLAMAV_server_IP
+      - CLAMD_PORT=3310
+      # max allowed file size (here: 250 MB)
</file context>
Suggested change
- CLAMD_IP=CLAMAV_server_IP
- CLAMD_IP=clamav
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P1: Placeholder value CLAMAV_server_IP needs to be replaced with the actual ClamAV service name. Since the clamav service is in the same compose file, use clamav as the hostname.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 32:

<comment>Placeholder value `CLAMAV_server_IP` needs to be replaced with the actual ClamAV service name. Since the `clamav` service is in the same compose file, use `clamav` as the hostname.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # field name expected in the multipart form
+      - APP_FORM_KEY=FILES
+      # talk to your existing ClamAV daemon
+      - CLAMD_IP=CLAMAV_server_IP
+      - CLAMD_PORT=3310
+      # max allowed file size (here: 250 MB)
</file context>
Suggested change
- CLAMD_IP=CLAMAV_server_IP
- CLAMD_IP=clamav
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P1: Placeholder value not replaced: CLAMAV_server_IP should be clamav to reference the ClamAV daemon service defined in the same compose file.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 32:

<comment>Placeholder value not replaced: `CLAMAV_server_IP` should be `clamav` to reference the ClamAV daemon service defined in the same compose file.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # field name expected in the multipart form
+      - APP_FORM_KEY=FILES
+      # talk to your existing ClamAV daemon
+      - CLAMD_IP=CLAMAV_server_IP
+      - CLAMD_PORT=3310
+      # max allowed file size (here: 250 MB)
</file context>
Suggested change
- CLAMD_IP=CLAMAV_server_IP
- CLAMD_IP=clamav
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P1: Placeholder value not replaced: CLAMD_IP=CLAMAV_server_IP should be CLAMD_IP=clamav to reference the clamav service defined in this compose file. The REST API won't be able to connect to the ClamAV daemon.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 32:

<comment>Placeholder value not replaced: `CLAMD_IP=CLAMAV_server_IP` should be `CLAMD_IP=clamav` to reference the clamav service defined in this compose file. The REST API won&#39;t be able to connect to the ClamAV daemon.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # field name expected in the multipart form
+      - APP_FORM_KEY=FILES
+      # talk to your existing ClamAV daemon
+      - CLAMD_IP=CLAMAV_server_IP
+      - CLAMD_PORT=3310
+      # max allowed file size (here: 250 MB)
</file context>
Suggested change
- CLAMD_IP=CLAMAV_server_IP
- CLAMD_IP=clamav
Fix with Cubic

- CLAMD_PORT=3310
# max allowed file size (here: 250 MB)
- APP_MAX_FILE_SIZE=262144000
ports:
# outside:inside
- "3000:3000"

clamav:
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P0: YAML indentation error: The clamav service has 3 spaces of indentation instead of 2, which will cause a YAML parsing error and prevent the compose file from being loaded.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 40:

<comment>YAML indentation error: The `clamav` service has 3 spaces of indentation instead of 2, which will cause a YAML parsing error and prevent the compose file from being loaded.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # outside:inside
+      - &quot;3000:3000&quot;
+
+   clamav:
+    image: clamav/clamav:latest
+    container_name: clamav
</file context>
Suggested change
clamav:
clamav:
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P0: YAML syntax error: The clamav: service has 3 spaces indentation instead of 2 spaces like other services. This will cause a parsing error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 40:

<comment>YAML syntax error: The `clamav:` service has 3 spaces indentation instead of 2 spaces like other services. This will cause a parsing error.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # outside:inside
+      - &quot;3000:3000&quot;
+
+   clamav:
+    image: clamav/clamav:latest
+    container_name: clamav
</file context>
Suggested change
clamav:
clamav:
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P0: YAML indentation error: clamav: has 3 spaces instead of 2. This will cause a YAML parsing error and prevent Docker Compose from running.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 40:

<comment>YAML indentation error: `clamav:` has 3 spaces instead of 2. This will cause a YAML parsing error and prevent Docker Compose from running.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # outside:inside
+      - &quot;3000:3000&quot;
+
+   clamav:
+    image: clamav/clamav:latest
+    container_name: clamav
</file context>
Suggested change
clamav:
clamav:
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P0: YAML indentation error: This line has 3 spaces instead of 2, which breaks YAML parsing. Service definitions must be consistently indented.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 40:

<comment>YAML indentation error: This line has 3 spaces instead of 2, which breaks YAML parsing. Service definitions must be consistently indented.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # outside:inside
+      - &quot;3000:3000&quot;
+
+   clamav:
+    image: clamav/clamav:latest
+    container_name: clamav
</file context>
Suggested change
clamav:
clamav:
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P0: YAML indentation error: clamav service has 3 spaces instead of 2. This will cause YAML parsing to fail or produce unexpected structure.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 40:

<comment>YAML indentation error: `clamav` service has 3 spaces instead of 2. This will cause YAML parsing to fail or produce unexpected structure.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # outside:inside
+      - &quot;3000:3000&quot;
+
+   clamav:
+    image: clamav/clamav:latest
+    container_name: clamav
</file context>
Suggested change
clamav:
clamav:
Fix with Cubic

image: clamav/clamav:latest
container_name: clamav
restart: unless-stopped
ports:
- "3310:3310"
environment:
- CLAMAV_NO_FRESHCLAMD=false
121 changes: 102 additions & 19 deletions public/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ dropZone.addEventListener("drop", (e) => {
}
});

// Extracted handleFile function for reusability in drag-and-drop and file input
function handleFile(file) {
const fileList = document.querySelector("#file-list");

Expand Down Expand Up @@ -128,14 +127,11 @@ const updateSearchBar = () => {
});

convertToInput.addEventListener("search", () => {
// when the user clears the search bar using the 'x' button
convertButton.disabled = true;
formatSelected = false;
});

convertToInput.addEventListener("blur", (e) => {
// Keep the popup open even when clicking on a target button
// for a split second to allow the click to go through
if (e?.relatedTarget?.classList?.contains("target")) {
convertToPopup.classList.add("hidden");
convertToPopup.classList.remove("flex");
Expand All @@ -152,7 +148,6 @@ const updateSearchBar = () => {
});
};

// Add a 'change' event listener to the file input element
fileInput.addEventListener("change", (e) => {
const files = e.target.files;
for (const file of files) {
Expand All @@ -165,21 +160,19 @@ const setTitle = () => {
title.textContent = `Convert ${fileType ? `.${fileType}` : ""}`;
};

// Add a onclick for the delete button
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const deleteRow = (target) => {
const filename = target.parentElement.parentElement.children[0].textContent;
const row = target.parentElement.parentElement;
row.remove();

// remove from fileNames
const index = fileNames.indexOf(filename);
fileNames.splice(index, 1);
if (index !== -1) {
fileNames.splice(index, 1);
}

// reset fileInput
fileInput.value = "";

// if fileNames is empty, reset fileType
if (fileNames.length === 0) {
fileType = null;
fileInput.removeAttribute("accept");
Expand Down Expand Up @@ -209,20 +202,104 @@ const uploadFile = (file) => {
xhr.open("POST", `${webroot}/upload`, true);

xhr.onload = () => {
let data = JSON.parse(xhr.responseText);

pendingFiles -= 1;

// 🔍 1) Log exactly what the browser got
console.log("Upload raw response:", xhr.status, xhr.responseText);

let data = {};
try {
data = JSON.parse(xhr.responseText || "{}");
} catch (e) {
console.error("Failed to parse upload response:", e, xhr.responseText);
}

// 🔍 2) Compute an "infected" flag as robustly as possible
const isInfected =
(typeof data === "object" &&
data !== null &&
(data.infected === true ||
data.infected === "true" ||
(typeof data.message === "string" &&
data.message.toLowerCase().includes("infected file found")))) ||
(typeof xhr.responseText === "string" &&
xhr.responseText.toLowerCase().includes("infected file found"));

// 🔴 3) If backend reports infection, show popup and stop
if (xhr.status >= 200 && xhr.status < 300 && isInfected) {
const infectedFiles = data.infectedFiles || [];
const details = infectedFiles
.map((f) =>
`${f.name}: ${
Array.isArray(f.viruses) && f.viruses.length
? f.viruses.join(", ")
: "malware detected"
}`,
)
.join("\n");

alert(
"⚠️ Infected file found. Conversion will be aborted.\n\n" +
(details ? "Details:\n" + details : ""),
);

// Remove row for this file
if (file.htmlRow && file.htmlRow.remove) {
file.htmlRow.remove();
}

// Remove from internal list
const idx = fileNames.indexOf(file.name);
if (idx !== -1) {
fileNames.splice(idx, 1);
}

if (fileNames.length === 0) {
fileType = null;
fileInput.removeAttribute("accept");
setTitle();
convertButton.disabled = true;
} else if (pendingFiles === 0 && formatSelected) {
convertButton.disabled = false;
}

convertButton.textContent = "Convert";

const progressbar = file.htmlRow?.getElementsByTagName("progress");
if (progressbar && progressbar[0]?.parentElement) {
progressbar[0].parentElement.remove();
}

return;
}

// Generic HTTP error
if (xhr.status !== 200) {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P2: Failed upload doesn't remove file from fileNames array or UI. This leaves inconsistent state where user could attempt to convert a file that failed to upload.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At public/script.js, line 277:

<comment>Failed upload doesn&#39;t remove file from `fileNames` array or UI. This leaves inconsistent state where user could attempt to convert a file that failed to upload.</comment>

<file context>
@@ -209,20 +202,104 @@ const uploadFile = (file) =&gt; {
+    }
+
+    // Generic HTTP error
+    if (xhr.status !== 200) {
+      console.error(&quot;Upload failed:&quot;, xhr.status, xhr.responseText);
+      alert(&quot;Upload failed. Please try again.&quot;);
</file context>
Fix with Cubic

console.error("Upload failed:", xhr.status, xhr.responseText);
alert("Upload failed. Please try again.");
convertButton.disabled = false;
convertButton.textContent = "Upload failed";

const progressbar = file.htmlRow.getElementsByTagName("progress");
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P2: Generic HTTP error handler doesn't remove the file from fileNames array or remove the row from the DOM. This leaves orphaned file entries in the UI that weren't actually uploaded.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At public/script.js, line 463:

<comment>Generic HTTP error handler doesn&#39;t remove the file from `fileNames` array or remove the row from the DOM. This leaves orphaned file entries in the UI that weren&#39;t actually uploaded.</comment>

<file context>
@@ -209,20 +382,104 @@ const uploadFile = (file) =&gt; {
+      convertButton.disabled = false;
+      convertButton.textContent = &quot;Upload failed&quot;;
+
+      const progressbar = file.htmlRow.getElementsByTagName(&quot;progress&quot;);
+      if (progressbar[0]?.parentElement) {
+        progressbar[0].parentElement.remove();
</file context>
Suggested change
const progressbar = file.htmlRow.getElementsByTagName("progress");
// Remove row for this file
if (file.htmlRow && file.htmlRow.remove) {
file.htmlRow.remove();
}
// Remove from internal list
const idx = fileNames.indexOf(file.name);
if (idx !== -1) {
fileNames.splice(idx, 1);
}
const progressbar = file.htmlRow?.getElementsByTagName("progress");
Fix with Cubic

if (progressbar[0]?.parentElement) {
progressbar[0].parentElement.remove();
}
return;
}

// Clean upload
if (pendingFiles === 0) {
if (formatSelected) {
if (formatSelected && fileNames.length > 0) {
convertButton.disabled = false;
}
convertButton.textContent = "Convert";
}

//Remove the progress bar when upload is done
let progressbar = file.htmlRow.getElementsByTagName("progress");
progressbar[0].parentElement.remove();
console.log(data);
const progressbar = file.htmlRow.getElementsByTagName("progress");
if (progressbar[0]?.parentElement) {
progressbar[0].parentElement.remove();
}
console.log("Upload parsed response:", data);
};

xhr.upload.onprogress = (e) => {
Expand All @@ -231,11 +308,16 @@ const uploadFile = (file) => {
console.log(`upload progress (${file.name}):`, (100 * sent) / total);

let progressbar = file.htmlRow.getElementsByTagName("progress");
progressbar[0].value = (100 * sent) / total;
if (progressbar[0]) {
progressbar[0].value = (100 * sent) / total;
}
};

xhr.onerror = (e) => {
console.log(e);
console.log("XHR error:", e);
alert("Upload failed due to a network error.");
convertButton.disabled = false;
convertButton.textContent = "Upload failed";
};

xhr.send(formData);
Expand All @@ -249,3 +331,4 @@ formConvert.addEventListener("submit", () => {
});

updateSearchBar();

34 changes: 34 additions & 0 deletions src/helpers/avToggle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// src/helpers/avToggle.ts

import {
ANTIVIRUS_ENABLED_DEFAULT,
CLAMAV_CONFIGURED,
} from "./env";

let antivirusEnabled = ANTIVIRUS_ENABLED_DEFAULT;

/**
* Is ClamAV configured at all (CLAMAV_URL set)?
*/
export function isAntivirusAvailable(): boolean {
return CLAMAV_CONFIGURED;
}

/**
* Is antivirus scanning currently enabled (and available)?
*/
export function isAntivirusEnabled(): boolean {
return CLAMAV_CONFIGURED && antivirusEnabled;
}

/**
* Change current antivirus enabled/disabled state.
* If CLAMAV is not configured, this is effectively a no-op and remains false.
*/
export function setAntivirusEnabled(enabled: boolean): void {
if (!CLAMAV_CONFIGURED) {
antivirusEnabled = false;
return;
}
antivirusEnabled = enabled;
}
27 changes: 25 additions & 2 deletions src/helpers/env.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// src/helpers/env.ts

export const ACCOUNT_REGISTRATION =
process.env.ACCOUNT_REGISTRATION?.toLowerCase() === "true" || false;

export const HTTP_ALLOWED = process.env.HTTP_ALLOWED?.toLowerCase() === "true" || false;
export const HTTP_ALLOWED =
process.env.HTTP_ALLOWED?.toLowerCase() === "true" || false;

export const ALLOW_UNAUTHENTICATED =
process.env.ALLOW_UNAUTHENTICATED?.toLowerCase() === "true" || false;
Expand All @@ -10,7 +13,8 @@ export const AUTO_DELETE_EVERY_N_HOURS = process.env.AUTO_DELETE_EVERY_N_HOURS
? Number(process.env.AUTO_DELETE_EVERY_N_HOURS)
: 24;

export const HIDE_HISTORY = process.env.HIDE_HISTORY?.toLowerCase() === "true" || false;
export const HIDE_HISTORY =
process.env.HIDE_HISTORY?.toLowerCase() === "true" || false;

export const WEBROOT = process.env.WEBROOT ?? "";

Expand All @@ -23,3 +27,22 @@ export const MAX_CONVERT_PROCESS =

export const UNAUTHENTICATED_USER_SHARING =
process.env.UNAUTHENTICATED_USER_SHARING?.toLowerCase() === "true" || false;

// ─────────────────────────────────────────────────────────────
// ClamAV / Antivirus integration
// ─────────────────────────────────────────────────────────────

// REST endpoint of benzino77/clamav-rest-api, e.g.
// CLAMAV_URL=http://192.168.68.134:3000/api/v1/scan
export const CLAMAV_URL = process.env.CLAMAV_URL ?? "";

// True only if CLAMAV_URL is non-empty
export const CLAMAV_CONFIGURED = CLAMAV_URL.length > 0;

// Default AV toggle value:
// - If ANTIVIRUS_ENABLED_DEFAULT=false → force disabled
// - Otherwise: enabled only when CLAMAV is configured
export const ANTIVIRUS_ENABLED_DEFAULT =
process.env.ANTIVIRUS_ENABLED_DEFAULT?.toLowerCase() === "false"
? false
: CLAMAV_CONFIGURED;
12 changes: 10 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { root } from "./pages/root";
import { upload } from "./pages/upload";
import { user } from "./pages/user";
import { healthcheck } from "./pages/healthcheck";
import { antivirus } from "./pages/antivirus"; // 👈 NEW

export const uploadsDir = "./data/uploads/";
export const outputDir = "./data/output/";
Expand Down Expand Up @@ -50,6 +51,7 @@ const app = new Elysia({
.use(listConverters)
.use(chooseConverter)
.use(healthcheck)
.use(antivirus) // 👈 register the antivirus toggle API
.onError(({ error }) => {
console.error(error);
});
Expand All @@ -67,13 +69,19 @@ if (process.env.NODE_ENV !== "production") {

app.listen(3000);

console.log(`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}${WEBROOT}`);
console.log(
`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}${WEBROOT}`,
);

const clearJobs = () => {
const jobs = db
.query("SELECT * FROM jobs WHERE date_created < ?")
.as(Jobs)
.all(new Date(Date.now() - AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000).toISOString());
.all(
new Date(
Date.now() - AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000,
).toISOString(),
);

for (const job of jobs) {
// delete the directories
Expand Down
45 changes: 45 additions & 0 deletions src/pages/antivirus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// src/pages/antivirus.tsx

import { Elysia, t } from "elysia";
import { userService } from "./user";
import {
isAntivirusAvailable,
isAntivirusEnabled,
setAntivirusEnabled,
} from "../helpers/avToggle";

export const antivirus = new Elysia()
.use(userService)
// Get current AV status
.get("/api/antivirus", () => {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P0: The new /api/antivirus GET/POST endpoints are exposed without { auth: true }, so unauthenticated callers can enable or disable the global scanning state used by the upload pipeline.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/pages/antivirus.tsx, line 14:

<comment>The new /api/antivirus GET/POST endpoints are exposed without `{ auth: true }`, so unauthenticated callers can enable or disable the global scanning state used by the upload pipeline.</comment>

<file context>
@@ -0,0 +1,45 @@
+export const antivirus = new Elysia()
+  .use(userService)
+  // Get current AV status
+  .get(&quot;/api/antivirus&quot;, () =&gt; {
+    const available = isAntivirusAvailable();
+    const enabled = isAntivirusEnabled();
</file context>

✅ Addressed in 3420464

const available = isAntivirusAvailable();
const enabled = isAntivirusEnabled();
return { available, enabled };
})
// Update AV status
.post(
"/api/antivirus",
({ body }) => {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P1: Security: POST /api/antivirus endpoint lacks authentication. This allows unauthenticated users to disable antivirus scanning. Add user to the handler context to require authentication, similar to other protected endpoints like deleteFile.tsx.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/pages/antivirus.tsx, line 22:

<comment>Security: POST `/api/antivirus` endpoint lacks authentication. This allows unauthenticated users to disable antivirus scanning. Add `user` to the handler context to require authentication, similar to other protected endpoints like `deleteFile.tsx`.</comment>

<file context>
@@ -0,0 +1,45 @@
+  // Update AV status
+  .post(
+    &quot;/api/antivirus&quot;,
+    ({ body }) =&gt; {
+      const { enabled } = body;
+
</file context>

✅ Addressed in 3420464

const { enabled } = body;

if (!isAntivirusAvailable()) {
// CLAMAV_URL missing: force disabled and report unavailable
return {
available: false,
enabled: false,
};
}

setAntivirusEnabled(Boolean(enabled));

return {
available: true,
enabled: isAntivirusEnabled(),
};
},
{
body: t.Object({
enabled: t.Boolean(),
}),
Comment on lines +88 to +92
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

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

P1: Missing authentication on POST /api/antivirus endpoint. This allows any user to disable antivirus scanning without being logged in, which is a security vulnerability. Add auth: true to the endpoint configuration like other sensitive endpoints in this codebase.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/pages/antivirus.tsx, line 39:

<comment>Missing authentication on POST `/api/antivirus` endpoint. This allows any user to disable antivirus scanning without being logged in, which is a security vulnerability. Add `auth: true` to the endpoint configuration like other sensitive endpoints in this codebase.</comment>

<file context>
@@ -0,0 +1,45 @@
+        available: true,
+        enabled: isAntivirusEnabled(),
+      };
+    },
+    {
+      body: t.Object({
</file context>
Suggested change
},
{
body: t.Object({
enabled: t.Boolean(),
}),
},
{
body: t.Object({
enabled: t.Boolean(),
}),
auth: true,
},

✅ Addressed in 3420464

},
);
Loading