Block Web Access to Code/Java Source Files

Thu Feb 19 10:49:45 EST 2026

Last year, while developing Jakarta Modules, I realized that there's a path to accessing Java source files by path in normal NSF apps. This ended up being tracked as a Defect Article, but has unfortunately since been deferred to an unspecified future time. I think it's worth fixing before such a time, though, so I set about doing that.

TL;DR

If you have a file in Code/Java in Designer named "com.mycompany.MyClass", the source is accessible via "foo.nsf/xsp/com/mycompany/MyClass.java". There are a couple ways to patch this:

  • Set up a web substitution rule for /*.nsf/xsp/*.java to /doesnotexist.html or the like. That won't cover non-Java files (like translation.properties, etc.), but it'd at least prevent your source code from being visible. It'd also be good to set one up for plugin.xml
  • Install my new project Code/Java blocker

Further Explanation

The specific problem here is the way the "foo.nsf/xsp/*" URL namespace works. That is essentially the "webapp version" of your NSF, and URLs coming in here are processed as Servlets and, failing that, static resources. When a request comes in matching that, the XPages runtime looks for IServletFactory implementations to handle it first - if one of those matches, it hands the request off to the matched Servlet. If none matches, it then moves on to some code that handles static resources like a classic Java web app would. I'm not quite sure why it does this, but I'd guess it's probably a holdover from traditional web behavior and not actually really useful. Still, it may be load-bearing after so many years, so I can see why HCL wouldn't want to outright remove this.

Anyway, the problem arises from the fact that the virtual filesystem used by the "static resource" search includes all file-resource-like entities that don't start with /WEB-INF. Java classes stored as Code/Java resources are this type of file, and so their conceptual file path is included in the pool. Thus, if you know the name of a Java class inside an NSF and have web access to that NSF, you can get its source via URL. This is admittedly a bit of a stretch as an attack vector: it being a problem relies on someone a) knowing the full name of a Java class and b) that Java class containing damaging information. If you have your app set to show stack traces on errors (not the default), someone could potentially glean some local class names, and from there walk the tree by looking at imports and class references, but it's still a long walk.

Long walk or no, though, it's worth fixing.

The first fix - the web redirection rule - should cover the bulk of what you'd want. There's an off chance that it might run afoul of some edge case where there actually is a Servlet that's intentionally serving up files that end in ".java", but that's pretty unlikely.

Because I like writing code, I made a small project that does the job a little more specifically. The way the plugin works is that it contributes a IServletFactory to the XPages runtime for all NSFs that builds a collection of Code/Java files and then, when a request comes in that matches that or some other vetoed paths, returns a stock Servlet that returns 404 in the same way that other non-matched paths in that namespace do. That should make it so that the source files are not accessible and also don't "leak" their presence at all.

Either way, I suggest applying a fix for this until it's done in the product.

More Dealing With The TinyMCE Switch

Tue Feb 17 11:42:19 EST 2026

Tags: xpages

Earlier in the month, I wrote about some of my preliminary experiences dealing with the fallout of the switch from CKEditor to TinyMCE in Domino 14.5. In that post, I talked about some of the ways to deal with covering both, but I only casually mentioned one of the severe limitations in the TinyMCE Dijit implementation: unlike the CKEditor version, it doesn't let you provide anything other than strings for properties. That's fine for a lot of things, but that means that complicated toolbars aren't settable, nor are boolean properties like paste_as_text. If you've ever dealt with users and the sorts of things they want to put into rich text fields, you might understand why that parameter is useful.

I wanted a way to configure this in a way that is reasonably global and reasonably light-touch, ideally in a way that would leave older customizations working on pre-14.5 servers. I've come up with a tack that I'm mostly happy with, though it still has some limitations.

The Tack

The route I'm taking now is sort of similar to CKEditor's customConfig property, but uses the Dojo module system. I'm subclassing the ibm.xsp.widget.layout.CKEditorWrapper class (still the name when using TinyMCE) and using that as a way to set other init properties. There are a few steps for this.

Module Path

First off, we'll need to register a custom Dojo module path. Unfortunately, this isn't settable in themes for some reason, so I dropped this code in the common layout custom control:

1
2
3
<xp:this.resources>
	<xp:dojoModulePath url="example/widget" prefix="example.widget"/>
</xp:this.resources>

Theme

Then, I cracked open the app's theme and configured it to apply a dojoType to rich-text fields on Domino 14.5+:

1
2
3
4
5
6
7
<control>
	<name>InputField.RichText</name>
	<property>
		<name>dojoType</name>
		<value>#{javascript:parseInt(session.evaluate('@Version')[0], 10) &gt;= 495 ? 'example/widget/CustomTinyMCE' : ''}</value>
	</property>
</control>

This will work except in the uncommon case where you're already setting a dojoType for your rich-text fields... if so, you may have some more work to do, or you may just want to set <property mode="override"> there.

JavaScript

Finally, make a JavaScript file named "example/widget/CustomTinyMCE.js" in your NSF. I recommend making this a File Resource and then using the Generic Text Editor to edit it - that one supports newer syntax and is a bit less crash-prone than the default Designer JS editor. Set the contents thusly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
define(
	'example/widget/CustomTinyMCE',
	['dojo/_base/declare', 'ibm/xsp/widget/layout/CKEditorWrapper'],
	function(declare) {
		return declare('example.widget.CustomTinyMCE', [ibm.xsp.widget.layout.CKEditorWrapper], {
			constructor: function () {
				this.opts.paste_as_text = true
				this.opts.toolbar = 'undo redo | cut copy pastetext'
			}
		})
	}
)

And customize at will. this.opts is set by the superconstructor to the contents of any dojoAttribute or attr properties on your component, so anything you set here will override what's set there. This can be useful in particular for things like code that sets the toolbar shorthands like "Large". Here, I just use a string for the toolbar, but, this being JavaScript, you are free to use the structured syntax.

Further Customization

The values you set in this.opts here will override anything set by dojoAttributes or in the default config HCL provides, so you have pretty free reign here. You could also use that for your own custom configurations if you have different needs on different pages - read the values in this.opts and then apply different configuration based on that. This would, admittedly, have been a nice touch in the case of the "Large"/"Medium"/"Small" toolbar shorthands.

Overall, I think this is a pretty reasonable approach. I wish that you could register dojoModulePaths in theme files, but the need for that could be worked around by moving the JavaScript to an OSGi plugin with more-predictable paths. This centralized route also leaves room for alternate customizations in the case that HCL switches the RT editor again in the future.

Authenticating Mastodon With Domino's OIDC Provider

Sat Feb 07 12:48:24 EST 2026

Tags: domino oidc

A few years back, when Twitter finally truly went over the edge into hell, I wrote a post about setting up my Mastodon instance, which uses Keycloak for OIDC auth backed by Domino LDAP for the directory.

In the intervening time, Domino sprouted OIDC provider capabilities and so it's been on my back-burner to switch Mastodon over to use it. Keycloak has been fine for me, but I only use it for OIDC needs, and it'll be all the better to cut down on the number of distinct servers I run. Plus, while Keycloak's Docker container makes it easy to run, I'm much more comfortable with how Domino works, and so it's always nice to use it when I can.

For the most part, slotting Domino into Keycloak's place here was a matter of following the directions in the Domino documentation to set up the provider, which I won't rehash here. There are only really a few things that make it special.

Dynamic Claims

As I mentioned in the original post, I use a special field in my person document called "mastodonusername" to store the name to use for Mastodon. In Keycloak, I mapped that value from LDAP to a claim in the generated JWT, and I'd need to do the same on Domino.

Though there's not a UI for it, towards the end of the 14.5 EA period, HCL added in the ability to specify one field to map from the person document to the token. This is done via notes.ini variables, so I put this in my server's Configuration doc:

1
2
OIDC_PROVIDER_DYNAMIC_CLAIM_FIELD=mastodonusername
OIDC_PROVIDER_DYNAMIC_CLAIM_NAME=mastodonusername

With that in place, the token gets the same "mastodonusername" claim that Keycloak was using, and it'll continue to map my login to the right user in Mastodon.

PKCE

The current proper way to do OIDC is to include PKCE, which was originally designed for mobile apps but can be used with all login flows. Domino's OIDC Provider lets you turn this off as a requirement on a per-client level, but it's still best to include it when possible.

Mastodon doesn't enable PKCE by default, but it made it in as an option a couple versions ago. Enabling it is done via an environment variable, so I added this to the .env file that my Mastodon container uses:

1
OIDC_USE_PKCE=true

And, well, that's it. Now it uses PKCE and all is well.

That's About It

And that's about it! I didn't expect any specific problem, since OIDC is a nice open standard and Domino's implementation hews very closely to best practices, but it's always nice to see things go smoothly in action.

Splitting the CKEditor/TinyMCE Difference in XPages on Domino 14.5

Wed Feb 04 14:29:09 EST 2026

Tags: xpages
  1. Oct 19 2018 - AbstractCompiledPage, Missing Plugins, and MANIFEST.MF in FP10 and V10
  2. Jan 07 2020 - Domino 11's Java Switch Fallout
  3. Jan 29 2021 - fontconfig, Java, and Domino 11
  4. Nov 17 2022 - Notes/Domino 12.0.2 Fallout
  5. Dec 15 2023 - Notes/Domino 14 Fallout
  6. Sep 12 2024 - PSA: ndext JARs on Designer 14 FP1 and FP2
  7. Dec 16 2024 - PSA: XPages Breaking Changes in 14.0 FP3
  8. Jun 17 2025 - Notes/Domino 14.5 Fallout
  9. Feb 04 2026 - Quick Tip: Stable ndext Classpaths In Designer 14+
  10. Feb 04 2026 - Splitting the CKEditor/TinyMCE Difference in XPages on Domino 14.5

At the top of the update notes for Designer 14.5 is this bit:

Ckeditor [sic] replaced: Ckeditor [sic] for Designer/Xpages [sic] has been replaced with TinyMCE 6.7. For more information on TinyMCE, see TinyMCE6 Documentation.

My guess is the reason for this change is that CKEditor 4 hit end-of-life in June 2023 and CKEditor 5 dropped the option to license as MPL, meaning the only way to use it in Domino would be to pay for a presumably-onerous enterprise license. Fair enough.

However, you may notice that the documentation for this change is a little... thin. Specifically, the screenshot above is the documentation. Considering that customizing CKEditor has been a popular and long-documented feature of XPages, it's possible you're sitting on some code that used to customize CKEditor to your needs but does not work in 14.5. Since CKEditor no longer ships with Domino, there's no opt-in switch, so we have to deal with it.

Well, I don't have all the answers for how to do this properly, but I did recently have a need to at least start digging into it, so I figured I'd share a preliminary tack to start making cross-version-compatible code.

Customizing TinyMCE

Like CKEditor, TinyMCE has mechanisms to customize its appearance in both simple and programmatic ways. The way TinyMCE is initialized is that it's passed a configuration object during creation, like in this example from their docs:

1
2
3
4
5
tinymce.init({
  selector: 'textarea',
  skin: 'oxide-dark',
  content_css: 'dark'
});

As it happens, the Dojo plugin XPages uses to instantiate TinyMCE (still called "ibm/xsp/widget/layout/CKEditorWrapper" for some reason) takes all of your HTML attributes and passes them to this object, vaguely similar to the way CKEditor did it. That means you can get the above behavior like so (we don't need the selector, since XPages does that for us):

1
2
3
4
5
6
<xp:inputRichText>
	<xp:this.attrs>
		<xp:attr name="skin" value="oxide-dark"/>
		<xp:attr name="content_css" value="dark"/>
	</xp:this.attrs>
</xp:inputRichText>

I say "vaguely similar" because, while CKEditor will interpret applicable attributes (like toolbar) as JavaScript by running them through eval(...), the same is not the case for the TinyMCE code. So, for example, this snippet, trying to replicate the next example from the TinyMCE page, will not work:

1
2
3
4
5
6
<xp:inputRichText>
	<xp:this.attrs>
		<xp:attr name="skin" value="(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'oxide-dark' : 'oxide')"/>
		<xp:attr name="content_css" value="(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'default')"/>
	</xp:this.attrs>
</xp:inputRichText>

That will just cause the page to try to load the URL "/yourapp.nsf/(window.matchMedia('(prefers-color-scheme:%20dark)').matches%20? 'dark' : 'default')=", which will, uh, not work. I assume you can deal with this via TinyMCE plugins, but I haven't looked into those yet.

When The Twain Meet

If you're migrating from older versions to 14.5+, your task is to modify all of your code that customizes CKEditor. The most common "good" case for this is that your customizations break in innocuous ways. However, it's also possible that your customizations will result in a broken editor, particularly around toolbars. For example, the afore-linked documentation from 8.5.2 includes the GUI version of this:

1
2
3
4
5
<xp:inputRichText>
	<xp:this.dojoAttributes>
		<xp:dojoAttribute name="toolbarType" value="Large"/>
	</xp:this.dojoAttributes>
</xp:inputRichText>

(for the record, xp:this.dojoAttributes is effectively interchangeable with xp:this.attrs for this use)

This causes CKEditor to display its "large"-type suite of toolbar icons, as opposed to "medium" or "slim". However, this causes TinyMCE to display no toolbar icons at all. The reason for this is that CKEditorWrapper.js maps "toolbarType" to just "toolbar" and, while CKEditor treated "toolbar" as either a shorthand name or as JavaScript, TinyMCE treats the same attribute as a space-delimited list of action names, and "Large" is not one.

This creates a bit of a pickle! You'll definitely need to open up your app's code to change this. If you're doing a one-way migration, you can change this to be a list of the buttons you'd like, but things will get messier if you want to have multiple versions of Domino deployed, or you want to prep your apps for the migration before making the switch.

There are a lot of ways one could go about doing this and I'm not sure I love the provisional mechanism I'm going to share here, but this is at least one way to do it. Since the one way we can know that we're using a different editor is by the build version of Domino, we can make a switch and only load attributes based on the running server version:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
	<xp:this.dataContexts>
		<xp:dataContext var="isTinyMce" value="${javascript:parseInt(session.evaluate('@Version')[0], 10) &gt;= 495}"/>
	</xp:this.dataContexts>
	<xp:inputRichText>
		<xp:this.attrs>
			<!-- CKEditor attributes -->
			<xp:attr name="toolbar" value="Slim" loaded="${not isTinyMce}"/>

			<!-- TinyMCE attributes -->
			<xp:attr name="toolbar" value="undo redo styles" loaded="${isTinyMce}"/>
		</xp:this.attrs>
	</xp:inputRichText>
</xp:view>

If you have a lot of CKEditor customizations, your best bet will be to use a customConfig script to consolidate them if you don't already. TinyMCE will ignore that, so at least it won't do any harm.

Like I said, I don't love this approach, since it'd be a real pain if you have a lot of rich-text fields in your apps, but at least it's a starting point. It could be more scalable (if very fiddly) to write a renderer to replace RichTextRenderer that checks the server version and then does its own heuristics to change the output values for the HTML. It might also be possible to dredge up the old CKEditor files from older Domino and write your own Dojo plugin to re-wrap them to keep things chugging for a bit, but that'd be only a temporary fix.

Anyway, it's a messy situation and I didn't see a lot of documentation for it, so I figured I'd jot this down. Good luck!


After posting this, I noticed that TinyMCE went through a similar permissive-or-GPL switch in the move from 6.8 to 7.0. That explains why Domino ships with version 6.8.4 and makes me suspect that we'll face another similar move before long.

Quick Tip: Stable ndext Classpaths In Designer 14+

Wed Feb 04 11:55:27 EST 2026

Tags: java xpages
  1. Oct 19 2018 - AbstractCompiledPage, Missing Plugins, and MANIFEST.MF in FP10 and V10
  2. Jan 07 2020 - Domino 11's Java Switch Fallout
  3. Jan 29 2021 - fontconfig, Java, and Domino 11
  4. Nov 17 2022 - Notes/Domino 12.0.2 Fallout
  5. Dec 15 2023 - Notes/Domino 14 Fallout
  6. Sep 12 2024 - PSA: ndext JARs on Designer 14 FP1 and FP2
  7. Dec 16 2024 - PSA: XPages Breaking Changes in 14.0 FP3
  8. Jun 17 2025 - Notes/Domino 14.5 Fallout
  9. Feb 04 2026 - Quick Tip: Stable ndext Classpaths In Designer 14+
  10. Feb 04 2026 - Splitting the CKEditor/TinyMCE Difference in XPages on Domino 14.5

When Notes 14 FP2 came out, I made a blog post detailing a change that HCL made to the JRE classpaths that Designer uses for compilation. Specifically, since jvm/lib/ext is not present in newer Java versions, people had found that they manually had to add JARs from ndext to the JRE to get the same sort of "local JAR" behavior from previous versions. HCL "fixed" this by making it so that Designer adds all of the JARs in ndext to the compiling JRE, which sort of fixed the problem. It had the unfortunate side effect, though, of adding the various contaminants present in the Notes JVM to the classpath, particularly Poi and (in that post's case) the ancient Servlet spec.

In that post, I explained how to fix it. However, I hadn't yet noticed that it's not just that Designer does this once, but rather that it does it every time you launch, re-breaking compilation if your code runs afoul of this. Since then, I found a workaround that seems to stick, so it's worth documenting here.

Whatever code re-mangles the classpath does it specifically to the one named "jvm", so what I do now is go to Preferences - "Java" - "Installed JREs" and add a new definition for the same path but with only the core runtime package and a distinct name:

Screenshot of the 'Edit JRE' Eclipse dialog showing a clean JRE

If you have any local JARs you do want to include, add them here.

Then, I mark that JRE as the default:

Screenshot of the 'Installed JREs' Eclipse pane showing the new JRE marked as default

With this so marked, it will (at least in my experience) stick as your default across launches and you will be blessedly free from this particular excessive zeal on Designer's part.