Yes, impurity (or rather the representation thereof) is contagious. This is where the common "functional core, imperative shell" comes into play. You generally try to push side effects to the edge.
There are other approaches you can take too, like encoding your effects in a monad which has a different runtime representation in tests. It's like mocking on steroids with type safety.
No contamination, main has a type ‘IO ()’. A readline function would be ‘IO String’, so in a way you can think of them as “upon execution, we have this type”, or the “execution wraps this type”. You can’t touch the inner type of such function naively, so this purity is viral.