Skip to content

feat: opt-in flight.views.restrict_to_path to keep templates inside the views path - #729

Merged
n0nag0n merged 4 commits into
masterfrom
fix/view-template-containment
Oct 7, 2026
Merged

n0nag0n merged 4 commits into
masterfrom
fix/view-template-containment

Conversation

@ambrose5773

@ambrose5773 ambrose5773 commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds flight.views.restrict_to_path (off by default). Nothing changes for existing apps until they opt in:

Flight::set('flight.views.restrict_to_path', true);

Engine applies that onto View::$restrictToPath when the view is created, the same way it already applies flight.views.path and flight.views.extension.

With it on, render(), fetch(), and exists() only accept template files whose real path sits inside the configured views path (including symlink targets). A blocked file throws Template file is outside the views path.; a missing file still throws Template file not found: .... getTemplate() itself is unchanged and still returns absolute paths. exists() never throws.

Existing ViewTest cases are untouched; this PR only adds tests. Direct $view->restrictToPath = true still works for unit tests and custom View instances.

The default could flip to on in the next major version.

Reported by Arekkusul.

Thanks @enlivenapp — the first cut changed getTemplate() / exists() behavior. This version does not.

Follow-ups: docs#51, skeleton#12.

Test plan

  • Original ViewTest unchanged (diff vs master only adds lines in ViewTest; Engine gets the new config key)
  • Default: absolute path outside the views path still renders; exists() is true
  • Opt-in via Flight::set('flight.views.restrict_to_path', true) applies to view()->restrictToPath
  • Opt-in: relative/absolute/symlink escapes rejected; missing file keeps "not found"
  • Full PHPUnit suite green locally

Reject absolute template paths and any resolved path outside the configured views directory.
Normalize both separators before the containment check, and build the regression next to the views directory so it stays on the same drive.
@enlivenapp

Copy link
Copy Markdown

Howdy folks,

I saw this come across the Discord channel and thought I'd pop over and toss in my 25 cents (inflation, ya know).

This would change getTemplate() from returning full paths to throwing, and would flip a test that checks the old behavior. Anyone on ^3.0 might get blindsided if this is a patch (an assumption since it's fix: in the commit.)

exists() looks like it would throw, which would break if ($view->exists($f)).

A missing views folder and a disallowed path would give the same message, Template path is not allowed.

Pubvana (my cms) overrides flight\template\View::getTemplate() currently so this doesn't really break anything for me as written but if flight\template\View::render() or flight\template\View::fetch() change that'd blow me up since I do call them with absolute paths.

Anyway, figured I'd toss that stuff out for consideration. ☮️

Restore getTemplate() and its existing test exactly as on master. Default behavior is unchanged: absolute paths still render and exists() never throws. With restrictToPath on, render(), fetch() and exists() only accept files that resolve inside the views path. A blocked file has its own error message, separate from "not found".
@ambrose5773 ambrose5773 changed the title fix: keep view templates inside the views directory feat: opt-in View::$restrictToPath to keep templates inside the views path Oct 5, 2026
Expose the opt-in as a Flight::set() key in the flight.views.* family. Engine applies it to View::$restrictToPath when the view is created, same as path and extension. Default remains false.
@ambrose5773 ambrose5773 changed the title feat: opt-in View::$restrictToPath to keep templates inside the views path feat: opt-in flight.views.restrict_to_path to keep templates inside the views path Oct 5, 2026
@ambrose5773

Copy link
Copy Markdown
Collaborator Author

Thanks @enlivenapp — really appreciate you catching this, and the Discord heads-up. You were right on every point.

The first version of this PR changed getTemplate() / exists() behavior and flipped an existing test. That was a mistake on my part for a fix: on ^3.0. Sorry about the churn for anyone watching the branch (Pubvana included).

It is reworked now so default public behavior matches master:

  • getTemplate() is unchanged and still returns absolute paths.
  • Absolute paths still work with render() / fetch().
  • exists() still returns a bool and never throws.
  • Existing tests were restored; only new tests were added.

The hardening is opt-in via the usual engine config family:

Flight::set('flight.views.restrict_to_path', true);

Engine applies that onto View::$restrictToPath when the view is created (same pattern as flight.views.path / flight.views.extension). Docs and skeleton follow-ups: flightphp/docs#51 and flightphp/skeleton#12.

Thanks again for the careful review — it made this a much better change.

@n0nag0n
n0nag0n merged commit 06f10a7 into master Oct 7, 2026
21 checks passed
@n0nag0n
n0nag0n deleted the fix/view-template-containment branch October 7, 2026 13:30
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.

3 participants