++ your observation on not giving an out on detainting.
I've got a solution to detainting which I think is very well suited to refactoring old code. It can be retrofitted as a single chunk of initialisation code, and old code will automatically validate and detaint the rigged variable on every assignment or modification.
use Tie::Constrained qw/detaint/;
tie my $var, 'Tie::Constrained', sub {
my $re = qr/whatever/;
$_[0] =~ /$re/ and &detaint;
};
Later, when you say,
$var = $tainted_thing;
$var is validated and untainted, while $tainted_thing remains tainted and unmodified. The trick is that the &detaint call acts on an anonymous copy of $tainted_thing's value just before assignment to $var. (Tie::Constrained::detaint() is almost identical to
frodo72's original detaint()).
This does not address OP's excellent question about generalizing his detaint routine. I question the value of that as a general practice, but it may be useful for an application which must iterate over a bunch of similar-type values.