rumenx/php-sitemap
Rumenx PHP Sitemap generates standards-compliant XML sitemaps and sitemap indexes in PHP. Build URL sets, add images, videos, news, and hreflang alternates, then output plain or gzipped XML through a simple, extensible API for small sites or large dynamic applications.
What it is: A framework-agnostic XML sitemap generator with first-party Laravel and Symfony bindings. Functionally it sits at the leaf of the dependency graph — it reads URL data and emits sitemap.xml / sitemap index files. Nothing depends on it, and nothing we build should depend on it semantically.
Change since last assessment — v1.0.4 (security patch): The package shipped a fix rejecting path traversal in Sitemap::store filenames (PR #213). This materially changes the risk picture in three ways:
store() without traversal rejection. That is a file-write primitive reachable from a parameter the API explicitly asks the caller to supply. For us it is only exploitable if a filename is ever derived from user-controlled input — which it should never be, but "should never be" is not a control.Architecture fit: Unchanged and still good, if we treat it as an implementation detail rather than a platform capability. Sitemap generation is a scheduled batch job with a single output artifact. It has no runtime coupling to request handling, no schema ownership, no data mutation. That makes it trivially replaceable and low blast-radius. The framework-agnostic core is a genuine plus for us if we run mixed Laravel/Symfony/plain-PHP services, since one integration pattern covers all of them.
Integration feasibility: High on paper. Composer-installable, MIT licensed, Laravel package auto-discovery, publishable config. A competent PHP engineer should have a working sitemap in a day. The real question is not "can we install it" but "should we be the ones who find its bugs" — and v1.0.4 is evidence that bugs of the file-write class do get found here.
Technical risk — revised:
| Signal | Reading |
|---|---|
| 1,340 stars | Suggests visibility, but see below |
| 0 dependents | Still the decisive signal. Stars are cheap; dependents are earned. Zero packages depend on it, which means zero production validation we can inspect. We would be an early adopter, not a consumer. |
| Last release 2026-09-03 → now v1.0.4 | The date anomaly on the prior release is not resolved, but the tag progression is now consistent with normal semver patch flow. Re-verify commit signatures and diff both tags against their predecessors. A security-relevant tag is exactly the tag an attacker would want to forge. |
| opportunity score 10.21 | Low whitespace. spatie/laravel-sitemap and Symfony's equivalents already own this problem with far larger install bases. Nothing here is uniquely addressed. |
| Single-maintainer pattern | Bus factor of one. Tolerable only behind an abstraction — and now with a documented security-patch dependency on that one person. |
Path traversal in store() (fixed v1.0.4) |
New. Prior versions are vulnerable. Confirms the write path was insufficiently defensive. Escalates "pin your version" from hygiene to a hard requirement. |
Key questions to put to the maintainer (or answer ourselves):
store() the only API that joins caller input onto a base path? Audit writeTo, index generation, and any config-driven path handling for the same class of defect. Do not assume the fix was exhaustive.lastmod semantics, hreflang alternates, image/video/news extensions?robots.txt / Search Console submission in scope or left to us?Verdict (revised): Technically adequate, strategically unproven, and now with one shipped security defect in its history. The patch itself is a credit to the maintainer. Approve for a bounded proof-of-concept behind an internal interface, pinned to ^1.0.4 minimum — never ≤ 1.0.3. Do not approve as a direct, unwrapped dependency in a revenue-critical service until the dependents anomaly, the release-date anomaly, and the advisory/CVE question are all answered.
Stack fit: Clean. Composer + PSR-4 + Laravel auto-discovery means no build-system changes, no new runtime, no infrastructure. It slots into the existing scheduled-job tier alongside whatever already runs on cron or the queue worker.
Wrap it — non-negotiable, and more so now. Define a single internal contract, e.g. SitemapEmitter::emit(UrlSource): SitemapResult, owned by us. The package lives behind exactly one adapter class. This costs us perhaps 100 lines and buys:
spatie/laravel-sitemap or a hand-rolled streaming writer in an afternoon.Version constraint: "rumenx/php-sitemap": "^1.0.4" — explicitly not ^1.0, which would permit a 1.0.3 resolution from a stale lockfile or a mirror. Add a CI assertion on the resolved version.
Migration path: There is nothing to migrate from if we have no sitemap today — start with a single property, single sitemap file, no index. If we do have an existing sitemap, run the new generator in shadow mode writing to a non-public path, and diff the two outputs for a week before cutting over.
Compatibility: Confirm PHP version support against our fleet's minimum before anything else — this is the most common reason a "drop-in" package isn't. Confirm the Laravel/Symfony major we run is in the CI matrix, not merely "it worked on the maintainer's machine."
Sequencing:
<=1.0.3 resolution — including transitive.../../etc/foo.xml and a null byte to store() and assert rejection. Confirm the fix holds in the version we install, rather than trusting the changelog.Deliberately do not sequence SEO-critical properties first. Nothing about this package justifies front-loading risk onto our highest-value surfaces.
Maintenance: Low steady-state, elevated in year one — and now measurably so. The domain is stable, but v1.0.4 demonstrates that this codebase is still shedding defects, and that the sole maintainer is the only person who will ship the next fix. Budget a nominal recurring allocation for version bumps and a standing "fork if abandoned" contingency, made cheap by the adapter. Add this package to the security-patch watchlist and subscribe to repo releases directly — if no advisory feed exists, GitHub releases are our only signal.
Support: Internal only. No vendor. The adapter author owns it; document that ownership explicitly, because unowned cron jobs are how sitemaps silently rot. Add an explicit note that security patches for this dependency route to the adapter owner, not to a general platform queue.
Scaling: The single material scaling risk is memory. A DOM-based generator will fall over on six-figure URL counts; a streaming generator won't. Confirm which this is during the spike, then set a documented ceiling and shard via sitemap index above it. Generation should run on a queue worker or dedicated cron, not in a web request.
Failure modes — ranked by business impact, which is the inverse of how loud they are:
store() filename (high impact, low likelihood given our controls). Only reachable if we ever thread untrusted input into the filename — which the adapter design forbids. Mitigation: hardcoded filename in the adapter, an assertion test for traversal inputs, and a hard floor of ^1.0.4. If the filesystem user has broad write scope, this becomes write-anywhere on the host; run the job under a constrained user regardless.rename().composer.lock and a policy of no unattended major-version bumps — with the caveat that we now want unattended patch bumps for security, so the policy must distinguish the two.Ramp-up: Genuinely cheap. A mid-level PHP developer is productive in half a day; the concepts (URL sets, lastmod, index files, robots.txt) are well documented and the API surface is small. The training investment is not in the package but in the monitoring — teach the on-call what a broken sitemap looks like and why it doesn't page anyone. That's the operational gap worth closing regardless of which library we pick.
Bottom line for the decision record: the package is a reasonable, low-cost implementation of a commodity problem, maintained by one person who has just demonstrated they respond to security reports. Its fitness remains unproven (zero dependents), its release metadata retains an unexplained anomaly, and it now carries a disclosed-but-unadvisored traversal fix that our scanners may not see. Approve a time-boxed spike behind an internal abstraction, pinned to ^1.0.4; withhold production approval on SEO-critical properties pending that spike, the maintainer's answers, and confirmation that no <=1.0.3 resolution exists anywhere in our estate.
How can I help you explore Laravel packages today?