GeezerVison is showing github.com — A txp:tag to create a page-wide CSP nonce · Issue #1933 · textpattern/textpattern
A txp:tag to create a page-wide CSP nonce #1933DescriptionOn the admin-side we have the constant TEXTPATTERN_CSP_NONCE and the \Textpattern\UI\Script and \Textpattern\UI\Style methods to output script src, script, style and link rel="stylesheet" tags with a corresponding nonce. Having just made updates to bot_wtc and glz_custom_fields, it works well.
On the forum and elsewhere here in the issue, we've discussed different ways of tapping into apache or nginx generated unique_id's for using as a nonce in the public side, but as Stef has shown, it works just as well if generated by txp and then used throughout a page template. Up to now, I've set this as a variable at the top of a page and then construct the style and script tags accordingly. For my own templates, that's fine, but when making a template, you start to build in conditionals for if it is available.
Is there mileage for have a txp:nonce tag (or whatever) for setting a csp nonce that other tags (e.g. txp:css) could 'contextually recognise' and adapt their output accordingly? See updated issue #1394.
I wonder if needs some kind of “contextual trigger”, for it to activate. In non-CSP use, it's not needed, only when csp headers have been set is it relevant. On the admin side you set that via config.php, but presumably front-end and admin-side CSP could potentially differ, so that activating flag can't be used for both. ActivityBloke commented on Jul 18, 2024 I like this idea. And it's an absolute cinch to implement in core, at least at a very basic level. You might like to investigate the world's smallest plugin, smd_token:
# smd_token v0.1.0
# Generate cryptographically random tokens for use within Textpattern
# Stef Dawson
# https://stefdawson.com/
# ......................................................................
# This is a plugin for Textpattern - http://textpattern.com/
# To install: textpattern > admin > plugins
# Paste the following text into the 'Install plugin' box:
# ......................................................................
H4sIAAAAAAAAA+1WS2/jNhC+61cM1ACyA1u2nDiOlTpIsEEfQBcNNtn2EsCgpZFEhKa0JFXb
WOS/d0hJjl1sFkUvvUQXkaPhzDffPCgWR5P4q47PY1+yNfpXOp7Hvl6nS1M+o7T7Wez/hUrz
0u2msT8Oo3Bs1xexz2pTlMpuoij2HwxmcMc2utGNxp3CslbcSiZnsV8YU+l4NNKknDrdMCnX
o85GijpRvDKtvwty/zNKVMwgJGpXmTJXrCp4woTYgWIyLdfgwGrISgW1RthwU3AJj7g1FTMG
VQe9VCk2aGN/at8Ut9lV2IrGrVomWK4PZaRWoKjs8nJOwiIKYU+S593XK8ETMCwnJKDQ1EoC
+wZcjUmtkF5fapQJQplBUjDFEsKoQ/gD1Q4KCmnXhsJlDlxqniJUos5pCST/8HAPBbLUnvG8
YhLCr1IbcsAsazCCz5IfCDzPvys3UpQsBeJFt6b8+AcgVyRCCRWjdNglJCU5y5QllXah2RrI
uECCQaFZ0QGrsLxN11wOr+8bcEuyI1EMoHXv7KNkK9HYbhyH8BMFsapzYqoqldED+oCM8uYr
xunFJHCtaySEFN9jCXUXzwBSFGgOrb1i/TaYlqHPmuUIuGVr8qU9L7oY7pnvyodRalLMWC2M
NaiQkoBHGT061dSe2A3ztj7TxlDseaskhB/Ntor3RQKja8+747oSbEeGJuf/2ZBAmZti4U/O
/X8YjSb/wqhrDlKuKDy+/b6LaOK3egtfllSww8bl77WpaqNBl2u0nZaD4M8IN40OW0WrWTa/
jGZTvGnpv3VjgEozUZhyQwn4U3GqIQmrHRzNjfiN+ZCU0lBkYduOtkzddJrQgBidnnpwuu/I
NW3s/vaoVj98fNiXDEFpabHgj4ZIe/amGVxwAM2JBZfPAPAGSFIZeR7PoEeEUsegyhi1+WKx
gKByQyLow1ePDIBVSgTTeolbro3uBU8HaJ8eWf70CXP6onZBvztkn0dKVZyj+e6BvbZ9htfK
fUDVC/ZJDvpXTunFe/G8lsDXORZ3/FA3HvETdvxUVGhroJ5RVHtwQig0YaMJSCvFV7VBfahH
sCzVJ025WD2XUE5O3Aqlceptq7Xq7fPL48ffHLVZLRM34/Zh9JznQWd4AbIWou81dBE9tht6
4pZ0eg5qb89M0FR5AItr6uvBq7ypeCcPgkbeHzQR9ok0JzhpGmXxVjbuKbEbum2ePjnugv7w
uiO0d9I47ky1EZ80bsPG8pXNirtumptnqdjm/fZ5v33eb5////ahf9h16v4ezyaxPz+/yNL5
dJZML2ZsNZ3PzrNVOr08G4+jywyzyL96+RswJZvVZQsAAA==
Take it for a test drive (see example 3) and if it works then I'm sure we could sneak a corresponding core tag in.
It's not very automatic (contextual) like your suggestion, but you can use it to wire up reusable tokens and inject them anywhere you like on a page. Bloke commented on Jul 18, 2024 This is the entire plugin code, btw:
if (txpinterface === 'public') {
if (class_exists('\Textpattern\Tag\Registry')) {
Txp::get('\Textpattern\Tag\Registry')
->register('smd_token');
}
}
/**
* Public tag: generate a random token.
*
* @param array $atts Tag attributes
* @param string $thing Tag container content
* @return string HTML
*/
function smd_token($atts, $thing = null)
{
extract(lAtts(array(
'length' => 16,
'prefix' => '',
), $atts));
$token = Txp::get('\Textpattern\Password\Random')->generate($length);
return $prefix.$token;
}
jools-r commented on Jul 18, 2024 SponsorMemberAuthor More actions Thanks, you showed us how to do that in another issue/forum thread, and the variable I use is essentially identical:
<txp:variable name="unique_id"><txp:php>echo Txp::get('\Textpattern\Password\Random')->generate(32);</txp:php></txp:variable>
Do you store your smd_token as a txp:variable and then use txp:if_variable or <txp:variable name="token" wraptag=' nonce="<+>"' /> to insert it where needed? Using smd_token twice in a template would result in a new token being generated.
I was thinking a public-side txp:nonce / txp:token tag could go a bit further:
- only generate a token if CSP headers have been set (or have an attribute to make it behave that way).
- be used several times in a page template, maybe with a
format attribute to add a nonce="………" snippet using the same token if it has already been generated, and skip it if no CSP headers are set. Sort of similar to how txp:meta_description can be output as a fully-fledged meta tag or as “naked” text.
- If that had some fixed variable/constant name within textpattern, other tags – such as "txp:css" (are there others?) could pick up on its existence, and build their links with the nonce included automatically if it exists.
The idea is that themes could be set up to be CSP-ready without lots of if_ conditionals, and where no CSP headers are set, they don't litter the code.
EDIT: I've installed it now and see how it works with the prefix attribute. Maybe it just needs a name attribute to use an already generated token further down the page.
BTW: do you see new comments to other buried issues? I added a feature request to Patrick's issue about txp:css. phiw13 commented on Jul 19, 2024 @jools-r
Does this not work for you ?
<txp:variable name="csp-token"><txp:smd_token length="24" /></txp:variable>">
And then down the page, things like:
<link nonce="<txp:variable name="csp-token" />" rel="stylesheet" href="styles.css">
––^––
Q. How do you construct your CSP header? With <txp:header name="Content-Security-Policy" value="" /> ?
Here I always run into problems, and the header tag is not parsed… Bloke commented on Jul 19, 2024 Yes, I spotted the comment on the txp:css issue.
I'd rather we didn't make this too specific for CSP but come up with a general solution that happens to fit CSP requirements nicely.
Adding a name attribute to the plugin (or eventual core tag or whatever) for retrieval is a neat idea and saves the manual drudgery of assigning it to a variable. Let's try that.
Regarding headers, I'm not sure why it would cause issues, unless the headers have already been sent or the plugins aren't loaded in time to parse the page but I thought they were loaded pretty early on. Does load order help?
Thinking further, the immediate remit for CSP is to provide a value that is by its nature temporary. It only survives the duration of the current page request. But what if you wanted a session token for some other usage? 10 minutes, 30 days, etc. Txp has a handy little table txp_token that we use for authentication and is generic enough to be wired up for this purpose in the plugin/tag.
Examples:
<!-- Single use page-only validity -->
<smd::token name="mycsp" prefix="nonce-" length="24" />
...
...
<link nonce="<smd::token name="mycsp" />" rel="stylesheet" href="styles.css">
...
...
<script src="//some-url" nonce="<smd::token name="mycsp" />"></script>
...
<!-- Stash the named token in the database for later usage on this or future pages -->
<smd::token name="my_sesh" prefix="ss" length="20" store />
<!-- Stash the named token in the database for later usage and set its expiry for half an hour -->
<smd::token name="my_sesh" prefix="ss" length="20" store expires="+30 mins" />
That sounds doable and useful. Begs the question if a corresponding if_token conditional might be handy in case the token has expired or isn't yet defined and you want to take different action based on its validity (e.g. regenerate a new one).
That means you could design a Form called nonce:
<smd::if_token not name="mycsp">
<smd::token name="mycsp" prefix="nonce-" length="24" />
</smd::if_token>
Then, all your accesses could simply be to inject the value this way:
<link nonce="<txp::nonce />" rel="stylesheet" href="styles.css">
The store attribute might be overkill since the act of specifying an expiry is enough to trigger storage mode, so maybe an empty expires means "store and never expires" whereas if a value is supplied the designated expiry date stamp is used.
We could go further perhaps as jools-r suggests and maybe if the name is a pre-determined special value such as TXP_CSP, that could trigger some sort of automatic CSP mode whereby the token is injected into the link and script tags when they're rendered from the UI library.
If we retrofit the txp:css tag to use it, that's fine. But we don't really have the notion of a script tag in core, so that would always need wiring up manually. Part of me therefore thinks this extra functionality is an unnecessary complication. Happy to consider it though.
Thoughts? jools-r commented on Jul 19, 2024 SponsorMemberAuthor More actions Nice! I'm all for a more generic solution and like the flexibility of the ideas here, i.e. name attribute for outputting an already generated token, and expires attribute for longer-term storage, with a corresponding if_ tag. Using the txp_token table sounds like a perfect fit.
@phiw13 : Yes, that works fine too. I didn't have the plugin, so just used txp:php to achieve the same thing and put it directly in a variable.
@Bloke: Have I misunderstood the shortcode? Wouldn't smd:token generate a token the first time it encounters the name attribute, and re-use the existing named token when it encounters it again (presumably then, the other attributes like length would be ignored). You wouldn't need if_token to detect if it doesn't exist.
It's not so much that we don't have a way to do it at the moment. The situation I am trying to cover is to find a clean way to make themes CSP-ready in advance without cluttering the code with too many if_ conditionals. Ideally, if the site user hasn't set CSP headers, you don't want to output a blank nonce="" attribute, but should CSP headers be set, you want your template to output the script/style/link tags with the nonce without any retrofitting. Maybe something like a nonce shortcode form with:
<smd::if_token name="mycsp" trim>
nonce="<smd::token name="mycsp" prefix="nonce-" />"
</smd::if_token>
and then use it as follow:
<link <txp::nonce /> rel="stylesheet" href="styles.css">
I just wonder whether that could all be handled by a single tag without the detour of the shortcode? Using wraptag solves the blank output …
<smd::token name="mycsp" wraptag='nonce="<+>"' />
… but will presumably generate a new token if it doesn't already exist. Adding a reuse attribute to suppress generation of a token, or switching around the behaviour to use a named token as standard and add a generate attribute to trigger generation might work but differs from the way other txp tags work.
...
On the txp:css front, either the tag could be made contextually intelligent, which would mean existing templates that use txp:css would be CSP-enabled). Or we use format="url" to construct our own link tag with a nonce. If we had a format="" attribute for outputting raw css, we could also construct CSP-ready inline style blocks. jools-r commented on Jul 19, 2024 SponsorMemberAuthor More actions On detecting whether CSP headers are set, might something similar to how admin-side CSP settings are stored in config.php be a possible route? If we had a defined $txpcfg item for the public-side CSP settings, and page_url could retrieve the corresponding entry – or txp:header knows how to respond to them – would that offer a way to preconfigure themes for CSP and allow a site user to set their headers independently without squirreling in the code. Bloke commented on Jul 19, 2024 Ack, youre right about the shortcode, sorry. Got carried away there. But yes the code could ignore the additional attributes if it discovers a second call of the same named token. That might be neater, although I'm not sure it's very intuitive. Hmmm.
So, are you thinking that if we allowed a similar value to be added to config.php with the {TEXTPATTERN_CSP_NONCE} replacement in it, the public side could detect that and apply the nonce and then render the relevant nonce, or not, in relevant tags? If we wired up the txp:css tag to output via \Textpattern\UI\Style that would probably just work since it doesn't care where it's used. It just checks if the global is set and uses it. Could that cause CSP value clashes if public tags are used on the admin side (e.g. for dashboards?). Is it safer to introduce a new constant/global pair and then retrofit UI\Style and UI\Script to process them both?
Side note: shouldn't this line have a conditional around it? If the CONTENT_SECURITY_POLICY constant isn't defined, that'll output an empty content-security-policy header won't it? Is that even allowed in the spec? Bloke commented on Jul 19, 2024 As a stop-gap, I've published an update to smd_token with the same-page name feature and conditional.
I'm mulling over the extension about storing the token in the database. The sticking point is that the token has to have a numeric reference_id (because it's primarily designed to link a token to another table such as the user table). That allows the (random) selector to be published as a reference to the real ID (row) to which the token relates without leaking any sensitive data.
The trouble with that for our purposes here is that we need to come up with some unique numeric value for a user-submitted tag value. The only usable value we have is the name. There are lots of PHP functions that will take some string and hash it, but they almost all output hexadecimal or alphanumeric strings. So another layer of conversion needs to take place somehow to make a pure integer that somehow maintains uniqueness.
Ideas welcome. phiw13 commented on Jul 22, 2024 @Bloke the plugin works nicely now.
However I am still unable to insert the value in a txp:header; but that is a separate issue; I opened a forum thread for it. jools-r commented on Jul 22, 2024 SponsorMemberAuthor More actions @Bloke Big thanks on the smd_token update. That is much more along the lines I was thinking. That gives us good flexibility and a cleaner tag. I still wonder whether there may be some mileage in making it a core tag – I guess it depends on whether CSP will become standard practice in future websites, in which case one would expect future themes to need to cater for it "out of the box".
The trouble with that for our purposes here is that we need to come up with some unique numeric value for a user-submitted tag value.
Um yeah. You're right: the txp_token table doesn't have a name column, so you need a unique numeric id that is computationally linked to the named string. Would crc32 help here at all? The id col is of type INT which research says has a 32-bit maximum size. I guess that's not what it was designed to be used for, though...
So, are you thinking that if we allowed a similar value to be added to config.php with the {TEXTPATTERN_CSP_NONCE} replacement in it, the public side could detect that and apply the nonce and then render the relevant nonce, or not, in relevant tags?
Yes. That was the idea. But I'm quickly getting out of my depth here and only borrowing the same concept already used on the admin side: again, I am thinking about how themes can be preconfigured to be CSP-capable so that a site admin can later add their headers and the relevant tags/theme code are ready to act on it. That's pretty much what we have on the admin-side now. It's a complex topic and @phiw13's post on the forum shows how complex it is to construct as tags. The notation in config.php is a bit easier to handle and would seem as good a place as any to store such 'freeform' settings. So yes, that could be via a corresponding, independently settable constant/global pair in the config.
Re. public tags on the admin-side. Doesn't the context determine the nonce? When you use public tags in a dashboard for the admin side, does the context switch from 'admin' to 'public' to enable their use? If not, the admin csp nonce would prevail. If yes, maybe pre-check for existence for of the admin csp nonce in such situations.
Side note: shouldn't this line have a conditional around it? If the CONTENT_SECURITY_POLICY constant isn't defined, that'll output an empty content-security-policy header won't it? Is that even allowed in the spec?
I think you're right. Or it should go a couple of lines further up within the if construction. Bloke commented on Jul 23, 2024 I'm happy to add the ability for front of house sites to implement CSP more easily. i.e. move smd_token as a concept to core and/or find a way to wire tokens into the config file as you suggest, or something else. Shall we hash out the most flexible way to do this here?
Let's shoot for implementing this during beta.
I think you're right. Or it should go a couple of lines further up within the if construction.
I agree. We can fix this during beta too. phiw13 commented on Jul 23, 2024
- I am not very enthusiast about dealing with CSP-tokens or settings via config files for the public side (it is OK, as now, for the admin side). config files (located on the files system) are usually only accessible via sFTP, which limits a little who can manage this.
- or do you have something in mind that is hooked to the (advanced) Preferences Panel?
- the way
smd_tokens works now is great. I got it hooked to the few <txp:css calls I use, usingformat="url" or flat.url.
- How would your system work for direct links to JS-scripts and stylesheet?
Bloke commented on Jul 23, 2024
- I am not very enthusiast about dealing with CSP-tokens or settings via config files for the public side
I agree it's not ideal. If there's a better way to manage them, let's figure that out. Bloke commented on Aug 12, 2024 petecooper commented on Nov 18, 2025 @Bloke What's your confidence level for this being a fit for 4.9.0? Did anything untoward come of the beta cycle? Bloke commented on Nov 18, 2025 Nothing broke in beta with the conditional header.
Not sure if we should just stick with the plugin for now or try to fold the essence of smd_token into core. petecooper commented on Nov 18, 2025
Not sure if we should just stick with the plugin for now or try to fold the essence of smd_token into core.
Tagging @bloatware & @jools-r for comment. jools-r commented on Nov 18, 2025 SponsorMemberAuthor More actions My view is still more or less what I wrote above:
I still wonder whether there may be some mileage in making it a core tag – I guess it depends on whether CSP will become standard practice in future websites, in which case one would expect future themes to need to cater for it "out of the box".
We have a way of dealing with it now thanks to smd_token, or a similar txp:php snippet, but if it ends up being something one needs routinely for sites that have javascript, iframes or form submits to the point that it becomes a staple plugin, then it may be better to have it in the core. petecooper commented on Nov 18, 2025 Thanks @Bloke & @jools-r. As I understand it, the existence of smd_token makes it less of an urgent / important thing for core in 4.9.0, so it might be viable to roll it in on a patch release instead. Is that appropriate? petecooper commented on Dec 3, 2025 Switching to 4.9 milestone for now.
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|