Skip to content

Deploy to PHP (shared hosting) - #1019

Open
hatemhosny wants to merge 32 commits into
developfrom
deploy-to-php
Open

Deploy to PHP (shared hosting)#1019
hatemhosny wants to merge 32 commits into
developfrom
deploy-to-php

Conversation

@hatemhosny

@hatemhosny hatemhosny commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features
    • Added shared-hosting PHP/MySQL self-hosting, including sharing, broadcast viewer pages, sandbox hosting, a CORS proxy for imports, and an oEmbed endpoint with dynamic metadata.
    • Added GitHub Pages deployment with manual branch selection.
    • Enhanced Docker/PHP/FTP/SSH deployment workflows with manual triggers, branch selection, and environment-based guards.
  • Documentation
    • Published a PHP shared-hosting guide and expanded the deployment guide for workflow triggers and configuration.
  • Bug Fixes
    • Improved host-based origin allowlisting and hardened sandbox path handling and proxy/share validation (including payload limits).
  • Chores
    • Updated build to package the PHP server assets and adjusted Docker build-context exclusions.

@netlify

netlify Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploy Preview for livecodes ready!

Name Link
🔨 Latest commit bfaedb9
🔍 Latest deploy log https://app.netlify.com/projects/livecodes/deploys/6a6c0d97efe59e0008b45312
😎 Deploy Preview https://deploy-preview-1019--livecodes.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@hatemhosny hatemhosny changed the title Deploy to PHP (shared servers) Deploy to PHP (shared hosting) Jul 29, 2026

- name: Deploy to FTP server
if: ${{ steps.check.outputs.proceed == 'true' }}
uses: SamKirkland/FTP-Deploy-Action@v4.4.0

- name: Deploy via SSH
if: ${{ steps.check.outputs.proceed == 'true' }}
uses: SamKirkland/web-deploy@v1
cache-dependency-path: "**/package-lock.json"

- name: Install dependencies
run: npm ci
- name: Check if FTP credentials are configured
id: check
run: |
if [ -z "${{ secrets.FTP_HOST }}" ]; then

- name: Install dependencies
if: ${{ steps.check.outputs.proceed == 'true' }}
run: npm ci
Comment thread server/php/inc/db.php
// the database user may not have the CREATE privilege
// (e.g. when the database was pre-created by the hosting provider)
}
$pdo->exec("USE `$dbName`");
Comment thread server/php/inc/utils.php

function sendCorsHeaders(): void
{
header('Access-Control-Allow-Origin: *');
Comment thread server/php/inc/utils.php
array $headers = [],
int $timeout = 5,
): array {
$ch = curl_init($url);
Comment thread server/php/inc/utils.php
array $headers = [],
int $timeout = 5,
): array {
$ch = curl_init($url);
Comment thread server/php/oembed.php
];

header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 1.1 MB

ℹ️ View Unchanged
Filename Size Change
build/404.html 1.45 kB 0 B
build/app.html 250 B 0 B
build/index.html 1.57 kB -1 B (-0.06%)
build/livecodes/app.css 23.7 kB 0 B
build/livecodes/app.js 113 kB +10 B (+0.01%)
build/livecodes/assets.js 8.66 kB +32 B (+0.37%)
build/livecodes/assets/noop.js 18 B 0 B
build/livecodes/assets/templates/diagrams-starter.html 2.19 kB 0 B
build/livecodes/backup.js 3.74 kB +2 B (+0.05%)
build/livecodes/blockly.js 15 kB -16 B (-0.11%)
build/livecodes/broadcast.js 1.19 kB +2 B (+0.17%)
build/livecodes/bundle-types.js 4.41 kB 0 B
build/livecodes/code-to-image.js 11 kB -32 B (-0.29%)
build/livecodes/codejar.js 19.4 kB 0 B
build/livecodes/codemirror.js 17 kB +17 B (+0.1%)
build/livecodes/compile.page.js 2.45 kB 0 B
build/livecodes/compile.worker.js 15.3 kB +43 B (+0.28%)
build/livecodes/compiler-utils.js 3.17 kB -3 B (-0.09%)
build/livecodes/custom-editor-utils.js 198 B 0 B
build/livecodes/deploy.js 6.91 kB +1 B (+0.01%)
build/livecodes/editor-settings.js 22.1 kB +20 B (+0.09%)
build/livecodes/embed-ui.js 5.52 kB +10 B (+0.18%)
build/livecodes/embed.js 91.5 kB +100 B (+0.11%)
build/livecodes/export.js 3.91 kB 0 B
build/livecodes/firebase.js 22.7 kB 0 B
build/livecodes/format.worker.js 14.5 kB +94 B (+0.65%)
build/livecodes/google-fonts.js 7.12 kB 0 B
build/livecodes/headless.js 80.1 kB +172 B (+0.22%)
build/livecodes/i18n-ar-language-info.json 5.34 kB 0 B
build/livecodes/i18n-ar-translation.json 9.34 kB 0 B
build/livecodes/i18n-bn-language-info.json 5.76 kB 0 B
build/livecodes/i18n-bn-translation.json 9.68 kB 0 B
build/livecodes/i18n-de-language-info.json 5.4 kB 0 B
build/livecodes/i18n-de-translation.json 9.47 kB 0 B
build/livecodes/i18n-en-language-info.json 4.63 kB 0 B
build/livecodes/i18n-en-translation.json 8.06 kB 0 B
build/livecodes/i18n-es-language-info.json 5.12 kB 0 B
build/livecodes/i18n-es-translation.json 9.19 kB 0 B
build/livecodes/i18n-fa-language-info.json 5.52 kB 0 B
build/livecodes/i18n-fa-translation.json 9.5 kB 0 B
build/livecodes/i18n-fr-language-info.json 5.31 kB 0 B
build/livecodes/i18n-fr-translation.json 9.45 kB 0 B
build/livecodes/i18n-hi-language-info.json 5.93 kB 0 B
build/livecodes/i18n-hi-translation.json 9.99 kB 0 B
build/livecodes/i18n-hu-language-info.json 5.29 kB 0 B
build/livecodes/i18n-hu-translation.json 9.38 kB 0 B
build/livecodes/i18n-id-language-info.json 4.87 kB 0 B
build/livecodes/i18n-id-translation.json 8.71 kB 0 B
build/livecodes/i18n-it-language-info.json 5.17 kB 0 B
build/livecodes/i18n-it-translation.json 9.26 kB 0 B
build/livecodes/i18n-ja-language-info.json 5.72 kB 0 B
build/livecodes/i18n-ja-translation.json 9.64 kB 0 B
build/livecodes/i18n-nl-language-info.json 5.07 kB 0 B
build/livecodes/i18n-nl-translation.json 8.91 kB 0 B
build/livecodes/i18n-pt-language-info.json 5.16 kB 0 B
build/livecodes/i18n-pt-translation.json 9.38 kB 0 B
build/livecodes/i18n-ru-language-info.json 5.7 kB 0 B
build/livecodes/i18n-ru-translation.json 10.3 kB 0 B
build/livecodes/i18n-tr-language-info.json 5.3 kB 0 B
build/livecodes/i18n-tr-translation.json 9.29 kB 0 B
build/livecodes/i18n-ur-language-info.json 5.97 kB 0 B
build/livecodes/i18n-ur-translation.json 9.81 kB 0 B
build/livecodes/i18n-zh-CN-language-info.json 5.01 kB 0 B
build/livecodes/i18n-zh-CN-translation.json 8.67 kB 0 B
build/livecodes/i18n.js 20.4 kB -49 B (-0.24%)
build/livecodes/import-src.js 17.9 kB -50 B (-0.28%)
build/livecodes/import.js 16.4 kB -19 B (-0.12%)
build/livecodes/index.js 5.51 kB -19 B (-0.34%)
build/livecodes/lang-art-template-compiler.js 1.68 kB 0 B
build/livecodes/lang-assemblyscript-compiler.js 290 B 0 B
build/livecodes/lang-assemblyscript-script.js 386 B 0 B
build/livecodes/lang-astro-compiler.js 2.37 kB 0 B
build/livecodes/lang-clio-compiler.js 1.58 kB 0 B
build/livecodes/lang-commonlisp-script.js 123 B 0 B
build/livecodes/lang-cpp-script.js 1.96 kB 0 B
build/livecodes/lang-cpp-wasm-script.js 2.88 kB 0 B
build/livecodes/lang-csharp-wasm-script.js 2.21 kB 0 B
build/livecodes/lang-diagrams-compiler-esm.js 5.11 kB 0 B
build/livecodes/lang-dot-compiler.js 1.69 kB 0 B
build/livecodes/lang-ejs-compiler.js 1.66 kB 0 B
build/livecodes/lang-eta-compiler.js 1.68 kB 0 B
build/livecodes/lang-fennel-compiler.js 1.64 kB 0 B
build/livecodes/lang-gleam-compiler.js 3.13 kB 0 B
build/livecodes/lang-go-wasm-script.js 3.29 kB 0 B
build/livecodes/lang-haml-compiler.js 1.69 kB 0 B
build/livecodes/lang-handlebars-compiler.js 1.98 kB 0 B
build/livecodes/lang-imba-compiler.js 147 B 0 B
build/livecodes/lang-java-script.js 4.09 kB 0 B
build/livecodes/lang-jinja-compiler.js 1.67 kB 0 B
build/livecodes/lang-julia-script.js 3.32 kB 0 B
build/livecodes/lang-liquid-compiler.js 1.71 kB 0 B
build/livecodes/lang-lua-wasm-script.js 205 B 0 B
build/livecodes/lang-malina-compiler.js 2.98 kB 0 B
build/livecodes/lang-markdown-compiler.js 1.7 kB 0 B
build/livecodes/lang-markdown-script.js 1.47 kB 0 B
build/livecodes/lang-minizinc-script.js 2.09 kB 0 B
build/livecodes/lang-mustache-compiler.js 1.68 kB 0 B
build/livecodes/lang-nunjucks-compiler.js 2 kB 0 B
build/livecodes/lang-perl-script.js 268 B 0 B
build/livecodes/lang-php-wasm-script.js 347 B 0 B
build/livecodes/lang-postgresql-compiler-esm.js 1.76 kB 0 B
build/livecodes/lang-prolog-script.js 204 B 0 B
build/livecodes/lang-pug-compiler.js 371 B 0 B
build/livecodes/lang-python-wasm-script.js 1.89 kB 0 B
build/livecodes/lang-r-script-esm.js 2.47 kB 0 B
build/livecodes/lang-rescript-compiler-esm.js 2.19 kB 0 B
build/livecodes/lang-rescript-formatter.js 1.55 kB 0 B
build/livecodes/lang-riot-compiler.js 2.85 kB 0 B
build/livecodes/lang-ruby-wasm-script.js 1.75 kB 0 B
build/livecodes/lang-scss-compiler.js 1.85 kB 0 B
build/livecodes/lang-solid-compiler.js 263 B 0 B
build/livecodes/lang-sql-compiler.js 1.67 kB 0 B
build/livecodes/lang-sql-script.js 2.08 kB 0 B
build/livecodes/lang-svelte-compiler.js 4.73 kB 0 B
build/livecodes/lang-tcl-script.js 1.86 kB 0 B
build/livecodes/lang-teal-compiler.js 1.75 kB 0 B
build/livecodes/lang-twig-compiler.js 1.68 kB 0 B
build/livecodes/lang-vento-compiler.js 1.73 kB 0 B
build/livecodes/lang-vue-compiler.js 6.13 kB 0 B
build/livecodes/lang-vue2-compiler.js 3.5 kB 0 B
build/livecodes/lang-wat-compiler.js 348 B 0 B
build/livecodes/lang-wat-script.js 1.98 kB 0 B
build/livecodes/language-info.js 7.92 kB 0 B
build/livecodes/monaco.js 22.4 kB +35 B (+0.16%)
build/livecodes/open.js 6.24 kB -2 B (-0.03%)
build/livecodes/processor-lightningcss-compiler.js 1.91 kB 0 B
build/livecodes/processor-postcss-compiler.js 2.06 kB 0 B
build/livecodes/processor-tailwindcss-compiler.js 5.32 kB +13 B (+0.25%)
build/livecodes/processor-unocss-compiler.js 355 B 0 B
build/livecodes/processor-windicss-compiler.js 450 B 0 B
build/livecodes/quill.css 697 B 0 B
build/livecodes/quill.js 5.88 kB +18 B (+0.31%)
build/livecodes/resources.js 3.43 kB 0 B
build/livecodes/result-utils.js 1.17 kB 0 B
build/livecodes/share.js 3.88 kB +7 B (+0.18%)
build/livecodes/snippets.js 6.08 kB -9 B (-0.15%)
build/livecodes/sync-ui.js 3.29 kB +2 B (+0.06%)
build/livecodes/sync.js 3.56 kB 0 B
build/livecodes/sync.worker.js 29.8 kB 0 B
build/livecodes/templates.js 27 kB 0 B
build/sdk/livecodes.js 4.43 kB 0 B
build/sdk/livecodes.umd.js 5.23 kB 0 B
build/sdk/package.json 456 B 0 B
build/sdk/preact.js 4.81 kB 0 B
build/sdk/react.js 4.78 kB 0 B
build/sdk/solid.js 4.94 kB 0 B
build/sdk/svelte.js 4.4 kB 0 B
build/sdk/vue.js 4.81 kB 0 B
build/sdk/web-components.js 5.83 kB 0 B

compressed-size-action

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying livecodes with  Cloudflare Pages  Cloudflare Pages

Latest commit: bfaedb9
Status: ✅  Deploy successful!
Preview URL: https://b19617e8.livecodes.pages.dev
Branch Preview URL: https://deploy-to-php.livecodes.pages.dev

View logs

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

This PR introduces a regression in src/livecodes/services/allowed-origin.tslocalhost is no longer recognized as a valid origin (a duplicate localpen.pages.dev line replaced it). This breaks origin-based feature gating on local development setups.

Reviewed changes — adds PHP/MySQL self-hosting support for shared hosting with complete server-side service implementations (share, broadcast, CORS proxy, sandbox, oEmbed, dynamic meta tags), refactors CI/CD deploy workflows into per-target files with guard conditions, and updates docs across multiple pages.

  • PHP server implementation (server/php/) — ports all Cloudflare Pages functions and the Node broadcast server to PHP + MySQL, using HTTP polling instead of WebSockets for broadcast state
  • CI/CD workflow refactor — splits deploy.yml into deploy-ghpages.yml and adds deploy-php.yml, with guard variables (PHP_ENV, VPS_ENV) preventing conflicting deployments
  • Build system changes — adds HOST_NAME env var, changes default ports to 443, switches to JSON.stringify for env var quoting, adds copy-php build script
  • Client-side service changes — allows self-hosted domains in allowedOrigin, strips default :443 from broadcast URLs, reorders sandbox URL priority
  • Documentation — new PHP setup guide, deployment reference, sidebar updates

🚨 localhost origin validation broken in client-side allowedOrigin

The src/livecodes/services/allowed-origin.ts diff accidentally replaced matchesDomain(hostname, 'localhost') with a duplicate matchesDomain(hostname, 'localpen.pages.dev'), meaning localhost is no longer recognized as a valid origin. This affects all allowedOrigin() callers on local development (broadcast viewer pages, share URL resolution, oEmbed).

Technical details
# localhost origin validation broken

## Affected sites
- `src/livecodes/services/allowed-origin.ts:21` — duplicate `localpen.pages.dev` should be `'localhost'`

## Required outcome
- Restore the `matchesDomain(hostname, 'localhost')` check in the client-side `allowedOrigin` function

## Suggested approach
Replace line 21:
```typescript
         matchesDomain(hostname, 'localpen.pages.dev') ||
```
with:
```typescript
         matchesDomain(hostname, 'localhost') ||
```

ℹ️ Nitpicks

  • server/php/README.md:5 references .github/workflows/php-deploy.yml but the actual file is named deploy-php.yml.

  • src/livecodes/services/broadcast.ts:4 — the .replace(':443', '') string replacement is fragile: it would mangle a hostname or path containing :443 (e.g. :8443/8/). Consider .replace(/:443\//, '/') or conditionally omitting the port when it equals '443'.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

Comment thread src/livecodes/services/allowed-origin.ts Outdated
Comment thread server/php/README.md Outdated
Comment thread src/livecodes/services/broadcast.ts Outdated
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds PHP/MySQL shared-hosting support with generated deployment artifacts, FTP/SSH workflows, broadcast and API endpoints, sandbox serving, configuration helpers, and documentation. Deployment guards and self-hosted origin, broadcast, and sandbox environment handling are also updated.

Changes

PHP self-hosting and deployment

Layer / File(s) Summary
Deployment workflow orchestration
.github/workflows/deploy-*.yml
Adds GitHub Pages deployment, manual Docker branch selection, PHP/FTP/SSH workflow guards, and generated PHP configuration escaping.
PHP packaging and environment wiring
.dockerignore, package.json, scripts/copy-php.js, scripts/utils.js
Adds PHP artifact copying, starter-template metadata generation, self-hosted environment defaults, and Docker build-context exclusion.
PHP runtime foundation
server/php/.htaccess, server/php/config.sample.php, server/php/inc/*, server/php/vendor/*
Adds Apache routing, configuration and database loaders, shared HTTP/origin/import helpers, and LZ-string decompression.
PHP application endpoints
server/php/api/*, server/php/index.php, server/php/oembed.php, server/php/sandbox/*
Adds CORS proxying, share storage, dynamic metadata and analytics handling, oEmbed responses, and versioned sandbox serving.
PHP broadcast channels
server/php/broadcast/*
Adds channel creation, authorization, persistence, state polling, viewer routing, and code/result broadcast pages.
Documentation and service integration
docs/docs/**, docs/sidebars.ts, functions/allowed-origin.ts, src/livecodes/services/*, server/php/README.md
Documents PHP hosting and deployment, adds the PHP guide to navigation, and updates origin, broadcast, and sandbox URL handling.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Publisher
  participant BroadcastIndex
  participant MySQL
  participant BroadcastViewer
  Publisher->>BroadcastIndex: POST channel update
  BroadcastIndex->>MySQL: Create or update channel
  MySQL-->>BroadcastIndex: Persist channel state
  BroadcastViewer->>BroadcastIndex: Poll channel state
  BroadcastIndex->>MySQL: Read channel result and data
  MySQL-->>BroadcastIndex: Return broadcast state
  BroadcastIndex-->>BroadcastViewer: JSON state response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding a PHP shared-hosting deployment path and related workflows/docs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch deploy-to-php

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 19

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (6)
docs/docs/advanced/php.mdx-108-110 (1)

108-110: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Hyphenate “third-party.”

Line 110 should say “third-party sites.”

🤖 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 `@docs/docs/advanced/php.mdx` around lines 108 - 110, Update the oEmbed
description to hyphenate “third-party” when describing third-party sites,
leaving the surrounding link and wording unchanged.

Source: Linters/SAST tools

server/php/README.md-5-5 (1)

5-5: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Wrong workflow filename.

The workflow added in this PR is .github/workflows/deploy-php.yml, not php-deploy.yml.

📝 Fix
-These files are copied to the `build/` directory after the app is built (`npm run copy-php`), and are deployed via FTP/SSH using the GitHub workflow `.github/workflows/php-deploy.yml`.
+These files are copied to the `build/` directory after the app is built (`npm run copy-php`), and are deployed via FTP/SSH using the GitHub workflow `.github/workflows/deploy-php.yml`.
🤖 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 `@server/php/README.md` at line 5, Update the deployment workflow reference in
the README text to use `.github/workflows/deploy-php.yml` instead of
`.github/workflows/php-deploy.yml`; leave the surrounding build and deployment
description unchanged.
server/php/vendor/lzstring.php-120-120 (1)

120-120: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Document the mbstring requirement.

server/php/vendor/lzstring.php calls mb_convert_encoding(...), so this server copy now needs the mbstring extension in addition to pdo_mysql/curl. Add it to the PHP server requirements in server/php/README.md, or provide an iconv('UTF-16BE', 'UTF-8', $result) fallback when mb_convert_encoding is missing.

🤖 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 `@server/php/vendor/lzstring.php` at line 120, Document the new mbstring
extension requirement for the PHP server in server/php/README.md, alongside the
existing pdo_mysql and curl requirements, since the lzstring implementation
calls mb_convert_encoding. Do not add a fallback unless choosing instead to
remove this dependency.
server/php/config.sample.php-20-38 (1)

20-38: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Sample omits two supported keys.

getConfig() in server/php/inc/config.php (lines 24-36) also loads SELF_HOSTED_SHARE and SELF_HOSTED_BROADCAST. Since the docs here say config.php can be created manually, list them too.

📝 Suggested addition
     // optional: comma-separated user tokens for the broadcast server (empty = no auth)
     'BROADCAST_TOKENS' => '',
+
+    // optional: enable self-hosted share / broadcast services
+    'SELF_HOSTED_SHARE' => '',
+    'SELF_HOSTED_BROADCAST' => '',
 ];
🤖 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 `@server/php/config.sample.php` around lines 20 - 38, Update the sample
configuration array to include the supported SELF_HOSTED_SHARE and
SELF_HOSTED_BROADCAST keys loaded by getConfig(), with appropriate empty/default
values and comments indicating their purpose.
server/php/broadcast/assets/code.html-87-87 (1)

87-87: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use subdirectory-safe broadcast URLs.

state.php is polled with absolute /broadcast/state.php, while .htaccess rewrites broadcast endpoints only at domain root (^/?$, ^channels/...). Appending broadcast/index.php instead of state.php also differs from the documented API path. Pass a base prefix from PHP or derive it from the channel page URL before making these requests.

🤖 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 `@server/php/broadcast/assets/code.html` at line 87, Update the polling request
in the broadcast client around fetch and its channel-page URL handling to
construct a subdirectory-safe endpoint prefix instead of hardcoding
/broadcast/state.php. Derive the base prefix from the current PHP-rendered page
or pass it from PHP, then append the documented state.php API path without
changing the channel encoding.
server/php/api/cors.php-46-53 (1)

46-53: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

statusText is always empty, so this fallback message is dead code.

fetchUrl() (utils.php) always returns 'statusText' => '', so the ternary at Line 51 always echoes 'Bad Gateway!' regardless of the actual upstream status. Either populate statusText in fetchUrl (e.g. via a reason-phrase map or curl_getinfo) or drop the ternary.

🤖 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 `@server/php/api/cors.php` around lines 46 - 53, The error response in the CORS
handler relies on `fetchUrl()` returning a meaningful `statusText`, but that
function currently always returns an empty string. Update `fetchUrl()` to
populate `statusText` from the upstream response, such as via curl response
metadata or a status-reason mapping, while preserving the existing `Bad
Gateway!` fallback when no message is available.
🧹 Nitpick comments (9)
.github/workflows/deploy-ghpages.yml (1)

37-43: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Scope the generated GitHub App token to the minimum permission needed.

Without a permissions: input, create-github-app-token issues a token carrying every permission granted to the app's installation, not just the contents: write this workflow uses to push to gh-pages.

🔒 Proposed fix
       - name: Generate GitHub App token
         if: ${{ steps.check-secrets.outputs.available == 'true' }}
         uses: actions/create-github-app-token@v1
         id: generate-token
         with:
           app-id: ${{ secrets.CI_APP_ID }}
           private-key: ${{ secrets.CI_APP_PRIVATE_KEY }}
+          permission-contents: write
🤖 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/deploy-ghpages.yml around lines 37 - 43, Update the
generate-token step using actions/create-github-app-token so its permissions
input grants only contents: write, preserving the existing conditional execution
and token configuration.

Source: Linters/SAST tools

.github/workflows/deploy-php.yml (1)

38-42: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Missing persist-credentials: false on new checkout steps. All three new checkout steps default to persisting the checkout credential in .git/config through subsequent build steps that run third-party lifecycle scripts, even though none of these workflows need the ambient git credential afterward (each authenticates its deploy action separately).

  • .github/workflows/deploy-php.yml#L38-L42: add persist-credentials: false to the deploy-ftp job's checkout.
  • .github/workflows/deploy-php.yml#L145-L149: add persist-credentials: false to the deploy-ssh job's checkout.
  • .github/workflows/deploy-ghpages.yml#L45-L49: add persist-credentials: false; the workflow already re-authenticates its push via the separately generated DEPLOY_TOKEN.
🤖 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/deploy-php.yml around lines 38 - 42, Disable checkout
credential persistence by adding persist-credentials: false to the checkout
steps in .github/workflows/deploy-php.yml lines 38-42 and 145-149, and
.github/workflows/deploy-ghpages.yml lines 45-49; leave the existing branch
selection and separate deployment authentication unchanged.

Source: Linters/SAST tools

server/php/inc/db.php (1)

30-58: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Schema DDL runs on every request.

Each request that touches the DB pays a CREATE DATABASE IF NOT EXISTS, a USE, and two CREATE TABLE IF NOT EXISTS round trips before any real work — noticeable on shared hosting where connection latency dominates. Consider running the bootstrap lazily (only after a query fails with "table doesn't exist") or once per deploy behind a marker file/setup endpoint.

🤖 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 `@server/php/inc/db.php` around lines 30 - 58, The database bootstrap in the
initialization flow runs on every request; move the CREATE DATABASE, USE, and
CREATE TABLE statements out of the normal request path and invoke them only
through a one-time setup mechanism, such as a deploy marker or explicit setup
endpoint. Ensure regular requests use an already-initialized schema without
issuing these DDL round trips.
server/php/broadcast/index.php (3)

65-69: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Compare tokens with hash_equals.

channelToken (and the user token on line 34) are secrets; use a constant-time comparison and an explicit string cast so a non-string payload cannot trigger an Array to string conversion notice.

♻️ Suggested change
-    if (($body['channelToken'] ?? '') !== $existing['channel_token']) {
+    $providedToken = $body['channelToken'] ?? '';
+    if (!is_string($providedToken) || !hash_equals((string) $existing['channel_token'], $providedToken)) {
🤖 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 `@server/php/broadcast/index.php` around lines 65 - 69, Update the channelToken
validation and the user-token comparison near line 34 to use hash_equals with
explicit string casts for both operands, preserving the existing unauthorized
response and exit behavior while preventing non-string payload conversion
notices.

43-47: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Eviction sweep on every broadcast POST, over an unindexed column.

Broadcasts are posted continuously while a user types, and each one issues a DELETE ... WHERE last_accessed < ? that cannot use an index (broadcast_channels in server/php/inc/db.php only indexes channel). Add an index on last_accessed and/or only sweep probabilistically (e.g. ~1% of requests) to keep the write path cheap.

🤖 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 `@server/php/broadcast/index.php` around lines 43 - 47, Add an index for
broadcast_channels.last_accessed in the schema setup, and change the eviction
sweep around the broadcast POST handler to run only probabilistically
(approximately 1% of requests). Preserve the existing 20-minute cutoff and
deletion behavior when the sweep is selected.

49-53: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Validate the client-supplied channel format.

(string) $body['channel'] emits an Array to string conversion warning when a client posts an array/object. A cheap preg_match('/^[a-z0-9]{1,11}$/', ...) guard (matching generateId() in server/php/inc/utils.php lines 16-25) rejects malformed input with a 400 before touching the DB.

🤖 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 `@server/php/broadcast/index.php` around lines 49 - 53, Update the channel
parsing around $newChannel and $channel to validate client-supplied values
before casting or using them: accept only strings matching generateId()’s
/^[a-z0-9]{1,11}$/ format, and return an HTTP 400 response for invalid scalar,
array, or object inputs before any database work. Preserve generated IDs for
empty or omitted channels.
server/php/broadcast/assets/code.html (1)

85-108: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Add backoff for failed polls.

On errors (network loss, DB down, 5xx) the loop keeps hitting state.php every 1.5s indefinitely, which is exactly the load pattern shared hosts throttle. An exponential backoff capped at ~30s keeps the viewer alive without hammering the server.

♻️ Suggested change
+        let delay = 1500;
         const poll = async () => {
           try {
             const res = await fetch('/broadcast/state.php?channel=' + encodeURIComponent(channel), {
               cache: 'no-store',
             });
@@
             if (newConfig !== lastConfig) {
               lastConfig = newConfig;
               await loadConfig(result, data);
             }
+            delay = 1500;
           } catch (e) {
-            // retry on next interval
+            delay = Math.min(delay * 2, 30000);
           }
-          setTimeout(poll, 1500);
+          setTimeout(poll, delay);
         };
🤖 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 `@server/php/broadcast/assets/code.html` around lines 85 - 108, Update the poll
loop around the async poll function to apply exponential backoff after fetch or
processing errors, capping the retry delay at approximately 30 seconds; reset
the delay after a successful poll, while preserving the existing 1.5-second
interval for healthy polling and the 404 disconnect behavior.
server/php/index.php (1)

104-110: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Fallback path can emit a partial page.

If a Throwable is raised after echo $modifiedBody (line 98) — e.g. inside header() on an already-flushed buffer — the catch block appends the entire unmodified $originalBody, producing two concatenated documents. Guard with a flag (or move the echo to the very end of the try) so the fallback only runs when nothing was written.

🤖 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 `@server/php/index.php` around lines 104 - 110, Update the try/catch flow
around the modified-body output and catch block to track whether a response body
has already been written, or defer echoing until all fallible operations
complete. Only emit the fallback $originalBody when no output was written,
preventing it from being appended after $modifiedBody; preserve the existing
error data updates.
server/php/.htaccess (1)

11-13: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard the sandbox catch-all against re-entrant rewrites.

In per-directory .htaccess context, Apache can re-process sandbox/index.php on the internal redirect, so RewriteRule ^ sandbox/index.php [L] can exceed LimitInternalRecursion and return AH00124. Add a condition that skips the already-rewritten URI.

♻️ Suggested guard
   # sandbox (separate origin): requests for the sandbox subdomain
   RewriteCond %{HTTP_HOST} ^sandbox\. [NC]
+  RewriteCond %{REQUEST_URI} !^/sandbox/index\.php$
   RewriteRule ^ sandbox/index.php [L]
🤖 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 `@server/php/.htaccess` around lines 11 - 13, Update the sandbox rewrite block
associated with the `HTTP_HOST` condition to skip requests whose URI is already
`sandbox/index.php` before applying the catch-all `RewriteRule`; preserve the
existing routing for other sandbox-subdomain requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/deploy-docker.yml:
- Around line 33-34: Secure the remote checkout commands by preventing the
branch input from being interpolated as unquoted shell syntax in the SSH script.
Update the deployment workflow around git checkout and pull to pass the selected
branch as a safely quoted or environment-backed argument, while preserving the
default to main and ensuring crafted branch values cannot execute additional
commands.

In @.github/workflows/deploy-php.yml:
- Around line 56-69: Replace direct `${{ vars.PHP_ENV }}` interpolation inside
each `node -e` script with an environment variable supplied through the step’s
`env:` block, then read that variable from `process.env` in the script. Apply
this change to all four occurrences across the PHP and SSH deployment jobs while
preserving the existing parsing and GITHUB_ENV writes.
- Around line 79-115: Update the shared config-generation logic used by the
“Create server config” steps in both deploy-php and deploy-ssh, preferably by
extracting it into a reusable script. Escape backslashes and single quotes when
serializing values into PHP single-quoted strings, and omit unset secrets rather
than writing the literal string “undefined”; ensure both jobs invoke the same
corrected implementation.

In `@scripts/utils.js`:
- Around line 69-74: Update the hostName assignment to apply the 'null' fallback
before converting or otherwise retaining the environment value, matching the
existing SANDBOX_HOST_NAME pattern. Ensure an unset HOST_NAME produces the
literal 'null' sentinel while preserving configured host names.

In `@server/php/.htaccess`:
- Around line 20-21: Add module-independent access denial for the inc/ and
vendor/ directories, using directory-level authorization such as nested
.htaccess files with Require all denied or an equivalent FilesMatch rule. Keep
the existing RewriteRule as defense in depth, and ensure the denial applies even
when an earlier rewrite rule matches or mod_rewrite is unavailable.

In `@server/php/api/cors.php`:
- Around line 14-21: Replace the ad hoc origin condition in the request guard
with the existing allowedOrigin() helper, ensuring requests are rejected when
the Origin header is absent or fails the helper’s exact host/subdomain
validation. Preserve the existing GET/POST method restriction and 403 response
behavior.

In `@server/php/api/share.php`:
- Line 24: Update the query prepared in the share-loading function to compare
the primary-key column directly and preserve indexed lookup, normalizing the
input parameter before binding if case-insensitive IDs are required. Apply the
same approach used to fix importProject(), removing LOWER() from the SQL column
expression.
- Around line 44-51: Add an explicit maximum size check for $rawBody immediately
after reading php://input and before json_decode or any INSERT path, rejecting
payloads above the configured MEDIUMTEXT-safe limit with HTTP 413 (or the
endpoint’s established client-error response). Ensure the limit applies to the
raw request bytes, including otherwise valid JSON, and preserve normal
processing for payloads within the limit.

In `@server/php/broadcast/assets/result.html`:
- Around line 29-33: Update the result iframe’s sandbox configuration to prevent
srcdoc content from combining allow-same-origin with allow-scripts. Remove
allow-same-origin unless same-origin access is required, or route the broadcast
result through a separate isolated origin consistent with the other sandbox path
while preserving the remaining permissions.

In `@server/php/broadcast/channel.php`:
- Around line 34-42: Update the HTML substitution in the channel response flow
to HTML-escape the value returned by getRequestOrigin() before passing it to
str_replace, while preserving the existing AppUrl placeholder replacement and
response behavior.

In `@server/php/broadcast/index.php`:
- Around line 86-91: Handle a false return from json_encode in the dataJson
assignment before checking for '[]' or calling strlen, and normalize encoding
failures to the existing valid empty-object representation '{}'. Preserve the
current size limit and empty-array conversion so storedData is always valid
JSON.

In `@server/php/inc/db.php`:
- Around line 22-28: Update the PDO initialization around the $options array and
new PDO call to set a finite PDO::ATTR_TIMEOUT, then wrap the connection attempt
in exception handling that logs server-side details without exposing the DSN,
host, port, or credentials to the client. Preserve exception mode while
returning a safe generic failure response and terminating the request on
connection failure.

In `@server/php/inc/utils.php`:
- Around line 49-96: Harden fetchUrl by allowing only http and https URLs, then
validate the hostname and resolved destination before curl_init; reject
loopback, private, reserved, link-local, and other non-public IPv4/IPv6
addresses using DNS-rebinding-safe resolution and ensure the address curl
connects to is the validated one. Return the existing failed-response shape for
rejected targets while preserving normal public HTTP(S) requests.
- Around line 27-32: Update getRequestOrigin() to validate HTTP_HOST against the
configured getConfig('HOST_NAME') or allowedOrigin() before using it as the
origin, falling back to the trusted configured host when invalid. In
channel.php, HTML-encode the validated AppUrl replacement at the template
injection point before rendering it.
- Around line 154-187: The self-hosted lookup in importProject should remain
index-friendly by avoiding LOWER() on the share.id column. Use an exact id
comparison, or lowercase the parameter once and compare it directly when IDs are
stored lowercase, while preserving case-insensitive behavior as appropriate.

In `@server/php/index.php`:
- Around line 63-87: Escape the client-controlled $origin before interpolating
it into HTML in the modifiedBody replacements, including the href replacement
and the livecodes content replacement. Reuse the existing encodeHTML function,
matching the escaping already applied to $currentUrl, while preserving the
current URL construction and replacement behavior.

In `@server/php/sandbox/index.php`:
- Around line 43-48: Update the path-traversal guard in the `$filePath`
validation to compare against `$sandboxDir` with a trailing directory separator,
ensuring only paths within the sandbox directory pass while preserving the
existing missing-file and 404 handling.

In `@src/livecodes/services/allowed-origin.ts`:
- Around line 21-22: Update the allowlist condition in the allowed-origin logic
to include matchesDomain(hostname, 'localhost') alongside the existing
localpen.pages.dev entry. Remove the duplicate localpen.pages.dev check while
preserving the HOST_NAME condition and keeping behavior aligned with
functions/allowed-origin.ts.

In `@src/livecodes/services/broadcast.ts`:
- Around line 2-4: Update the self-hosted branch of getUrl to use port 3030 when
BROADCAST_PORT is unset, then apply the existing removal of the default HTTPS
port 443 so the URL remains valid in both cases.

---

Minor comments:
In `@docs/docs/advanced/php.mdx`:
- Around line 108-110: Update the oEmbed description to hyphenate “third-party”
when describing third-party sites, leaving the surrounding link and wording
unchanged.

In `@server/php/api/cors.php`:
- Around line 46-53: The error response in the CORS handler relies on
`fetchUrl()` returning a meaningful `statusText`, but that function currently
always returns an empty string. Update `fetchUrl()` to populate `statusText`
from the upstream response, such as via curl response metadata or a
status-reason mapping, while preserving the existing `Bad Gateway!` fallback
when no message is available.

In `@server/php/broadcast/assets/code.html`:
- Line 87: Update the polling request in the broadcast client around fetch and
its channel-page URL handling to construct a subdirectory-safe endpoint prefix
instead of hardcoding /broadcast/state.php. Derive the base prefix from the
current PHP-rendered page or pass it from PHP, then append the documented
state.php API path without changing the channel encoding.

In `@server/php/config.sample.php`:
- Around line 20-38: Update the sample configuration array to include the
supported SELF_HOSTED_SHARE and SELF_HOSTED_BROADCAST keys loaded by
getConfig(), with appropriate empty/default values and comments indicating their
purpose.

In `@server/php/README.md`:
- Line 5: Update the deployment workflow reference in the README text to use
`.github/workflows/deploy-php.yml` instead of
`.github/workflows/php-deploy.yml`; leave the surrounding build and deployment
description unchanged.

In `@server/php/vendor/lzstring.php`:
- Line 120: Document the new mbstring extension requirement for the PHP server
in server/php/README.md, alongside the existing pdo_mysql and curl requirements,
since the lzstring implementation calls mb_convert_encoding. Do not add a
fallback unless choosing instead to remove this dependency.

---

Nitpick comments:
In @.github/workflows/deploy-ghpages.yml:
- Around line 37-43: Update the generate-token step using
actions/create-github-app-token so its permissions input grants only contents:
write, preserving the existing conditional execution and token configuration.

In @.github/workflows/deploy-php.yml:
- Around line 38-42: Disable checkout credential persistence by adding
persist-credentials: false to the checkout steps in
.github/workflows/deploy-php.yml lines 38-42 and 145-149, and
.github/workflows/deploy-ghpages.yml lines 45-49; leave the existing branch
selection and separate deployment authentication unchanged.

In `@server/php/.htaccess`:
- Around line 11-13: Update the sandbox rewrite block associated with the
`HTTP_HOST` condition to skip requests whose URI is already `sandbox/index.php`
before applying the catch-all `RewriteRule`; preserve the existing routing for
other sandbox-subdomain requests.

In `@server/php/broadcast/assets/code.html`:
- Around line 85-108: Update the poll loop around the async poll function to
apply exponential backoff after fetch or processing errors, capping the retry
delay at approximately 30 seconds; reset the delay after a successful poll,
while preserving the existing 1.5-second interval for healthy polling and the
404 disconnect behavior.

In `@server/php/broadcast/index.php`:
- Around line 65-69: Update the channelToken validation and the user-token
comparison near line 34 to use hash_equals with explicit string casts for both
operands, preserving the existing unauthorized response and exit behavior while
preventing non-string payload conversion notices.
- Around line 43-47: Add an index for broadcast_channels.last_accessed in the
schema setup, and change the eviction sweep around the broadcast POST handler to
run only probabilistically (approximately 1% of requests). Preserve the existing
20-minute cutoff and deletion behavior when the sweep is selected.
- Around line 49-53: Update the channel parsing around $newChannel and $channel
to validate client-supplied values before casting or using them: accept only
strings matching generateId()’s /^[a-z0-9]{1,11}$/ format, and return an HTTP
400 response for invalid scalar, array, or object inputs before any database
work. Preserve generated IDs for empty or omitted channels.

In `@server/php/inc/db.php`:
- Around line 30-58: The database bootstrap in the initialization flow runs on
every request; move the CREATE DATABASE, USE, and CREATE TABLE statements out of
the normal request path and invoke them only through a one-time setup mechanism,
such as a deploy marker or explicit setup endpoint. Ensure regular requests use
an already-initialized schema without issuing these DDL round trips.

In `@server/php/index.php`:
- Around line 104-110: Update the try/catch flow around the modified-body output
and catch block to track whether a response body has already been written, or
defer echoing until all fallible operations complete. Only emit the fallback
$originalBody when no output was written, preventing it from being appended
after $modifiedBody; preserve the existing error data updates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 03d16012-943f-4d47-8c65-c91bb4819fd6

📥 Commits

Reviewing files that changed from the base of the PR and between 5bbb04a and bd19168.

📒 Files selected for processing (40)
  • .dockerignore
  • .github/workflows/deploy-docker.yml
  • .github/workflows/deploy-ftp.yml
  • .github/workflows/deploy-ghpages.yml
  • .github/workflows/deploy-php.yml
  • .github/workflows/deploy-ssh.yml
  • .github/workflows/deploy.yml
  • docs/docs/advanced/docker.mdx
  • docs/docs/advanced/php.mdx
  • docs/docs/advanced/services.mdx
  • docs/docs/contribution/README.md
  • docs/docs/contribution/build-system.mdx
  • docs/docs/contribution/deployment.mdx
  • docs/docs/features/self-hosting.mdx
  • docs/sidebars.ts
  • functions/allowed-origin.ts
  • package.json
  • scripts/copy-php.js
  • scripts/utils.js
  • server/php/.htaccess
  • server/php/README.md
  • server/php/api/cors.php
  • server/php/api/share.php
  • server/php/broadcast/assets/code.html
  • server/php/broadcast/assets/index.html
  • server/php/broadcast/assets/result.html
  • server/php/broadcast/channel.php
  • server/php/broadcast/index.php
  • server/php/broadcast/state.php
  • server/php/config.sample.php
  • server/php/inc/config.php
  • server/php/inc/db.php
  • server/php/inc/utils.php
  • server/php/index.php
  • server/php/oembed.php
  • server/php/sandbox/index.php
  • server/php/vendor/lzstring.php
  • src/livecodes/services/allowed-origin.ts
  • src/livecodes/services/broadcast.ts
  • src/livecodes/services/sandbox.ts
💤 Files with no reviewable changes (1)
  • .github/workflows/deploy.yml

Comment thread .github/workflows/deploy-docker.yml Outdated
Comment thread .github/workflows/deploy-php.yml
Comment thread .github/workflows/deploy-php.yml
Comment thread scripts/utils.js Outdated
Comment thread server/php/.htaccess
Comment on lines +20 to +21
# block direct web access to server-side includes and config
RewriteRule ^(inc|vendor)/ - [F,L]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

inc/+vendor/ protection is bypassable and mod_rewrite-dependent.

Two gaps:

  • On the sandbox host, line 13 matches first with [L], so this deny rule never runs.
  • If mod_rewrite is unavailable (the IfModule silently skips the whole block), inc/config.php becomes directly requestable; a PHP parse-error or misconfigured handler then leaks MySQL credentials.

Prefer a module-independent deny in addition to the rewrite rule (e.g. server/php/inc/.htaccess with Require all denied, or <FilesMatch> deny in this 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 `@server/php/.htaccess` around lines 20 - 21, Add module-independent access
denial for the inc/ and vendor/ directories, using directory-level authorization
such as nested .htaccess files with Require all denied or an equivalent
FilesMatch rule. Keep the existing RewriteRule as defense in depth, and ensure
the denial applies even when an earlier rewrite rule matches or mod_rewrite is
unavailable.

Comment thread server/php/inc/utils.php
Comment thread server/php/index.php
Comment thread server/php/sandbox/index.php
Comment thread src/livecodes/services/allowed-origin.ts Outdated
Comment thread src/livecodes/services/broadcast.ts Outdated
Comment on lines +2 to +4
getUrl: () =>
process.env.SELF_HOSTED_BROADCAST === 'true'
? `https://${location.hostname}:${process.env.BROADCAST_PORT || '3030'}`
? `https://${location.hostname}:${process.env.BROADCAST_PORT}/`.replace(':443', '')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep a fallback broadcast port.

When BROADCAST_PORT is unset, Line 4 now returns https://<host>:undefined/, breaking self-hosted broadcast connections. Restore the previous 3030 fallback before removing port 443.

Proposed fix
-      ? `https://${location.hostname}:${process.env.BROADCAST_PORT}/`.replace(':443', '')
+      ? `https://${location.hostname}:${process.env.BROADCAST_PORT || '3030'}/`.replace(
+          ':443',
+          '',
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getUrl: () =>
process.env.SELF_HOSTED_BROADCAST === 'true'
? `https://${location.hostname}:${process.env.BROADCAST_PORT || '3030'}`
? `https://${location.hostname}:${process.env.BROADCAST_PORT}/`.replace(':443', '')
getUrl: () =>
process.env.SELF_HOSTED_BROADCAST === 'true'
? `https://${location.hostname}:${process.env.BROADCAST_PORT || '3030'}/`.replace(
':443',
'',
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/livecodes/services/broadcast.ts` around lines 2 - 4, Update the
self-hosted branch of getUrl to use port 3030 when BROADCAST_PORT is unset, then
apply the existing removal of the default HTTPS port 443 so the URL remains
valid in both cases.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (1)
server/php/inc/db.php (1)

35-43: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Do not suppress every database-creation failure.

catch (Throwable $e) swallows unexpected PDO::exec() errors as well as the expected “pre-created database” case. Catch only the expected permission failure (PDOException with code 1044 / SQLSTATE 42000), and rethrow anything else with the original exception preserved.

🤖 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 `@server/php/inc/db.php` around lines 35 - 43, Update the database-creation
try/catch around $pdo->exec() to catch PDOException specifically and suppress
only the expected access-denied condition identified by error code 1044 or
SQLSTATE 42000. Rethrow all other exceptions, preserving the original exception,
and retain the existing behavior for successful creation and an empty $dbName.
🧹 Nitpick comments (1)
server/php/inc/db.php (1)

47-63: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Move schema creation out of the request path.

getDb() is used by the share and broadcast endpoints, so every request executes two DDL statements. This adds latency and metadata-lock contention under concurrent traffic. Prefer a deployment/first-run migration and keep request-time initialization limited to selecting the configured database.

🤖 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 `@server/php/inc/db.php` around lines 47 - 63, Remove the share and
broadcast_channels CREATE TABLE statements from the request-time getDb()
initialization path. Move schema creation into the deployment or first-run
migration mechanism, while keeping getDb() limited to selecting and returning
the configured database connection.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/deploy-docker.yml:
- Line 36: The deploy script currently interpolates VPS_ENV into the remote
shell, allowing its contents to be interpreted as shell syntax. Update the
appleboy/ssh-action configuration to expose the value through env.VPS_ENV_RAW
and include VPS_ENV_RAW in envs, then write it using printf '%s\n'
"$VPS_ENV_RAW" instead of the interpolated echo in the deployment script.

In @.github/workflows/deploy-php.yml:
- Line 11: Update both actions/checkout configurations in deploy-php.yml,
including the deploy-ftp block at lines 11-11 and deploy-ssh block at lines
149-153, to use the same event-aware ref expression, such as
github.event.inputs.branch || github.ref_name. Remove the main fallback and
restore the missing ref in deploy-ssh.
- Around line 149-153: Update both “Checkout code” steps in the workflow to set
actions/checkout@v4’s persist-credentials option to false, ensuring subsequent
build and deploy commands do not retain Git credentials.
- Around line 57-64: Update the “Load PHP_ENV configuration” workflow step to
append SELF_HOSTED=true to process.env.GITHUB_ENV before npm run build:app,
using appendFileSync and preserving the existing DOCS_BASE_URL and PHP_ENV
handling.
- Around line 59-64: Remove the stale duplicate const env declaration from the
node -e script in the deploy workflow, keeping the initial env derived from
PHP_ENV_RAW and all subsequent uses of that variable unchanged.

In `@server/php/api/share.php`:
- Around line 45-50: Update the request-body handling around $maxBodyBytes and
file_get_contents('php://input') to reject a declared Content-Length exceeding
the limit before reading any body, then read at most $maxBodyBytes + 1 bytes
when the length is unknown or chunked. Preserve the existing 413 response for
bodies that exceed the limit after the bounded read.

In `@server/php/inc/db.php`:
- Around line 29-33: Update getDb so the PDO connection and all database
bootstrap steps, including USE and table creation, use a local connection
variable; assign it to the static cache only after every setup operation
succeeds, ensuring failed initialization is not reused by later calls.

In `@src/livecodes/services/broadcast.ts`:
- Line 4: Update the broadcast URL construction near the protocol/hostname
expression to default BROADCAST_PORT to 3030 when unset or empty, derive the
optional port suffix separately, and append it only when the effective port is
not 443. Remove the nested template that can produce an undefined or empty port.

---

Outside diff comments:
In `@server/php/inc/db.php`:
- Around line 35-43: Update the database-creation try/catch around $pdo->exec()
to catch PDOException specifically and suppress only the expected access-denied
condition identified by error code 1044 or SQLSTATE 42000. Rethrow all other
exceptions, preserving the original exception, and retain the existing behavior
for successful creation and an empty $dbName.

---

Nitpick comments:
In `@server/php/inc/db.php`:
- Around line 47-63: Remove the share and broadcast_channels CREATE TABLE
statements from the request-time getDb() initialization path. Move schema
creation into the deployment or first-run migration mechanism, while keeping
getDb() limited to selecting and returning the configured database connection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2c4c64b9-653e-4cd5-a80c-c77e6737dbcf

📥 Commits

Reviewing files that changed from the base of the PR and between bd19168 and 71410c9.

📒 Files selected for processing (12)
  • .github/workflows/deploy-docker.yml
  • .github/workflows/deploy-php.yml
  • scripts/utils.js
  • server/php/README.md
  • server/php/api/cors.php
  • server/php/api/share.php
  • server/php/broadcast/index.php
  • server/php/inc/db.php
  • server/php/index.php
  • server/php/sandbox/index.php
  • src/livecodes/services/allowed-origin.ts
  • src/livecodes/services/broadcast.ts

git pull origin main
git checkout "$DEPLOY_BRANCH"
git pull origin "$DEPLOY_BRANCH"
echo "${{ vars.VPS_ENV }}" > .env

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
if [ -f .github/workflows/deploy-docker.yml ]; then
  nl -ba .github/workflows/deploy-docker.yml | sed -n '1,90p'
else
  fd -a deploy-docker.yml .github 2>/dev/null || true
fi

echo
echo "== search VPS_ENV and ssh-action usages =="
rg -n "VPS_ENV|appleboy/ssh-action|envs:|shell:" .github || true

echo
echo "== behavioral shell expansion probe =="
python3 - <<'PY'
import re, subprocess
text = '''
              envs: DEPLOY_BRANCH
              script: |
              \t  echo "${{ vars.VPS_ENV }}" > .env\n'''
print("workflow value interpolation happens before action sends script; Python only models the source string structure, not GitHub runner substitution.")
PY

Repository: live-codes/livecodes

Length of output: 221


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
if [ -f .github/workflows/deploy-docker.yml ]; then
  awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/deploy-docker.yml | sed -n '1,90p'
else
  find .github -type f -name 'deploy-docker.yml' -print 2>/dev/null || true
fi

echo
echo "== search VPS_ENV and ssh-action usages =="
rg -n "VPS_ENV|appleboy/ssh-action|envs:|shell:" .github || true

echo
echo "== read-only YAML structure probe =="
python3 - <<'PY'
from pathlib import Path
p = Path(".github/workflows/deploy-docker.yml")
if p.exists():
    lines = p.read_text().splitlines()
    for i,l in enumerate(lines,1):
        if "vars.VPS_ENV" in l or "VPS_ENV_RAW" in l or "appleboy/ssh-action" in l or "envs:" in l:
            print(f"{i}: {l}")
PY

Repository: live-codes/livecodes

Length of output: 2290


Do not interpolate VPS_ENV into the remote shell script.

{{ vars.VPS_ENV }} is expanded before appleboy/ssh-action sends the script, so values containing command substitutions, backticks, or shell syntax can execute on the VPS instead of being written safely to .env. Add VPS_ENV_RAW to envs and write it with printf '%s\n' "$VPS_ENV_RAW"; set env.VPS_ENV_RAW: ${{ vars.VPS_ENV }}.

🧰 Tools
🪛 zizmor (1.28.0)

[info] 36-36: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 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/deploy-docker.yml at line 36, The deploy script currently
interpolates VPS_ENV into the remote shell, allowing its contents to be
interpreted as shell syntax. Update the appleboy/ssh-action configuration to
expose the value through env.VPS_ENV_RAW and include VPS_ENV_RAW in envs, then
write it using printf '%s\n' "$VPS_ENV_RAW" instead of the interpolated echo in
the deployment script.

Source: Linters/SAST tools

push:
branches:
- main
- deploy-to-php

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file excerpt =="
sed -n '1,220p' .github/workflows/deploy-php.yml 2>/dev/null || true

echo
echo "== git diff stat/name-status =="
git diff --stat HEAD~1..HEAD 2>/dev/null || true
git diff -- .github/workflows/deploy-php.yml 2>/dev/null | sed -n '1,240p' || true

echo
echo "== repository refs for relevant files (static) =="
if [ -f .github/workflows/deploy-php.yml ]; then
  echo "exists"
else
  echo "missing"
fi

Repository: live-codes/livecodes

Length of output: 8863


Use one event-aware deployment ref across both jobs.

Both deploy-ftp checkout configs fall back to main even when deploy-to-php is pushed, and deploy-ssh no longer checks out any branch. Use one expression such as github.event.inputs.branch || github.ref_name in both actions/checkout ref settings.

📍 Affects 1 file
  • .github/workflows/deploy-php.yml#L11-L11 (this comment)
  • .github/workflows/deploy-php.yml#L149-L153
🤖 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/deploy-php.yml at line 11, Update both actions/checkout
configurations in deploy-php.yml, including the deploy-ftp block at lines 11-11
and deploy-ssh block at lines 149-153, to use the same event-aware ref
expression, such as github.event.inputs.branch || github.ref_name. Remove the
main fallback and restore the missing ref in deploy-ssh.

Comment thread .github/workflows/deploy-php.yml
Comment thread .github/workflows/deploy-php.yml
Comment on lines +149 to +153
- name: Checkout code
if: ${{ steps.check.outputs.proceed == 'true' }}
uses: actions/checkout@v4
# with:
# ref: ${{ github.event.inputs.branch || 'main' }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
sed -n '1,220p' .github/workflows/deploy-php.yml | cat -n

echo
echo "== action pins and npm/build usage =="
rg -n "actions/checkout|npm ci|npm install|npm run|persist-credentials|permissions:|GITHUB_TOKEN|git push|php" .github/workflows/deploy-php.yml

Repository: live-codes/livecodes

Length of output: 12572


🌐 Web query:

actions/checkout v4 persist-credentials default true README

💡 Result:

For the actions/checkout action, the persist-credentials input defaults to true [1][2]. This setting determines whether the authentication token or SSH key used to fetch the repository is configured in the local git config [2][3]. When enabled (the default), this allows your workflow scripts to run authenticated git commands (such as git push or git fetch) against the repository [2][4]. The action automatically cleans up these credentials during the post-job phase [2][4]. If you wish to prevent these credentials from being configured in the local git config, you can explicitly set persist-credentials: false in your workflow step [2][4]. Note that newer versions of the action have improved security by storing these credentials in a separate file under $RUNNER_TEMP rather than directly in the repository's.git/config [4][5][6].

Citations:


Disable checkout credential persistence for both builds.

actions/checkout@v4 persists credentials by default, so the subsequent npm ci, npm run build:app, npm run copy-php, config generation, and deploy steps run with Git credentials available. Set persist-credentials: false on both Checkout code steps unless authenticated Git commands are later needed in the same job.

🧰 Tools
🪛 zizmor (1.28.0)

[warning] 149-153: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 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/deploy-php.yml around lines 149 - 153, Update both
“Checkout code” steps in the workflow to set actions/checkout@v4’s
persist-credentials option to false, ensuring subsequent build and deploy
commands do not retain Git credentials.

Source: Linters/SAST tools

Comment thread server/php/api/share.php
Comment on lines +45 to +50
$maxBodyBytes = 2 * 1024 * 1024; // adjust as appropriate
if ($rawBody !== false && strlen($rawBody) > $maxBodyBytes) {
http_response_code(413);
echo 'Payload Too Large!';
exit;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the request read before checking its size.

The 2 MiB check runs only after file_get_contents('php://input') has already buffered the entire request, so oversized requests can still exhaust PHP memory before returning 413. Reject an oversized Content-Length early and read at most $maxBodyBytes + 1 bytes for chunked/unknown-length requests.

🤖 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 `@server/php/api/share.php` around lines 45 - 50, Update the request-body
handling around $maxBodyBytes and file_get_contents('php://input') to reject a
declared Content-Length exceeding the limit before reading any body, then read
at most $maxBodyBytes + 1 bytes when the length is unknown or chunked. Preserve
the existing 413 response for bodies that exceed the limit after the bounded
read.

Comment thread server/php/inc/db.php
Comment on lines +29 to +33
try {
$pdo = new PDO("mysql:host=$host;port=$port;charset=utf8mb4", $user, $password, $options);
} catch (PDOException $e) {
throw new RuntimeException('Database connection failed.');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)db\.php$|server/php/inc' || true

echo
echo "db.php outline:"
if [ -f server/php/inc/db.php ]; then
  ast-grep outline server/php/inc/db.php --view expanded || true
  echo
  echo "db.php contents:"
  cat -n server/php/inc/db.php
fi

echo
echo "Search getDb/getConnection usages:"
rg -n "getDb|getConnection|class .*Db|new .*Db|PDO|PDOException|RuntimeException|USE |CREATE TABLE|mysqli|DB::|db::" server/php -S || true

Repository: live-codes/livecodes

Length of output: 4498


Cache the connection only after bootstrap succeeds.

$pdo is stored in the static cache immediately after connection, before USE and table creation complete. If bootstrap fails after getDb() returns once, later calls reuse the partially initialized PDO and skip setting up the database/tables. Keep connection initialization and setup local, and assign the static cache only after all setup succeeds.

🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 32-32: Define and throw a dedicated exception instead of using a generic one.

See more on https://sonarcloud.io/project/issues?id=live-codes_livecodes&issues=AZ-yrvg4pXJeL55yIG8X&open=AZ-yrvg4pXJeL55yIG8X&pullRequest=1019

🤖 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 `@server/php/inc/db.php` around lines 29 - 33, Update getDb so the PDO
connection and all database bootstrap steps, including USE and table creation,
use a local connection variable; assign it to the static cache only after every
setup operation succeeds, ensuring failed initialization is not reused by later
calls.

getUrl: () =>
process.env.SELF_HOSTED_BROADCAST === 'true'
? `https://${location.hostname}:${process.env.BROADCAST_PORT || '3030'}`
? `https://${location.hostname}${process.env.BROADCAST_PORT !== '443' ? `:${process.env.BROADCAST_PORT}` : ''}/`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore the default broadcast port.

When BROADCAST_PORT is unset, this expression generates https://<host>:undefined/; an empty value also produces an invalid trailing colon. Restore the 3030 fallback and derive the port suffix separately to avoid the nested template.

Proposed fix
 getUrl: () =>
-  process.env.SELF_HOSTED_BROADCAST === 'true'
-    ? `https://${location.hostname}${process.env.BROADCAST_PORT !== '443' ? `:${process.env.BROADCAST_PORT}` : ''}/`
-    : 'https://vps.livecodes.io:3030/',
+  (() => {
+    const broadcastPort = process.env.BROADCAST_PORT || '3030';
+    const portSuffix = broadcastPort === '443' ? '' : `:${broadcastPort}`;
+    return process.env.SELF_HOSTED_BROADCAST === 'true'
+      ? `https://${location.hostname}${portSuffix}/`
+      : 'https://vps.livecodes.io:3030/';
+  })(),
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 4-4: Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=live-codes_livecodes&issues=AZ-yrvbfpXJeL55yIG8V&open=AZ-yrvbfpXJeL55yIG8V&pullRequest=1019


[warning] 4-4: Refactor this code to not use nested template literals.

See more on https://sonarcloud.io/project/issues?id=live-codes_livecodes&issues=AZ-yrvbfpXJeL55yIG8W&open=AZ-yrvbfpXJeL55yIG8W&pullRequest=1019

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

In `@src/livecodes/services/broadcast.ts` at line 4, Update the broadcast URL
construction near the protocol/hostname expression to default BROADCAST_PORT to
3030 when unset or empty, derive the optional port suffix separately, and append
it only when the effective port is not 443. Remove the nested template that can
produce an undefined or empty port.

Source: Linters/SAST tools

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Important

The FTP job's "Load PHP_ENV configuration" step has a duplicate const env declaration that would throw a SyntaxError and break deployment.

Reviewed changes — 13 targeted fixes addressing prior review feedback: restored localhost origin validation, replaced fragile :443 string replacement with conditional port logic, HTML-encoded origin in index.php, switched CORS endpoint to use allowedOrigin() helper, added payload size limit to share endpoint, hardened json_encode in broadcast, added PDO timeout and error wrapping, fixed sandbox path traversal guard, fixed shell injection in Docker deploy, fixed HOST_NAME env handling, fixed README workflow filename, added value escaping to config generation, and fixed SSH job's PHP_ENV loading.

  • src/livecodes/services/allowed-origin.ts — restored localhost check and added HOST_NAME support
  • src/livecodes/services/broadcast.ts — replaced .replace(':443', '') with conditional port inclusion
  • server/php/index.php — HTML-encodes $origin in meta-tag and oembed link replacements
  • server/php/api/cors.php — switched from ad-hoc stripos check to allowedOrigin() helper
  • server/php/api/share.php — added $maxBodyBytes size limit with 413 response
  • server/php/broadcast/index.php — added JSON_INVALID_UTF8_SUBSTITUTE flag and is_string() guard
  • server/php/inc/db.php — added PDO::ATTR_TIMEOUT => 5 and try/catch with generic error
  • server/php/sandbox/index.php — added DIRECTORY_SEPARATOR to str_starts_with path traversal check
  • .github/workflows/deploy-docker.yml — branch passed via DEPLOY_BRANCH env var (quoted) instead of inline ${{ }} interpolation; added docker prune
  • .github/workflows/deploy-php.yml — SSH job's "Load PHP_ENV" and both jobs' "Create server config" now read from env: block; config c() function escapes values and skips empty secrets; FTP/SSH deploy triggers added to deploy-to-php branch
  • scripts/utils.js — fixed hostName to use correct || fallback instead of String() wrapping
  • server/php/README.md — corrected workflow filename to deploy-php.yml
  • .github/workflows/deploy-ftp.yml — added deploy-to-php push trigger

⚠️ FTP job "Load PHP_ENV" step has duplicate const env — SyntaxError at runtime

The deploy-ftp job's "Load PHP_ENV configuration" step (.github/workflows/deploy-php.yml) now declares env twice inside the same node -e script — once from the env-block-safe read (added by this commit) and once from the old inline interpolation (not removed). Redeclaring const in the same scope throws SyntaxError: Identifier 'env' has already been declared in Node.js, which would crash the step before any env vars are written to GITHUB_ENV, breaking FTP deployment.

The SSH job's equivalent step was fixed correctly (old line replaced with new line), but the FTP job's old line was left in place.

Technical details
# Duplicate const env in FTP "Load PHP_ENV"

## Affected sites
- `.github/workflows/deploy-php.yml:64-66` — two `const env = ...` declarations back-to-back, the second (`\`${{ vars.PHP_ENV }}\``) shadows and errors on the first

## Required outcome
- Remove the old inline-interpolated `const env = \`${{ vars.PHP_ENV }}\`.replace(/\r/g, '');` line, keeping only the safe `const env = (process.env.PHP_ENV_RAW || '').replace(/\r/g, '');` — mirroring the SSH job's correct fix

Pullfrog  | Fix it ➔View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Important

The "Create server config" step backtick escaping was removed, exposing ${{ vars.PHP_ENV }} to shell command substitution. The previous review's duplicate const env issue in "Load PHP_ENV" is still present.

Reviewed changes — 1 commit (68534fd) that changed only deploy-php.yml "Create server config" steps: removed backtick escaping from the const phpEnv template literal and changed the esc function's inner .replace() from double quotes to a template literal.

  • .github/workflows/deploy-php.yml — unescaped backticks in both "Create server config" steps

⚠️ Unescaped backticks expose ${{ vars.PHP_ENV }} to shell command substitution

Both the FTP and SSH "Create server config" steps now use unescaped backticks around ${{ vars.PHP_ENV }} inside a node -e "..." double-quoted shell string. The shell interprets unescaped backticks as command substitution delimiters, meaning the expanded multi-line PHP_ENV content will be executed as shell commands rather than passed as a JavaScript template literal. This can produce incorrect phpEnv values (empty/corrupted) or execute arbitrary commands from the Actions variable content.

Technical details
# Shell command substitution via unescaped backticks

## Affected sites
- `.github/workflows/deploy-php.yml:95` — FTP "Create server config" step
- `.github/workflows/deploy-php.yml:205` — SSH "Create server config" step

## Required outcome
- Escape the backticks so the shell treats them as literal characters, not command substitution delimiters: `\`${{ vars.PHP_ENV }}\``

ℹ️ Nitpicks

  • .github/workflows/deploy-php.yml:64-66 — the FTP "Load PHP_ENV" step still has a duplicate const env declaration (carried from previous commit). The const env = \${{ vars.PHP_ENV }}`.replace(...)on line 66 will throwSyntaxError: Identifier 'env' has already been declaredbecause line 64 already declaresconst env`. Remove line 66 and use only the env-block-safe read from line 64 — matching the SSH job's correct pattern.

Pullfrog  | Fix it ➔View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found — the unescaped backtick regression from 68534fd has been reverted.

Reviewed changes — 1 commit (d527349) that restores backtick escaping on the const phpEnv template literal in both "Create server config" steps and re-escapes the esc function's inner template literal, reverting the shell command substitution exposure introduced in 68534fd.

  • .github/workflows/deploy-php.yml — restored ``` escaping on template literals in both "Create server config" steps

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes — this incremental delta fixes the last remaining open issue from prior reviews: the duplicate const env declaration in the "Load PHP_ENV" step that was causing a SyntaxError in the FTP deploy job.

  • Removed duplicate const env in both deploy jobs — the FTP "Load PHP_ENV" step previously had a const env from process.env.PHP_ENV_RAW immediately shadowed by const env = \${{ vars.PHP_ENV }}`` on the next line. The SSH job had been fixed earlier; the FTP job now matches.
  • Added SELF_HOSTED=true to GITHUB_ENV before build — ensures npm run build:app sees the correct self-hosted flag in both FTP and SSH jobs.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
B Reliability Rating on New Code (required ≥ A)
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found. Previous review feedback addressed cleanly.

Reviewed changes — one clean-up commit with targeted improvements to the PHP server code and deploy workflow:

  • Added SELF_HOSTED to server config generation — the deploy workflow now writes 'SELF_HOSTED' => 'true' to the generated PHP config.php, consistent with the SELF_HOSTED=true already added to the build environment in the prior commit.
  • Removed LOWER() from share ID primary-key lookupsshare.php and importProject in utils.php now use exact id = ? comparisons instead of LOWER(id) = LOWER(?). Generated IDs use only lowercase characters, so the indexed primary-key lookups are safe.
  • Added CF-IPCountry to analytics log — server-side analytics now include the Cloudflare CF-IPCountry header for geographic attribution.
  • License and documentation touch-ups — corrected lzstring.php license attribution, added explicit license URL, and updated .htaccess comment.

ℹ️ Nitpicks

  • The push trigger in .github/workflows/deploy-php.yml includes deploy-to-php alongside main. This looks like a development convenience — the trigger should be removed before merging.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants