Closed vdmitriyev closed 1 month ago
Neither one will ever change the provided URL. URL()
will check the URL and error if the check fails. SafeURL()
isn't a function at all, but rather a type, and converting your string
to the SafeURL
type allows you to declare the URL safe and bypass the checks.
Hi @vdmitriyev, I think that if I clarify the difference between escaping and sanitization and why templ does this stuff, it might become clear...
Escaping is the process of turning a URL string into something that is allowed within a HTML attribute - i.e. HTML encoding.
So that means http://google.com/?q=test&language=en-US
(correctly) becomes http://google.com/?q=test&language=en-US
.
Without escaping, any string that contained a double quote could start to modify the underlying HTML document structure. We don't want that, because that's a vector for a content injection attack (https://owasp.org/www-community/attacks/Content_Spoofing) or XSS attack.
For example, if I used a URL string containing: http://google.com" onclick="alert('hello')
and templ didn't escape the content, instead of the expected HTML output of <a href="google.com">
I'd get <a href="http://google.com" onclick="alert('hello')">
which injects script into my page.
templ always escapes output content, unless you explicitly use the unsafe templ.Raw
component to write out arbitrary HTML, because failure to do so would allow injection attacks.
templ allows developers to include string content that is loaded from external resources, such as databases, content management systems and files. That's the whole point, really, or we'd just use plain HTML.
Those external resources might contain content that is not under the developer's control. For example, in Github, you can provide a URL to your personal website in your profile.
However, it turns out that you can abuse URIs and include malicious content in them. See https://positive.security/blog/ms-officecmd-rce for an example - as you can see from the article, a URL that starts with ms-officecmd:
instead of http:
can be used to damage the recipient's computer.
So, if you're building a system, one of your end users might manage to get a malicious URL to display on your system. Not good!
To avoid this security threat, templ forces you to sanitize URLs by using the templ.URL
function. The function strips out untrusted / unusual schemes from URIs. In most cases, this is what you want.
BUT... if you know what you're doing, and you know that the URL is definitely safe, you can mark the string as safe by converting the string into a templ.SafeURL
type.
@kalafut thank your for providing further insides on what SafeURL()
is, it really helps to understand how it works
replying to: https://github.com/a-h/templ/issues/908#issuecomment-2336633242
@a-h thanks for your very detailed explanations and materials, I appreciate that very much. Now it got much clearer why URL() is enforced by templ
and why it works like that by-design
. Because it would be easier to assume, that all values that are passed to a templ
-template are non trusted by default and handle it.
However, I am still confused, why templ.SafeURL()
is still keep changing supplied string. Here is an example using <a>
tag with workaround how to overcome sanitization using <form>
.
Instead of using <a>
tag to generate a clickable URL with parameters (on a server side without using any input user), a HTML <form>
must be used, to overcome sanitization through templ.SafeURL()
.
UploadViewUploadedAsForm
-> workaround with form, which send GET requests to the server with required parametersUploadViewUploadedAsLink
-> who I would expect to do it, if templ.SafeURL
would not sanitize a string and change &
to &
<form>
templ UploadViewUploadedAsForm(fileHash string, exerciseID string, uploadType string) {
<form
id={ fmt.Sprintf("upload-download-form-%s", exerciseID) }
method="GET"
action="/upload/download"
>
<input type="hidden" name="id" value={ exerciseID }/>
<input type="hidden" name="group" value="exercises"/>
<input type="hidden" name="upload-type" value={ uploadType }/>
<input type="hidden" name="hash" value={ fileHash }/>
<button
type="submit"
class="btn btn-success"
id={ fmt.Sprintf("upload-download-form-button-%s", exerciseID) }
hx-swap="true"
>Download</button>
</form>
}
<form>
rendered<form id="upload-download-form-E01" method="GET" action="/upload/download"><input type="hidden" name="id" value="E01"> <input type="hidden" name="group" value="exercises"> <input type="hidden" name="upload-type" value="test-type"> <input type="hidden" name="hash" value="test"> <button type="submit" class="btn btn-success" id="upload-download-form-button-E01" hx-swap="true">Download</button></form>
templ.SafeURL
with unexpected output (changes &
to &
)templ UploadViewUploadedAsLink(fileHash string, exerciseID string, uploadType string) {
<a
href={ templ.SafeURL(fmt.Sprintf("/upload/download?id=%s&group=exercises&upload-type=%s&hash=%s", exerciseID, uploadType, fileHash)) }
id={ fmt.Sprintf("upload-download-form-button-%s", exerciseID) }
>
Download
</a>
}
templ.SafeURL
<a href="/upload/download?id=E01&group=exercises&upload-type=test-type&hash=test" id="upload-download-form-button-E01">Download</a>
templ UploadViews() {
@UploadViewUploadedAsForm("test", "E01", "test-type")
@UploadViewUploadedAsLink("test", "E01", "test-type")
}
Hope, that this example make it clearer what I would like to achieve with templ.SaveURL()
.
It's doing the correct thing here. It's escaping, not sanitizing, the content. Your expectation is incorrect.
@a-h thank you for your prompt response and clarification.
So, there is no way available to "turn off" escaping done by templ, even for some special cases (see the example from the previous comment -> https://github.com/a-h/templ/issues/908#issuecomment-2351658168)? Because templ.SaveURL()
is not a correct approach to be used to achieve such behavior, due to the reason that escaping
and sanitization
got different goals.
The only way to overcome this, is to user <forms>
(which will not work for HTMX's hx-vals).
@vdmitriyev I think what @a-h is getting at here is that for your use case the escaping is correct.
This anchor:
<a href="/upload/download?id=E01&group=exercises&upload-type=test-type&hash=test" id="upload-download-form-button-E01">Download</a>
Will take you to:
/upload/download?id=E01&group=exercises&upload-type=test-type&hash=test
Which I believe is your expectation.
@joerdav thank you for the clarification, unfortunately it didn't work in my case.
In my understanding, the GET request using <form>
or <a>
should be equal and initiate an equal GET request to a backend. But I am going to investigate the backend side in details and more carefully, in order to see, what exactly backend receives in both cases.
I'll close this, since no further discussion has happened. Feel free to re-open if needed, but I'm pretty sure templ is not doing anything wrong here.
I'll close this, since no further discussion has happened. Feel free to re-open if needed, but I'm pretty sure templ is not doing anything wrong here.
@a-h tanks for the note closing note. templ
is definitely not doing anything wrong.
The only suggestion from my side for now would be to reflect the escaping
behavior of templ
in documentation besides sanitization
(e.g., for example this page - https://templ.guide/syntax-and-usage/attributes/#url-attributes). This could potentially reduce the confusion.
Agreed, I just made a commit to add some extra information to the docs.
Hi all,
I would really appreciate a help/advice on an issue with sanitization. Despite a number of old issues were helpful:
Description
It looks like there is no different between outputs of
templ.SafeURL
andtempl.URL
when they are applied to a<a href="<URL>">test</a>
To Reproduce
Templ file:
Screenshot:
Expected behavior
Expected behavior would be omit a url sanitization/escaping by
templ.SafeURL
, as it mentioned in the documentation here - https://templ.guide/syntax-and-usage/attributes/However it also could be that I am simply confused about the expected behavior of
templ.SafeURL
Additional
In the documentation here https://templ.guide/security/injection-attacks/, the examples should look as follows (closing tag is missing by tag, so
templ
cannot be compiler properly):Environment (version)
P.S.:
Thank you for an amazing library and your time, it took you all to develop it! Besides the great library I really appreciate the documentation. It is very helpful how developer experience with VS Code extension. further details on deployment with docker, etc. 🚀 💪