Conversation
There was a problem hiding this comment.
Pull request overview
Adds a full CakePHP “testapp” demo application to validate the plugin’s OpenTelemetry instrumentation end-to-end (CRUD + DB + error handling), including local Docker tooling and Azure (Bicep) deployment docs.
Changes:
- Adds a CakePHP 5 demo app (Posts/Comments CRUD) wired to the OTel plugin and middleware.
- Adds local dev environment via Docker Compose (PostgreSQL + Jaeger) and production/dev Dockerfiles.
- Adds Azure IaC (Bicep) and step-by-step specs for infra, deployment, and verification.
Reviewed changes
Copilot reviewed 89 out of 128 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents the included testapp demo app and quick-start steps |
| README.ja.md | Japanese README updates incl. demo app section |
| testapp/.gitignore | Ignores app-local config, vendor, tmp/logs, IDE artifacts for the demo app |
| testapp/Dockerfile | Production image build for demo app (PHP-FPM + Nginx + supervisord) |
| testapp/Dockerfile.dev | Dev image build for demo app (volume-mounted source) |
| testapp/README.md | Demo app README (local + Azure deployment) |
| testapp/compose.yaml | Local dev stack (app + postgres + jaeger) |
| testapp/composer.json | Demo app dependencies (CakePHP + migrations + OTel plugin) and scripts |
| testapp/composer.lock | Locked dependency graph for demo app |
| testapp/index.php | Entrypoint delegating to webroot/index.php |
| testapp/phpcs.xml | PHPCS ruleset for demo app |
| testapp/phpstan.neon | PHPStan config for demo app |
| testapp/phpunit.xml.dist | PHPUnit configuration for demo app |
| testapp/psalm.xml | Psalm configuration for demo app |
| testapp/supervisord.pid | Supervisord PID file (currently committed) |
| testapp/bicep/main.bicep | Main Azure deployment template wiring modules together |
| testapp/bicep/main.json | Generated ARM JSON output for the Bicep template |
| testapp/bicep/parameters.bicepparam | Parameter file for Bicep deployment |
| testapp/bicep/modules/acr.bicep | Azure Container Registry module |
| testapp/bicep/modules/ca.bicep | Azure Container App module incl. env vars (DB + OTel) |
| testapp/bicep/modules/cae.bicep | Container Apps Environment module |
| testapp/bicep/modules/log.bicep | Log Analytics + Application Insights module |
| testapp/bicep/modules/postgres.bicep | PostgreSQL Flexible Server module |
| testapp/bin/bash_completion.sh | Bash completion script for Cake console |
| testapp/bin/cake | Shell wrapper for Cake console |
| testapp/bin/cake.bat | Windows batch wrapper for Cake console |
| testapp/bin/cake.php | PHP entry for Cake console runner |
| testapp/config/.env.example | Example env vars for local configuration (incl. base URL + salt placeholders) |
| testapp/config/app.php | Base CakePHP app config (incl. fullBaseUrl env + logging) |
| testapp/config/app_local.example.php | Example local overrides (DB + debug + salt) |
| testapp/config/bootstrap.php | App bootstrap incl. fullBaseUrl handling and framework setup |
| testapp/config/paths.php | Defines core path constants |
| testapp/config/plugins.php | Plugin load configuration (DebugKit/Bake/Migrations) |
| testapp/config/routes.php | Routes (maps / to Posts index; includes Pages routes and fallbacks) |
| testapp/config/Migrations/20260328000000_CreatePosts.php | Migration to create posts table |
| testapp/config/Migrations/20260328000001_CreateComments.php | Migration to create comments table (FK to posts) |
| testapp/config/Migrations/schema-dump-default.lock | Schema dump lock file |
| testapp/config/schema/i18n.sql | Optional i18n schema |
| testapp/config/schema/sessions.sql | Optional DB sessions schema |
| testapp/docker/nginx/default.conf | Nginx vhost config for CakePHP front controller |
| testapp/docker/php/php-production.ini | Production PHP settings (opcache, expose_php off) |
| testapp/docker/php/www-overrides.conf | PHP-FPM pool override (clear_env) |
| testapp/docker/supervisor/supervisord.conf | Supervisord config to run php-fpm + nginx |
| testapp/plugins/.gitkeep | Keeps plugins dir in repo |
| testapp/resources/.gitkeep | Keeps resources dir in repo |
| testapp/specs/00-overview.md | Overall project overview/spec |
| testapp/specs/01-application.md | Step 1 spec: app construction (models/controllers/views/OTel) |
| testapp/specs/02-docker.md | Step 2 spec: dockerization approach |
| testapp/specs/03-infrastructure.md | Step 3 spec: Azure infra via Bicep |
| testapp/specs/04-deployment.md | Step 4 spec: CI/CD + deployment flow |
| testapp/specs/05-verification.md | Step 5 spec: OTel verification steps (Jaeger + App Insights) |
| testapp/src/Application.php | App bootstrap + middleware queue (ErrorHandler + OTel + HostHeader + routing + CSRF) |
| testapp/src/Console/Installer.php | Composer install hooks (create app_local, perms, salt) |
| testapp/src/Controller/AppController.php | Base controller loading Flash component |
| testapp/src/Controller/CommentsController.php | Comment add/delete actions |
| testapp/src/Controller/ErrorController.php | Error controller wiring error template path |
| testapp/src/Controller/PagesController.php | Static pages controller w/ traversal protection |
| testapp/src/Controller/PostsController.php | Posts CRUD actions with pagination |
| testapp/src/Controller/Component/.gitkeep | Keeps Component dir in repo |
| testapp/src/Middleware/HostHeaderMiddleware.php | Validates Host header against configured base URL in production |
| testapp/src/Model/Behavior/.gitkeep | Keeps Behavior dir in repo |
| testapp/src/Model/Entity/.gitkeep | Keeps Entity dir in repo |
| testapp/src/Model/Entity/Comment.php | Comment entity accessible fields |
| testapp/src/Model/Entity/Post.php | Post entity accessible fields |
| testapp/src/Model/Table/.gitkeep | Keeps Table dir in repo |
| testapp/src/Model/Table/CommentsTable.php | Comments table config + validation + belongsTo Posts |
| testapp/src/Model/Table/PostsTable.php | Posts table config + validation + hasMany Comments |
| testapp/src/View/AjaxView.php | AJAX view that switches layout and response type |
| testapp/src/View/AppView.php | Base AppView |
| testapp/src/View/Cell/.gitkeep | Keeps View Cell dir in repo |
| testapp/src/View/Helper/.gitkeep | Keeps View Helper dir in repo |
| testapp/templates/Error/error400.php | 400 error template |
| testapp/templates/Error/error500.php | 500 error template |
| testapp/templates/Pages/home.php | Debug home page with environment checks and DebugKit/DB status |
| testapp/templates/Posts/add.php | Post create form template |
| testapp/templates/Posts/edit.php | Post edit form template |
| testapp/templates/Posts/index.php | Post list template with paginator |
| testapp/templates/Posts/view.php | Post detail template + comments listing + comment form |
| testapp/templates/cell/.gitkeep | Keeps templates/cell dir in repo |
| testapp/templates/element/flash/default.php | Default flash element |
| testapp/templates/element/flash/error.php | Error flash element |
| testapp/templates/element/flash/info.php | Info flash element |
| testapp/templates/element/flash/success.php | Success flash element |
| testapp/templates/element/flash/warning.php | Warning flash element |
| testapp/templates/email/html/default.php | HTML email template |
| testapp/templates/email/text/default.php | Text email template |
| testapp/templates/layout/ajax.php | Ajax layout |
| testapp/templates/layout/default.php | Default layout (nav + container + assets) |
| testapp/templates/layout/error.php | Error layout |
| testapp/templates/layout/email/html/default.php | HTML email layout wrapper |
| testapp/templates/layout/email/text/default.php | Text email layout wrapper |
| testapp/tests/bootstrap.php | Test bootstrap (autoload, app bootstrap, migrations, fixed time/session id) |
| testapp/tests/schema.sql | Placeholder schema file for tests (if not using migrations) |
| testapp/tests/Fixture/.gitkeep | Keeps Fixture dir in repo |
| testapp/tests/TestCase/ApplicationTest.php | Tests for bootstrap/plugin loading + middleware order |
| testapp/tests/TestCase/Controller/PagesControllerTest.php | Integration tests for Pages controller and CSRF behavior |
| testapp/tests/TestCase/Controller/Component/.gitkeep | Keeps controller component tests dir |
| testapp/tests/TestCase/Model/Behavior/.gitkeep | Keeps model behavior tests dir |
| testapp/tests/TestCase/View/Helper/.gitkeep | Keeps view helper tests dir |
| testapp/webroot/.htaccess | Rewrite rule for Apache setups |
| testapp/webroot/favicon.ico | App favicon |
| testapp/webroot/index.php | Front controller for all web requests |
| testapp/webroot/js/.gitkeep | Keeps webroot JS dir |
| testapp/webroot/css/cake.css | App UI styling and overrides |
| testapp/webroot/css/fonts.css | Raleway font-face declarations |
| testapp/webroot/css/home.css | Home page styling |
| testapp/webroot/css/milligram.min.css | Milligram CSS (minified) |
| testapp/webroot/css/normalize.min.css | Normalize CSS (minified) |
| testapp/webroot/font/Raleway-License.txt | Font license file |
| testapp/webroot/font/cakedingbats-webfont.eot | Cake dingbats font |
| testapp/webroot/font/cakedingbats-webfont.svg | Cake dingbats font |
| testapp/webroot/font/cakedingbats-webfont.ttf | Cake dingbats font |
| testapp/webroot/font/cakedingbats-webfont.woff | Cake dingbats font |
| testapp/webroot/font/cakedingbats-webfont.woff2 | Cake dingbats font |
| testapp/webroot/font/raleway-400-cyrillic-ext.woff2 | Raleway font subset |
| testapp/webroot/font/raleway-400-cyrillic.woff2 | Raleway font subset |
| testapp/webroot/font/raleway-400-latin-ext.woff2 | Raleway font subset |
| testapp/webroot/font/raleway-400-latin.woff2 | Raleway font subset |
| testapp/webroot/font/raleway-400-vietnamese.woff2 | Raleway font subset |
| testapp/webroot/font/raleway-700-cyrillic-ext.woff2 | Raleway font subset |
| testapp/webroot/font/raleway-700-cyrillic.woff2 | Raleway font subset |
| testapp/webroot/font/raleway-700-latin-ext.woff2 | Raleway font subset |
| testapp/webroot/font/raleway-700-latin.woff2 | Raleway font subset |
| testapp/webroot/font/raleway-700-vietnamese.woff2 | Raleway font subset |
| testapp/webroot/img/cake-logo.png | CakePHP logo asset |
| testapp/webroot/img/cake.icon.png | CakePHP icon asset |
| testapp/webroot/img/cake.logo.svg | CakePHP SVG logo asset |
| testapp/webroot/img/cake.power.gif | CakePHP “powered by” asset |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| { name: 'OTEL_TRACES_EXPORTER', value: 'otlp' } | ||
| { name: 'OTEL_EXPORTER_OTLP_PROTOCOL', value: 'http/protobuf' } | ||
| { name: 'OTEL_EXPORTER_OTLP_ENDPOINT', value: 'https://japaneast-0.in.applicationinsights.azure.com/v2/track' } | ||
| { name: 'OTEL_EXPORTER_OTLP_HEADERS', value: 'x-ms-client-request-id=${appInsightsConnectionString}' } | ||
| { name: 'OTEL_LOGS_EXPORTER', value: 'otlp' } | ||
| { name: 'OTEL_METRICS_EXPORTER', value: 'none' } |
There was a problem hiding this comment.
The OpenTelemetry environment variables here don’t appear to match the configured OTLP exporter/protocol:
OTEL_EXPORTER_OTLP_PROTOCOLis set tohttp/protobuf, butOTEL_EXPORTER_OTLP_ENDPOINTis set to an Application Insights/v2/trackendpoint, which is not an OTLP HTTP endpoint.OTEL_EXPORTER_OTLP_HEADERSis set tox-ms-client-request-id=<connection-string>, but the OTLP exporter expects auth/connection headers relevant to the OTLP endpoint; a connection string also contains semicolons which can break header parsing.
This will likely prevent traces/logs from being exported. Consider using the correct OTLP ingestion endpoint + required authorization header(s), or switch to Azure Monitor’s supported OpenTelemetry distribution/configuration for Application Insights ingestion.
| { name: 'OTEL_TRACES_EXPORTER', value: 'otlp' } | |
| { name: 'OTEL_EXPORTER_OTLP_PROTOCOL', value: 'http/protobuf' } | |
| { name: 'OTEL_EXPORTER_OTLP_ENDPOINT', value: 'https://japaneast-0.in.applicationinsights.azure.com/v2/track' } | |
| { name: 'OTEL_EXPORTER_OTLP_HEADERS', value: 'x-ms-client-request-id=${appInsightsConnectionString}' } | |
| { name: 'OTEL_LOGS_EXPORTER', value: 'otlp' } | |
| { name: 'OTEL_METRICS_EXPORTER', value: 'none' } | |
| { name: 'APPLICATIONINSIGHTS_CONNECTION_STRING', value: appInsightsConnectionString } |
| docker compose up -d | ||
| ``` | ||
|
|
||
| `http://localhost:8765` でアクセス。 |
There was a problem hiding this comment.
The documented local URL doesn’t match the Docker Compose port mapping. compose.yaml publishes the app on http://localhost:8080, but this README says http://localhost:8765.
Please update the README to reflect the actual exposed port (or adjust compose.yaml if 8765 is intended).
| `http://localhost:8765` でアクセス。 | |
| `http://localhost:8080` でアクセス。 |
| * You should treat it as extremely sensitive data. | ||
| */ | ||
| 'Security' => [ | ||
| 'salt' => env('SECURITY_SALT', '8e367f0c2c16e5179e1720da4e18b791b11b5e3c1bc156592f8298d042d8367f'), |
There was a problem hiding this comment.
Installer::setSecuritySaltInFile() replaces the __SALT__ placeholder in config/app_local.php, but config/app_local.example.php currently hardcodes a default salt value instead of containing the __SALT__ placeholder. As a result, new installs that rely on the installer will keep the same shared salt unless SECURITY_SALT is set externally.
To keep the installer effective (and avoid insecure shared defaults), consider changing the example to use the __SALT__ placeholder (or otherwise ensure the installer updates the salt deterministically).
| 'salt' => env('SECURITY_SALT', '8e367f0c2c16e5179e1720da4e18b791b11b5e3c1bc156592f8298d042d8367f'), | |
| 'salt' => env('SECURITY_SALT', '__SALT__'), |
| RUN composer install --no-dev --optimize-autoloader --no-interaction | ||
|
|
||
| COPY . . |
There was a problem hiding this comment.
The build stage runs composer install before the application source (including src/Console/Installer.php and config/app_local.example.php) is copied into the image. Because composer.json defines post-install-cmd as App\Console\Installer::postInstall, this is very likely to fail (class/file not found) or skip generating config/app_local.php.
Consider either copying the minimal app files needed by the scripts (e.g., src/Console/Installer.php + config/app_local.example.php) before composer install, or running composer install --no-scripts in the cached layer and executing the necessary setup steps after COPY . ..
| RUN composer install --no-dev --optimize-autoloader --no-interaction | |
| COPY . . | |
| RUN composer install --no-dev --optimize-autoloader --no-interaction --no-scripts | |
| COPY . . | |
| RUN composer run-script post-install-cmd --no-interaction |
| $fullBaseUrl = Configure::read('App.fullBaseUrl'); | ||
| if (!$fullBaseUrl) { | ||
| throw new InternalErrorException( | ||
| 'SECURITY: App.fullBaseUrl is not configured. ' . | ||
| 'This is required in production to prevent Host Header Injection attacks. ' . | ||
| 'Set APP_FULL_BASE_URL environment variable or configure App.fullBaseUrl in config/app.php', | ||
| ); | ||
| } | ||
|
|
||
| $configuredHost = parse_url($fullBaseUrl, PHP_URL_HOST); | ||
| $requestHost = $request->getUri()->getHost(); | ||
|
|
||
| if ($configuredHost && $requestHost && strtolower($configuredHost) !== strtolower($requestHost)) { | ||
| throw new BadRequestException( | ||
| 'Invalid Host header. Request host does not match configured application host.', | ||
| ); | ||
| } |
There was a problem hiding this comment.
If App.fullBaseUrl is misconfigured (e.g., missing scheme) parse_url($fullBaseUrl, PHP_URL_HOST) can return null/false. In that case the middleware silently skips host validation and allows requests through, which undermines the intended Host header injection protection.
Suggestion: treat a missing/invalid host from parse_url() as a configuration error (throw InternalErrorException), and only proceed when a valid configured host is present.
| public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface | ||
| { | ||
| if (Configure::read('debug')) { | ||
| return $handler->handle($request); | ||
| } | ||
|
|
||
| $fullBaseUrl = Configure::read('App.fullBaseUrl'); | ||
| if (!$fullBaseUrl) { | ||
| throw new InternalErrorException( | ||
| 'SECURITY: App.fullBaseUrl is not configured. ' . | ||
| 'This is required in production to prevent Host Header Injection attacks. ' . | ||
| 'Set APP_FULL_BASE_URL environment variable or configure App.fullBaseUrl in config/app.php', | ||
| ); | ||
| } | ||
|
|
||
| $configuredHost = parse_url($fullBaseUrl, PHP_URL_HOST); | ||
| $requestHost = $request->getUri()->getHost(); | ||
|
|
||
| if ($configuredHost && $requestHost && strtolower($configuredHost) !== strtolower($requestHost)) { | ||
| throw new BadRequestException( | ||
| 'Invalid Host header. Request host does not match configured application host.', | ||
| ); | ||
| } | ||
|
|
||
| return $handler->handle($request); | ||
| } |
There was a problem hiding this comment.
This middleware is security-critical, but there are no tests covering its behavior (e.g., throws when debug is false and App.fullBaseUrl is missing, rejects mismatched Host headers, allows matching hosts). Adding a focused test case would help prevent regressions and make the expected production behavior explicit.
| $lines = explode("\n", $content); | ||
|
|
||
| foreach ($lines as $line) : | ||
| echo '<p> ' . $line . "</p>\n"; | ||
| endforeach; |
There was a problem hiding this comment.
$line is inserted into HTML without escaping. If $content can include user-provided data, this enables HTML/script injection in HTML emails.
Consider escaping each line (e.g., using h($line)) before wrapping it in <p>, or otherwise ensuring the content is sanitized/HTML-safe by construction.
| @@ -0,0 +1 @@ | |||
| 1 | |||
There was a problem hiding this comment.
A PID file should not be committed to the repository. Keeping supervisord.pid under version control can confuse local/dev/prod process supervision and is likely to be overwritten at runtime.
Remove this file from the repo and add it to .gitignore (or configure supervisord to write its PID under a runtime directory like /var/run).
No description provided.