Repository navigation
feat(nginx): a Front controller rule sets where requests that match no file go — GH #1999 - #2005
Merged
Merged
Conversation
…o file go — GH #1999 An app with its own router (lxsdevcode's admin subdomain) needs the PHP fallback of the site's location / changed, e.g. try_files $uri $uri/ /index.php?mod=$uri&$args; A raw location / drops the panel's PHP locations (RootOverridden), and a catch-all rewrite also sends CSS/JS/images to PHP. New typed Rule Builder kind front_controller (script + query), for admins and, behind tenant domain options, for owners: - internal/frontcontroller: the shared grammar. Script is a .php path from the docroot (no .. or //); the query takes only [A-Za-z0-9_.-=&/%] literals and $args $document_uri $is_args $query_string $request_uri $uri, so the fallback stays one nginx token. - API: validated on save, at most one per domain, in the tenant-safe subset. - nginxrules.Compile renders nothing for it; FrontController sends the fallback to the agent as php_fallback. - Agent: vhostData.TryFilesFallback re-validates at render time and falls back to /index.php?$query_string. With no rule the vhost is byte for byte unchanged (tested across vhost shapes). PHP locations are untouched. - nginx import maps location / { try_files $uri $uri/ <script>?<query>; } onto the rule; other root locations stay a security warning. - UI: Front Controller card with a live try_files preview; the picker hides it once a domain has one, and an imported one replaces the existing rule. - Docs: user Domains page, server settings, nginx runbook, ADR-0169 addendum. Claude-Session: https://claude.ai/code/session_0173PcNd4h6NceYPc4FuhvXj
…and document the cache and reverse-proxy interactions — GH #1999 TryFilesFallback is now pure; writeVhost logs the rejected value once instead of once per server block. Docs: a non-default query sends routed pages to PHP with a query string, so they bypass the page cache (fail-closed); a domain served entirely by a reverse proxy ignores the rule, like Deny paths and Static cache. Claude-Session: https://claude.ai/code/session_0173PcNd4h6NceYPc4FuhvXj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
GH #1999
What
lxsdevcode runs an app with its own router on a subdomain. Requests that match no file must reach PHP as
try_files $uri $uri/ /index.php?mod=$uri&$args;, and static files must still be served directly. Today they can't get that:location /in the directives drops the panel's PHP locations (RootOverridden).rewritealso sends CSS, JS and images to PHP.This adds a typed Rule Builder kind, Front Controller (
front_controller: script + query). Admins get it, and so do owners when tenant domain options is on (same gate as Deny paths and Static cache).internal/frontcontroller. It is checked at save in the API and again at render in the agent..phppath from the docroot, with no..or//.[A-Za-z0-9_.-=&/%]literals plus$args $document_uri $is_args $query_string $request_uri $uri. A variable name is read to its end, so$urixis rejected.tenantSafeNginxRuleTypes.nginxrules.Compilerenders nothing for the rule; a secondlocation /would tripRootOverridden.nginxrules.FrontControllersends the fallback as the newphp_fallbackparam.{{.TryFilesFallback}}. The method re-validates and falls back to/index.php?$query_string, so an invalid value never reaches nginx, and avhostDatathat never set it renders the default. PHP locations are untouched.location / { try_files $uri $uri/ <script>?<query>; }maps onto the rule, so the reporter's exact Plesk block imports. Any other root location stays a security warning.try_filespreview.Risk
GitNexus detect-changes rates this critical. That is because
vhostTemplatetouches every vhost. Mitigations:TestVhost_NoFrontControllerIsByteIdenticalrenders five vhost shapes (php, static, php+cache, php+intercept, no cert). Each one is byte-identical to the template with the old fallback written out literally.Domainstruct hunks that showed up came from gofmt realigning existing fields; I reverted them.DisallowUnknownFields, so a new panel talking to an old agent still renders, just without the rule.Known interactions (documented, not changed)
location = /index.phpwith a non-empty$query_string. Then$jabali_qs_kindisother, so routed pages bypass the page cache. This fails closed. Existing files are unaffected.location /is replaced (a reverse-proxy domain or an admin rawlocation /) ignores the rule, the same as Deny paths and Static cache.Verification
internal/frontcontroller,nginxrules,nginximport,api,reconcilerandpanel-agent/internal/commandsall pass.src/shellspasses (74 files, 329 tests), andtscis clean..., one per domain, tenant subset, API validate,FrontControllervalidate, agent re-validate (before and after the logging refactor), empty → default, template token, reconciler param, import root branch, import default note.gh1999tand domaingh1999t.test, driven throughdomain.createwithphp_fallback:/users/edit/123→mod=/users/edit/123;/dashboard→mod=/dashboard;/users?page=2&sort=name→mod=/users, with the args passed through./assets/css/app.cssis served directly (body{color:red}).nginx -tpasses./index.php?a=1; return 302 https://evil.testis logged and rendered as the default, byte-identical to the reconciler's render.origin/main(no-op), and tests re-run.Branch
gh1999-front-controller-rule:d29a7decc,a8aa24281.https://claude.ai/code/session_0173PcNd4h6NceYPc4FuhvXj