Skip to main content
Jahia Store
EN

Jahia GraphQL Extension Websites

community
Download 2.2.1

Information

Module ID
graphql-extension-websites
Group ID
org.jahia.community
Status
community
Category
Developer tools
Author
fbourasse
Developer website
http://www.jahia.com
Requires Jahia
8.2.2.1
Released
2026-09-21
Source
scm:git:git@github.com:Jahia/graphql-extension-websites.git

GraphQL extension to manipulate websites

Dependencies

Depended on by

Nothing depends on this module.

Versions

Security release. Fixes SEC-363 / GHSA-8332-wc4c-g6m9 - exportWebsite could recursively delete every site's export archives. Also ships the modulesToDeploy fix, which was broken in every release up to and including 2.2.0.

Requires Jahia 8.2.2.1 · depends on default, graphql-dxm-provider

⚠️ Action required - only if you export to "."

exportWebsite now refuses an exportPath that resolves to the exports base directory itself.

Are you impacted?

Only if a caller passes exportPath: ".", "./", "x/.." or any other value that normalizes to <jahia-var>/exports. Paths naming a subdirectory - what the documentation has always shown - are unaffected.

What changes

Those values now return false and delete and export nothing. Previously they returned true and destroyed the exports tree.

What to do

Name a subdirectory, e.g. exportPath: "my-site-export".

No permission, role or configuration change. Nothing to re-grant - unlike 2.1.x → 2.2.0, this release alters no permission, no role definition and no authorization behaviour.

🔒 exportPath: "." destroyed every site's export archives

exportWebsite clears whatever already sits at exportPath before exporting, because Jahia refuses a server export directory that is not empty. That clear is a recursive delete. The permission check authorises the site; the deletion acts on a caller-named path.

exportPath: "." resolved to <jahia-var>/exports itself. It passed path confinement - it never escaped the base - and the recursive delete then removed every site's archives, including sites the caller had not named, while the mutation returned true. Nothing in the response told the operator what had happened.

Containment is a subset check, and a directory is a subset of itself: equality is the one overlap such a check cannot exclude. The fix adds that comparison in two independent places - in exportWebsite, before the site is even resolved, and again inside the cleanup helper, which now takes a base it may never delete.

Severity: medium (CVSS 4.9, AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N, CWE-863 + CWE-22). Reaching the mutation requires the module's server role - the site role is refused - so this is not a privilege escalation. Its weight comes from being silent, cross-site, and as easy to fire by accident as on purpose: "." is a value an operator could reasonably type meaning "here".

Verified in both directions on a real Jahia rig: 33/33 e2e tests pass on this build (8.2.3.2 and 8-SNAPSHOT), while the released 2.2.0 jar fails the new regression spec and leaves /var/jahia/exports holding only export.properties, roles/, systemsite/ and users/.

🐛 createSiteByKey's modulesToDeploy now works

On 2.2.0 and every earlier release, supplying modulesToDeploy failed with IllegalArgumentException: argument type mismatch for every caller, including root, while omitting it worked. It was declared String[] in Java; graphql-java-annotations only converts a GraphQL list argument to a parameterized List<T>.

Callers that worked around this by omitting the argument need no change - omitting it still creates the site with just its template set. They may now pass it. The wire type [String] is unchanged.

🧪 Test harness

The Cypress e2e suite could not run at all, on any build, and there is no CI workflow that would have caught it: ci.build.sh died on a clean checkout, and provisioning pulled graphql-dxm-provider/LATEST, whose provider swap dropped dashboard from the GraphQL schema and threw in every spec's before all - silently skipping 30 of 33 tests. Both fixed; 33/33 now pass.

Upgrading

Jahia imports a module's initial JCR content once per module version. This release changes no permission or role definition, so there is nothing to re-import and nothing to re-grant.

See README § Upgrading for the full note.

Artifacts

graphql-extension-websites-2.2.1.jar is the deployable module bundle (Bundle-Version: 2.2.1, Jahia-Required-Version: 8.2.2.1). Deploy it through the Jahia module manager or drop it in karaf/deploy.

Full changelog: 2_2_0...2_2_1

Requires Jahia 8.2.2.1Released 2026-09-21

Security release. Completes the remediation of SEC-136 / GHSA-r6x2-vrm8-vjvr, closing the advisory's §4.2 and §4.3 alongside the §4.1 fix shipped in 2.1.0.

Requires Jahia 8.2.2.1 · depends on default, graphql-dxm-provider

⚠️ Action required — delegated users lose export and repository-wide read

The shipped graphql-extension-websites-administrator role no longer grants site export or repository-wide read. Existing delegated holders lose both on upgrade. This is the fix, not a regression.

To restore export (and deletion) for a delegated user, grant them graphql-extension-websites-site-administrator on each site they administer, in addition to the server role. Server administrators are unaffected and retain full reach.

exportAllSites now requires full server-administrator rights and returns the new NOT_SERVER_ADMINISTRATOR result otherwise. If you consume ExportAllSitesResults exhaustively, handle the new constant.

Jahia imports a module's initial JCR content once per module version, so the new permissions and the revised roles land only on the version change to 2.2.0.

🔒 Security

Exports are now genuinely bounded (§4.3)

Both export mutations already ran under the caller's own session rather than escalating to root. The documentation drew a confidentiality guarantee from that which did not hold: the shipped role granted jcr:read_default at the repository root, so for its holders the "bound" was the entire repository. Confined-to-what-the-caller-can-read plus caller-can-read-everything is still a full-instance dump.

  • exportWebsite is target-scoped. The caller must hold websitesExport on the site being exported, checked in their own session and failing closed. Relying on the session read-bound alone would make the security property depend on read ACLs lining up, and would still write a misleading near-empty archive to disk for an unauthorized caller.
  • exportAllSites requires server administrator. A bulk export spans the whole instance; a read-bounded version would hand a delegated holder an archive silently containing only their own sites — a partial backup that looks complete, which is worse than a refusal. The gate runs before the S3 precondition, so an unauthorized caller learns nothing about the configuration.
  • The server role drops jcr:read_default. It was never needed to reach the API: verified on a live instance, a caller holding only graphqlAdminMutation and websitesCreate — no read permission at all — invokes createSiteByKey successfully, while the same caller is denied exportAllSites. Jahia already satisfies the DXM admin field's jcr:read/jcr:system requirement for authenticated users by other means. Read is granted per site instead.

Operations are independently delegable (§4.2)

All five mutations previously shared one websitesAdmin permission, so delegating any one of them delegated all of them. Each now carries its own.

Build a custom server role from the individual permissions to delegate a narrower subset.

Why the permission layout looks the way it does

Three details are load-bearing and should not be "tidied up":

  • The target-scoped mutations deliberately do not name their fine permission in the annotation. Annotations are evaluated at the repository root, where websitesDelete and websitesExport are never granted — they live on the site-scoped role. Naming them there would deny every site administrator before the method body ran, while looking stricter.
  • Nor can those permissions be added to the root-granted server role to compensate. JCR permissions inherit downward, so a root grant satisfies the per-site check on every site and makes it vacuous.
  • All five permissions are siblings under admin, never nested. Jahia registers nested permission nodes as aggregated sub-privileges, so nesting one under another would silently grant it to the parent's holders. Being children of admin is what lets server administrators aggregate them all with no special case in the code.

Quality

71 unit tests and 25 Cypress end-to-end specs, all passing, verified against this build.

Coverage added this cycle asserts both halves of every scoping rule — a site administrator can export and delete the site they administer and cannot touch one they do not, with a server administrator control alongside, since testing only the refusal would let an implementation that breaks the operation for everyone pass as secure. Reflection tests pin each mutation's annotation against the two dangerous "consistency fix" edits described above. Every new gate was reverted in place to confirm exactly the intended tests fail.

Artifact: graphql-extension-websites-2.2.0.jar (OSGi bundle; embeds the AWS SDK v2, hence the size). Sources jar attached separately.

Full changelog: https://github.com/Jahia/graphql-extension-websites/compare/2_1_0...2_2_0

Requires Jahia 8.2.2.1Released 2026-09-14
Requires Jahia 8.2.2.1Released 2026-04-05

Add a new GraphQL API entry point under admin/jahia to export all websites towards an AWS S3 bucket

Tag: https://github.com/Jahia/graphql-extension-websites/releases/tag/1_0_1

Full changelog: https://github.com/Jahia/graphql-extension-websites/compare/1_0_0...1_0_1

Requires Jahia 8.1.3.0Released 2025-06-12
Requires Jahia 8.1.1.1Released 2025-06-12