The file isn't going to be used after it's closed. The string from the file is going to be used after the file is closed. But with lazy IO, you don't have (all of) the string from the file yet, even though you've "read" it.
That is, the abstractions don't do what non-Haskell abstractions would lead you to expect.
Right. The whole point of lazy IO is that you hide the actual IO behind values that don't appear to be IO. That means your use of the file isn't visible to the type system, so it's not really reasonable to expect it to prevent it. Unless I miss something, you can't write lazy IO without lying to the type system anyway (unsafePerformIO).
That is, the abstractions don't do what non-Haskell abstractions would lead you to expect.