Jump to content
TNG Community

Security & Privacy with the use of hyperlinks in Citations and TNG in general


Rob Severijns

Recommended Posts

Rob Severijns

I know I'm not a security expert but still hope it's helpfull to some of you.

If anyone likes to chime in, feel free to do so.

About the topic

In TNG you can use the "rel" attribute with hyperlinks in order to add extra security & privacy.

See the table below for an explanation of the "rel" attribute or search the internet for more detailed information.

image.png

Unfortunatly the "rel" attribute can't be used with hyperlinks in citations.

To mitigate this you can use the JavaScript code at the end of this topic.

What does it do:

  • Detects external links only (not your own domain)
  • Forces them to open in a new tab (target="_blank")
  • Automatically adds secure attributes: rel="noopener noreferrer nofollow"
  • Works for dynamically added links (AJAX, React, etc.)

Which gives you:

  • SEO-safe: external links use nofollow
  • Security-safe: prevents tab-nabbing with noopener noreferrer
  • User-friendly: external links open in new tabs
  • Robust: updates automatically as new links appear

Just add the script at the bottom of the footer.php in your template folder.

BTW TNG doesn't always have the closing </body> tag in footer.php but that fine as long as the script is placed at the bottom of the footer.php it works.

If you remove the "nofollow" value from the script you allow search engines to to use the link as a ranking signal.

Simply said:

  • Skipping nofollow won’t break anything.
  • It just means you’re passing SEO value to the linked page.
  • Only use nofollow when you don’t fully trust or control that link.

The script is shown below.

<!-- Add this just before the closing </body> tag -->
<script>
(() => {
  const addSafeExternalAttrs = link => {
    const href = link.getAttribute('href');
    if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:')) return;

    const linkUrl = new URL(href, window.location.origin);
    if (linkUrl.origin === window.location.origin) return;

    link.setAttribute('target', '_blank');

    const existingRel = link.getAttribute('rel') || '';
    const relValues = new Set(existingRel.split(/\s+/).filter(Boolean));
    ['noopener', 'noreferrer', 'nofollow'].forEach(v => relValues.add(v));
    link.setAttribute('rel', Array.from(relValues).join(' '));
  };

  document.querySelectorAll('a[href]').forEach(addSafeExternalAttrs);

  const observer = new MutationObserver(mutations => {
    for (const mutation of mutations) {
      mutation.addedNodes.forEach(node => {
        if (node.nodeType === 1) {
          if (node.tagName === 'A') addSafeExternalAttrs(node);
          else node.querySelectorAll?.('a[href]').forEach(addSafeExternalAttrs);
        }
      });
    }
  });

  observer.observe(document.body, { childList: true, subtree: true });
})();
</script>

To test if the script works you can do the following:

  • Go to a page with an external hyperlink
  • Rightclick the hyperlink and choose "Inspect"

You should see something like this:

<a href="https://example.com" target="_blank" rel="noopener noreferrer nofollow">Example</a>

If those attributes were missing before but now appear automatically the script works.

If they don’t appear, clear the cache or hard refresh with Ctrl+F5 or otherwise check the console for errors (red text)

 

Link to comment
Share on other sites

Michel KIRSCH

Hi Rob,

trying the code with insert it into the end.php file.

It works as attended.

 

Michel

Link to comment
Share on other sites

Rob Severijns

Hi Michel,

Thank you for testing and replying.
Is there a specific reason why you entered the JS script in end.php instead of footer.php?

Both seem to work

Link to comment
Share on other sites

Rob Severijns

Hi Michel,

Did a quick follow up check and it seems that footer.php is the better option since it loads with every page.

end.php can be used if end.php is called for at the end of every page.
Not sure if that's the case with TNG
Let me know if my thoughts are correct or if it needs further explanation.

Link to comment
Share on other sites

Michel KIRSCH

Hi Rob,

I just do a quick test with getperson.php.

And it seems that end.php is loaded by genlib.php. ($flags['noend'] is never set)

I didn't look for admin pages... :-(

 

Michel

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...