| ▲ | ratorx 9 days ago |
| I’m not convinced that a language level feature is worth it for this. You could achieve the same thing with a function returning an f-string no? And if you want injection safety, just use a tag type and a sanitisation function that takes a string and returns the type. Then the function returning the f-string could take the Sanitised string as an argument to prevent calling it with unsanitised input. I guess it’s more concise, but differentiating between eager and delayed execution with a single character makes the language less readable for people who are not as familiar with Python (especially latest update syntax etc). EDIT: to flesh out with an example: class Sanitised(str):
# init function that sanitises or just use as a tag type that has an external sanitisation function. def sqltemplate(name: Sanitised) -> str:
return f”select * from {name}” # Usage
sqltemplate(name=sanitise(“some injection”)) # Attempt to pass unsanitised
sqltemplate(name=“some injection”) # type check error |
|
| ▲ | nhumrich 9 days ago | parent | next [-] |
| > You could achieve the same thing with a function returning an f-string no
no. > just use a tag type and a sanitisation function that takes a string and returns the type Okay, so you have a `sqlstring(somestring)` function, and the dev has to call it. But... what if they pass in an f-string? `sqlstring(f'select from mytable where col = {value}')` You havent actually prevented/enforced anything. With template strings, its turtles all the way down. You can enforce they pass in a template and you can safely escape anything that is a variable because its impossible to have a variable type (possible injection) in the template literal. |
| |
| ▲ | ratorx 9 days ago | parent [-] | | Added example to parent comment. This example still works, the entire f-string is sanitised (including whatever the value of name was). Assuming sqlstring is the sanitisation function. The “template” would be a separate function that returns an f-string bound from function arguments. | | |
| ▲ | nhumrich 9 days ago | parent [-] | | Yes. Only if your dev remembers to use sanatized all the time. This is how most SQL works today. You could also forget and accidentally write a f-string, or because you dont know. But with t-strings you can actually prevent unsanatized inputs. With your example, you need to intentionally sanitize still. You cant throw an error on unsanitized because the language has no way to know if its sanitized or not. Either way, its just a string. "returning an f-string" is equivalent to returning a normal string at runtime. | | |
| ▲ | ratorx 9 days ago | parent | next [-] | | Well you enforce this with types. That’s how every other language does it. By specifying that the type of the function has to be a sanitised string, it will reject unsanitised string with the type checker. > it has no way of knowing if it’s sanitised or not It does. You define the SanitisedString class. Constructing one sanitises the string. Then when you specify that as the argument, it forces the user to sanitise the string. If you want to do it without types, you can check with `isinstance` at runtime, but that is not as safe. | | |
| ▲ | nhumrich 9 days ago | parent [-] | | Your example is a bit too simple. What I mean by that is, you have hardcoded your function to inject a specific part of your string. But t-strings allow you to write the full query `t'select * from table where name = {name}'` directly, without have to use a function. This matters because the SQL connection library itself can enforce templates.
SQL libraries can NOT enforce "sanitized types" because then you couldnt write raw sql without problems. They have to know the difference between "this is hard coded" and "this is a dynamic user variable". And the libraries can't know that, without t-strings. |
| |
| ▲ | stefan_ 9 days ago | parent | prev [-] | | No, most SQL today uses placeholders and has since circa 2008. If you are sanitizing you are doing it wrong to begin with. |
|
|
|
|
| ▲ | shikon7 9 days ago | parent | prev | next [-] |
| If its only use is to make injecton safety a bit easier to achieve, it's worth it to me. |
| |
| ▲ | ratorx 9 days ago | parent [-] | | Does it make it easier? The “escape” for both is to just use unsafe version of the Template -> string function or explicitly mark an unsafe string as sanitised. Both seem similar in (un)safety | | |
| ▲ | davepeck 9 days ago | parent [-] | | > the Template -> string function There is no such function; Template.__str__() returns Template.__repr__() which is very unlikely to be useful. You pretty much have to process your Template instance in some way before converting to a string. | | |
| ▲ | ratorx 9 days ago | parent [-] | | Right, but it is possible to write a template -> string function that doesn’t sanitise and use it (or more realistically use the wrong one). Just as it’s possible to unsafely cast an unsafe string to a sanitised one and use it (rather than use a sanitise function that returns the wrapper type). They are both similar in their unsafety. |
|
|
|
|
| ▲ | vjerancrnjak 9 days ago | parent | prev | next [-] |
| It's worse than function returning an f-string. Template type is very flat, you won't know which arguments are left unbound. modules, classes, protocols, functions returning functions, all options in Python, each work well for reuse, no need to use more than 2 at once, yet the world swims upstream. |
| |
|
| ▲ | itishappy 9 days ago | parent | prev [-] |
| I don't see how this prevents calling your returned f-string with unsensitized inputs. evil = "<script>alert('evil')</script>"
sanitized = Sanitized(evil)
whoops = f"<p>{evil}</p>"
|
| |
| ▲ | ratorx 9 days ago | parent [-] | | I’m not sure you understood my example. The f-string is within a function. The function argument only accepts sanitised input type. If you create a subclass of str which has an init function that sanitises, then you can’t create a Sanitised type by casting right? And even if you could, there is also nothing stopping you from using a different function to “html” that just returns the string without sanitising. They are on the same relative level of safety. | | |
| ▲ | itishappy 9 days ago | parent [-] | | Oh, I'm pretty sure I didn't understand your example and am probably missing something obvious. That's why I'm here asking dumb questions! I think I'm following more, and I see how you can accomplish this by encapsulating the rendering, but I'm still not seeing how this is possible with user facing f-strings. Think you can write up a quick example? | | |
| ▲ | ratorx 9 days ago | parent [-] | | Added example to parent comment. | | |
| ▲ | itishappy 9 days ago | parent [-] | | Thanks mate! (BTW: Indenting code with four spaces makes HN format it like code.) So the thing I'm still not getting from your example is allowing the template itself to be customized. evil = "<script>alert('evil')</script>"
template1 = t"<p>{evil}</p>"
template2 = t"<h1>{evil}</h1>"
html(template1)
html(template2)
| | |
| ▲ | ratorx 9 days ago | parent [-] | | template1 is a function that takes in a parameter evil (with a SanitisedString type that wraps a regular str) and returns the fully expanded str. It is implemented by just returning an f-string equivalent to the t-string in your example. Same with template2. Using the SanitisedString type forces the user to explicitly call a sanitiser function that returns a SanitisedString and prevents them from passing in an unsanitised str. | | |
| ▲ | itishappy 8 days ago | parent [-] | | You're just handing off responsibility for sanitization to the user instead of the library author. With t-strings the rendering function is responsible for sanitization, and users can pass unrendered templates to it. With f-strings there's no concept of an unrendered template, it just immediately becomes a string. Whoever is creating the template therefore has to be careful what they put in it. |
|
|
|
|
|
|