Build a Small Perl Source Filter You Can Test Safely
You will build and run a tiny Perl source filter that transforms ROT13-encoded source before Perl parses it. The example uses Perl 5.38.2 and the core Filter::Util::Call module installed with perl-doc. Allow about 15 minutes. You need a shell, Perl, and a directory where you can create two disposable files.
The route
Jump straight to the step you need, or tick off Done means at the end.
What a source filter changes
Perl normally reads a file and sends its text to the parser. A source filter inserts a transformation between those two stages. The transformation happens while the file is being compiled, not after the program has started running. A filter can therefore make text that is not ordinary Perl source become valid Perl source before the parser sees it.
That timing is the first safety boundary. A filter can change code, hide syntax errors until compile time, and run setup code from a use statement before the rest of the file is read. It is not a general text-processing substitute. The installed perlfilter manual describes filters as string-level tools: they do not understand comments, quoted strings, or heredocs reliably.
Checkpoint: confirm the local Perl
Check the interpreter and the two modules that this guide uses:
perl -v
perl -MFilter::Util::Call -e 'print "Filter::Util::Call available\n"'
perl -MFilter::Simple -e 'print "Filter::Simple available\n"'
On the machine used for this guide, Perl reports version 5.38.2 and both module checks succeed. The filter itself only needs Filter::Util::Call. Filter::Simple is mentioned here because it is the friendlier interface included with the standard distribution, but it is not needed for the example.
1. Create the filter module
Make a file named Rot13.pm in a disposable directory. The module's import method runs because the example program uses use Rot13. It creates an object and attaches it to the current source stream with filter_add.
mkdir -p "$HOME/perl-filter-demo"
cd "$HOME/perl-filter-demo"
cat > Rot13.pm <<'EOF'
package Rot13;
use Filter::Util::Call;
sub import {
my ($type) = @_;
filter_add(bless [], $type);
}
sub filter {
my ($self) = @_;
my $status = filter_read();
tr/n-za-mN-ZA-M/a-zA-Z/ if $status > 0;
return $status;
}
1;
EOF
Each call to filter_read obtains the next piece of source and appends it to Perl's default $_. A positive status means data was read, zero means end of file, and a negative value means an error. The filter must return that status. The transliteration is applied only when data was available.
The module deliberately carries no meaningful state. The blessed array is just the filter object required by this interface. A stateful filter can bless a hash instead, but state also creates more opportunities for a line-oriented transformation to lose track of the source.
2. Put encoded source after the use statement
Create a program in the same directory. The first line is ordinary Perl and installs the filter. The next line is ROT13 text, so it is not readable Perl until the filter translates it.
cat > demo.pl <<'EOF'
use Rot13;
cevag "uryyb sebz n fbhepr svygre\a";
EOF
perl -c demo.pl
perl demo.pl
Expected output is:
demo.pl syntax OK
hello from a source filter
The filter is installed while Perl compiles the first line, so the second line is transformed before parsing. A common distraction is to test the filter on the use Rot13 line itself. That line has already been compiled by the time the module installs its filter.
Checkpoint: see what the filter actually receives
Change the encoded line to invalid ROT13 or ordinary text, then rerun perl -c demo.pl. Parse errors describe the transformed source as Perl sees it, not necessarily the bytes stored on disk. Keep the original file under version control or make a copy before experimenting, because debugging the encoded representation by eye is needlessly difficult.
3. Understand scope and filter order
A filter applies to the source stream of the file that installed it. If that file then executes use OtherModule, Perl creates a new source stream for OtherModule; the first file's filter does not automatically rewrite the module's source. This is a useful containment rule, but it also surprises people who expect one filter to cover an entire dependency tree.
You can install more than one filter in one file. Perl sends source through them in the order they appear. If the first filter decodes data and the second decompresses it, the source must be encoded in the reverse order required to produce the original text. Test the complete chain with a small fixture before applying it to a real module.
External filters are possible too. The manual describes filters that send source through another executable, such as a C preprocessor. That introduces a subprocess for each use and, with shell-based variants, another command-parsing boundary. Prefer direct argument passing when an external filter is genuinely necessary, and never pass untrusted source or interpolated user input to a shell filter.
Limits and recovery
Do not use a source filter as a parser. A line-oriented substitution can be fooled by a heredoc, a quoted string containing marker text, or a regular expression spread across lines. The manual also records that __DATA__ content is not filtered, and that some filters use or alter the DATA handle. Code relying on that handle needs a separate test.
Source obfuscation is not a security boundary. Perl must have enough information to recover the source in order to compile it, so a determined user who can run the program can retrieve that information. Do not put passwords, private keys, or proprietary security decisions into an obfuscating filter.
To undo this guide's state, remove only the disposable directory after checking its path:
cd ..
pwd
rm -rf "$HOME/perl-filter-demo"
The final command is destructive. It removes the two demo files and nothing else when the directory is exactly the one created above. If you want to keep the experiment, omit it.
Done means
Filter::Util::Callloads successfully on the intended Perl interpreter.perl -c demo.placcepts the transformed source.perl demo.plprintshello from a source filter.- You know the filter applies to one source stream, runs at compile time, and does not understand Perl syntax.
- You have not treated obfuscation as encryption or passed untrusted text through a shell filter.