GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

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 #1933

New issue
Copy link
New issue
Copy link
Open
Open
A txp:tag to create a page-wide CSP nonce#1933
Copy link
Labels
security
Milestone
v4.9

Description

@jools-r
jools-r
opened on Jul 18, 2024
Issue body actions

On 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.

Activity

  1. Bloke commented on Jul 18, 2024

    @Bloke
    Bloke
    on Jul 18, 2024
    Member
    More actions

    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.

  2. Bloke commented on Jul 18, 2024

    @Bloke
    Bloke
    on Jul 18, 2024
    Member
    More actions

    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;
    }
    
  3. jools-r commented on Jul 18, 2024

    @jools-r
    jools-r
    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.

  4. phiw13 commented on Jul 19, 2024

    @phiw13
    phiw13
    on Jul 19, 2024
    More actions

    @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…

  5. Bloke commented on Jul 19, 2024

    @Bloke
    Bloke
    on Jul 19, 2024
    Member
    More actions

    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?

  6. jools-r commented on Jul 19, 2024

    @jools-r
    jools-r
    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.

  7. jools-r commented on Jul 19, 2024

    @jools-r
    jools-r
    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.

  8. Bloke commented on Jul 19, 2024

    @Bloke
    Bloke
    on Jul 19, 2024
    Member
    More actions

    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?

  9. Bloke commented on Jul 19, 2024

    @Bloke
    Bloke
    on Jul 19, 2024
    Member
    More actions

    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.

  10. phiw13 commented on Jul 22, 2024

    @phiw13
    phiw13
    on Jul 22, 2024
    More actions

    @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.

  11. jools-r commented on Jul 22, 2024

    @jools-r
    jools-r
    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.

  12. Bloke commented on Jul 23, 2024

    @Bloke
    Bloke
    on Jul 23, 2024
    Member
    More actions

    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.

  13. phiw13 commented on Jul 23, 2024

    @phiw13
    phiw13
    on Jul 23, 2024
    More actions
    • 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?
  14. Bloke commented on Jul 23, 2024

    @Bloke
    Bloke
    on Jul 23, 2024
    Member
    More actions
    • 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.

  15. Bloke commented on Aug 12, 2024

    @Bloke
    Bloke
    on Aug 12, 2024
    Member
    More actions

    I just fixed the CSP header to only output if there's a nonce defined. Please test as part of the 4.9.0 beta cycle.

  16. petecooper
    added
    security
    on Aug 15, 2024
  17. petecooper
    added this to the v4.9.0 milestone on Mar 14, 2025
  18. petecooper commented on Nov 18, 2025

    @petecooper
    petecooper
    on Nov 18, 2025
    Member
    More actions

    @Bloke What's your confidence level for this being a fit for 4.9.0? Did anything untoward come of the beta cycle?

  19. Bloke commented on Nov 18, 2025

    @Bloke
    Bloke
    on Nov 18, 2025
    Member
    More actions

    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.

    Reacted by Pete Cooper
  20. petecooper commented on Nov 18, 2025

    @petecooper
    petecooper
    on Nov 18, 2025
    Member
    More actions

    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.

  21. jools-r commented on Nov 18, 2025

    @jools-r
    jools-r
    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.

    Reacted by Pete Cooper
  22. petecooper commented on Nov 18, 2025

    @petecooper
    petecooper
    on Nov 18, 2025
    Member
    More actions

    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?

  23. petecooper
    modified the milestones: v4.9.0, v4.9 on Dec 3, 2025
  24. petecooper commented on Dec 3, 2025

    @petecooper
    petecooper
    on Dec 3, 2025
    Member
    More actions

    Switching to 4.9 milestone for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    security

    Type

    No type

    Projects

    No projects

      Milestone

      • v4.9No due date

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.