Browse documentationOpen

byrcsc/laravel-comments · 1.x

Rendering and safety.

What the package treats as untrusted, what it deliberately does not do for you, and where each trust boundary sits.

The package stores what it was given and hands it back unchanged. That is a design decision with consequences, and this page is the list of them.

The body is stored verbatim

No sanitization, no markdown, no HTML filtering, no trimming. A body containing <script>alert(1)</script> is stored as exactly that, and read back as exactly that.

Sanitizing on the way in would be the package deciding what your application renders, and it would be wrong for at least half of them: a code-review tool wants angle brackets intact, a support desk wants the raw text for an export, and a markdown site wants the source rather than someone else's idea of safe HTML.

So escaping is yours, on output:

{{ $comment->body }}

Blade escapes this. {!! !!} does not — and if you render markdown or HTML, sanitize before you do.

Untrusted input

Treat all four of these as input from a stranger, because that is what they are:

ValueWhy
$comment->bodyStored verbatim, never inspected
$comment->guest_nameWhatever the form sent, verified by nothing
$comment->guest_emailNever verified, never confirmed, never mailed
$attachment->nameWhatever the caller recorded, defaults to a basename

The revision bodies carry the same warning as the comment body: they are prior versions of untrusted input.

The shipped mail view escapes everything it interpolates. If you publish and edit it, keep it that way.

The guest email is not a mailbox

The package never sends anything to guest_email. It is recorded because your application asked for it, and it is not treated as a verified address anywhere — the reply notification skips guest-authored parents outright, whatever channel is configured.

Verifying it, if you want to, is your application's job, and so is deciding whether to show it at all.

Attachments: the file is yours

An attachment row is metadata. The package never opens the file, never checks it exists, never validates its type, and never deletes it.

That means every one of these is your application's:

  • Validating the upload. MIME type, size, extension, and content checks all happen before attach() is called. The row records what you tell it, and a row claiming application/pdf proves nothing about the bytes.
  • Serving the file. The package builds no URLs and defines no routes. A signed URL, a controller with an authorization check, or a public disk — all yours.
  • Authorizing the download. An attachment on a pending or rejected comment is still a row with a readable path. Nothing hides it.
  • Deleting the bytes. Removing a row fires AttachmentRemoved; that is where the delete belongs. See attachments.

Guest writes are unthrottled

The package ships no rate limiting. An application that opens create to guests in its policy owns the throttling, the spam checks, and the captcha, because those are the checks that actually matter for anonymous writes and none of them can be answered generically.

The package does give you the seams: guests default to pending so nothing anonymous is visible until somebody looks at it, and markAsSpam() is where your own detection records its verdict.

Status is not access control

Approving a comment does not publish it and rejecting one does not hide it. A rejected comment is a readable row, and Comment::query()->get() will return it.

Whatever you show a visitor comes from your query:

$post->comments()->approved()->topLevel()->get();

Forgetting approved() on a public page shows pending and rejected comments to everybody. That is the single easiest mistake to make with this package.

The package never authorizes itself

approve(), pin(), react(), edit(), and attach() all work with nobody logged in and the harshest policy the framework allows registered. They are engine methods, not endpoints. See authorization.

Enum casting is not validation

status casts to CommentStatus, so a status column holding an unknown string will fail when the model hydrates it — not when it was written by raw SQL. Use CommentStatus::values() in your form rules, and prefer the transition methods over writing the column directly.