freema/n8n-bundle
A PHP bundle for integrating n8n workflow automation into your application. Configure n8n connections, trigger workflows via webhooks, and exchange data between your app and n8n automations with minimal setup.
Architecture fit. The 2.1.0 release does not change the fundamental finding — freema/symfony-n8n-bundle is a Symfony bundle — but it sharpens why that matters and what the package actually is. The previous assessment treated the bundle as potentially "an HTTP client with Symfony wiring." This release shows the substance is the callback correlation protocol: signed callback URLs dispatched to n8n, an HMAC-SHA256 of the request UUID embedded in the payload's _n8n_bundle block, pending-request handlers persisted to a PSR-6 cache pool and rediscovered in another worker process by getHandlerId() among services tagged n8n.response_handler, and a Symfony event (N8nResponseReceivedEvent) as the join point to application logic.
That is not a library you can cherry-pick. Every mechanism has a Symfony-native analogue in Laravel — PSR-6 cache pool → Illuminate\Cache, DI tag → $this->app->tag()/when, event dispatcher → Illuminate\Contracts\Events\Dispatcher, kernel.secret → APP_KEY — but the port is a reimplementation of the correlation layer, not a copy of a client class. The outbound-only path (fire a webhook, don't care about the answer) remains trivially replaceable by Http::post(). The request/response path is where the bundle earns its existence, and it is also where the Symfony coupling is total.
Integration feasibility. Effectively unchanged as a drop-in: still low. What changed is the calculus between the three paths:
new cannot be found by the process that services the callback. Manual instantiation defeats the mechanism.The honest baseline is now stronger in one direction: a homegrown Http::post() integration will not give you signed, expiring, single-use callbacks, and the 2.1.0 advisory demonstrates that getting that wrong is an unauthenticated write primitive into your response handlers. If you need the callback half, you are choosing between adopting a Symfony bundle and building the protocol yourself. If you only need the outbound half, the package is irrelevant.
Technical risk. High, and this release adds the first concrete data point to what was previously a list of structural concerns:
_n8n_bundle.uuid, allowing an unauthenticated party to dispatch N8nResponseReceivedEvent with arbitrary data and execute a pending request's response handler. That is a genuine auth bypass in a package with five stars and zero dependents. It was found, disclosed, and fixed — credit due — but it is exactly the class of defect that survives when no one is looking.401. Upgrading the PHP dependency is not sufficient; the workflows themselves must be edited to post to the callback_url they are handed. This is a coordinated change across two systems owned by different people, which is a scheduling risk as much as a technical one.APP_SECRET/n8n.callback.secret must be set (defaults to kernel.secret), and tracking.cache_pool must be shared (Redis) across application servers. Deploy the code without the shared pool and you reintroduce the exact bug 2.1.0 fixes: handlers stored in one worker's filesystem cache are invisible to the worker that receives the callback.Key questions. Revised; items 1–3 from the prior assessment are now partially answered by the changelog itself.
kernel.secret.409), response handler correlation, event dispatch. Workflow CRUD/executions/credentials remain unconfirmed.=== string compare would leave a timing oracle.401s that look like outages.409-on-already-handled guard depends on cache TTL. If the TTL is shorter than n8n's worst-case workflow duration, a legitimate late callback is rejected; if the entry is evicted early, a replay succeeds.callback.require_signature configurable to false in production, and is there any warning? A default-true toggle is fine; an undocumented escape hatch that someone flips during an incident is not.Stack fit. Unchanged at install time: expect partial overlap on transitive Symfony constraints, with new exposure on symfony/cache (PSR-6) and symfony/http-foundation. Do not register the bundle in Laravel; treat it as a library at most. Note that Laravel's PSR-6 adapter is a shim over Illuminate\Cache — workable for a port, but the semantics (pool namespacing, tag invalidation, TTL behavior) are not identical and should be verified rather than assumed.
Migration path. Sequence updated for the security release:
N8nResponseReceivedEvent and pending response handlers. Patch or remove the route before anything else.app/Support/N8n adapter now has a higher specification bar than the prior assessment implied. A credible Laravel implementation must include: HMAC-SHA256 over the request UUID plus a bound expiry, constant-time comparison, single-use enforcement via Cache::add() keyed to the UUID with a TTL exceeding worst-case workflow duration, a handler registry resolved through the container, and a documented secret with a rotation path. Budget accordingly — this is days, not hours.Compatibility. Pin the n8n server image and the callback contract (URL shape, signature algorithm, expected response schema) next to the client code. For a Laravel-side build, keep the transport on PSR-18/PSR-17 so tests don't require a live n8n. For the Symfony side, note the deprecated callback.route_path — it was never functional, so route definition is now explicitly your routing config; that's a documentation change, not a code one, but it will confuse anyone following older guides.
Sequencing. Outbound trigger first (highest value, lowest risk, and fully replaceable by Http::post()). Inbound callback handling second — but only with signing, expiry, and single-use designed in from the start, not retrofitted; 2.1.0 is a case study in the cost of retrofitting. Workflow/execution CRUD last, if ever; that is where n8n API drift bites hardest.
Maintenance. Two rules: never run below 2.1.0, and expect the "wait for a stranger" model to fail periodically. The advisory is the demonstration — a low-traffic package sat with an auth bypass in a callback endpoint until someone looked. Forking and vendoring still shifts the burden in-house in a bounded way, but the scope of what you're maintaining just grew: it now includes a signed callback protocol and a cache-backed correlation store, not just an HTTP wrapper. Budget a recurring review line item, and add "subscribe to the repo's security advisories" as a concrete task if you depend on it at all.
Support. Unchanged: no commercial backing, no dependents, no institutional knowledge. The escalation path remains "read the code," now with the added burden that you must be able to read it well enough to audit cryptographic handling. That is a materially different skill requirement than reading a 200-line HTTP wrapper — verify that the owning engineer can actually assess HMAC construction, expiry binding, and replay windows. If not, the build-it-yourself option is not viable either, and the decision becomes "use the bundle in Symfony" or "don't do this integration."
Scaling. The PHP client is still not the bottleneck; n8n's worker and queue configuration is. Two new constraints land on the application tier:
tracking.cache_pool defaults to cache.app, which on a multi-server deployment is per-node storage. Point it at Redis (or an equivalent shared pool) before scaling past one application server, or callbacks will arrive at workers that have never heard of the request. This is not a performance tuning knob; it is a correctness prerequisite.Failure modes. All prior failure modes stand; the release adds and modifies several:
new instead of as a service. Both are silent: the callback is accepted and quietly dropped.409-on-already-handled check, which is only as durable as the cache entry. A cache flush, eviction under memory pressure, or TTL expiry reopens the window.APP_SECRET or callback.secret breaks every callback dispatched but not yet returned. Needs a dual-secret grace window or a drain-before-rotate procedure.401 and the workflow's result is silently lost unless n8n's error workflow alerts on it. This is the most likely upgrade casualty.Ramp-up. The PHP surface remains small; the learning cost is now split three ways: n8n itself (nodes, error workflows, execution data, credentials), the callback protocol (signing, expiry, replay, correlation TTLs), and the deployment topology that makes the second one work (shared cache, single secret across nodes). A Laravel team can still integrate the outbound path in a day. The callback path is a week to do safely and, if built in-house, a week to build at all. One engineer should own the n8n boundary end-to-end, including secret lifecycle and cache-pool configuration, with the payload and callback contracts documented in the repo.
Net position. Unchanged in direction, strengthened in evidence. The stack mismatch plus a single-maintainer, zero-dependent package still makes adoption as a Laravel dependency hard to justify — and a recently disclosed auth bypass in its callback endpoint is precisely the asymmetry the prior assessment warned about, now observed rather than hypothesized. Two things do shift. First, the package is now more valuable as a reference implementation than as a dependency: 2.1.0 is a compact, readable specification of how to do signed, expiring, single-use callbacks with cross-process correlation, and any in-house Laravel adapter should be checked against it point by point. Second, the bar for that in-house adapter has risen — a naive Http::post() plus an unsigned callback route replicates the vulnerability this release fixes. The correct decision remains: use the package as a reference during a short spike, then build and own a thin Laravel adapter that implements the protocol properly — or, if the callback path isn't actually needed, skip the integration entirely.
How can I help you explore Laravel packages today?