But of course we just laugh about Perl and pat ourself on our backs with our safe new languages because we clearly know much more than those anachronistic neckbeards.
And there is some justification for that: if those "safe new languages" are doing the type checking at compile time, that is better than only finding out you have a safety issue when you fail at run time.
It's not explicit enough and it's easy enough to find legitimate code with accidental untainting of dangerous data.
Ruby requires an explicit untaint call, and IMHO it's the right way to go.
It's leaky as hell, because all components have to get the marking right. Also, not all Objects have equal trust levels. Objects created due to a HTTP request (GET-Parameters) should certainly be tainted, but how about Object read through IO - do we trust out filesystem? Do we trust the database? Thats more of an architectural decision.
In the end, the problem comes down to this: you have to whitelist the world and everything you miss is an error.
And yes, this can be encoded in the type system and you can also make it so the sanitization is context dependent, i.e. http://www.comp.nus.edu.sg/~prateeks/papers/csas-ccs11.pdf
Different languages (SQL, JS, HTML, Shell, Plaintext, etc.) are treated as different types. Language-specific functions only accept arguments of the relevant type (eg. shell_exec takes Shell, db_query takes SQL, etc.). User input is Plaintext (usually; sometimes it might be something more specific like BASE64).
Different languages can't be combined (eg. SQL can't be concatenated with Plaintext), but they can be converted down to Plaintext and Plaintext can be converted to any language via escaping functions. This avoids injection attacks, since the only way to please the type-checker is by escaping properly.