Map Perl Prerequisites Safely with scandeps

scandeps reads a Perl script and lists the modules it actually pulls in, which beats guessing from a pile of use statements. You will produce a prerequisite list, decide when recursive scanning is useful, and investigate the files behind the result. The examples use scandeps from libmodule-scandeps-perl version 1.35-1ubuntu0.24.04.1. Allow about fifteen minutes if the script is small and already available.

Security boundary: the installed version is older than the upstream 1.36 fix for CVE-2024-10224. The project advisory says ScanDeps is intended for sanitised input. Do not point this command at attacker-controlled filenames or source, and do not treat its result as permission to execute or install anything automatically. The examples below use files you own and harmless code. No elevated privileges are needed.

1. Check the installed command

Confirm the executable and package are the ones you expect:

$ command -v scandeps
/usr/bin/scandeps
$ dpkg-query -W -f='${Package} ${Version}\n' libmodule-scandeps-perl
libmodule-scandeps-perl 1.35-1ubuntu0.24.04.1

This release does not provide a useful --version option; it reports an unknown option and then prints usage text, so use the package query for the installed Debian package version. Checkpoint: if command -v finds a different path, stop and inspect that installation before comparing output with this guide.

2. Scan a Perl file recursively

Create or choose a trusted Perl file. This example imports File::Spec and uses it without touching the filesystem:

$ scandeps /path/to/project/bin/example.pl
'Carp'               => '1.54',
'Config'             => '5.038002',
'Cwd'                => '3.89',
'DynaLoader'         => '1.54',
'File::Spec'         => '3.88',
'File::Spec::Unix'   => '3.88',
'strict'             => '1.12',
'warnings'           => '1.65',
...

The actual list depends on the Perl installation and the code. Output is a Perl hash fragment in PREREQ_PM-style form: module names as keys, detected versions as values. By default, scandeps follows module files it finds, so a small source file can produce far more entries than its visible use statements suggest.

Do not treat this as a complete packaging manifest. Static analysis is heuristic, and the manpage warns that missing files can be false positives. Dynamic loading, conditional code and modules selected from data may need a separate review.

3. Limit the scan to the files named

Use -R, also called --no-recurse, when you only need the direct dependencies found in the input files:

$ scandeps -R /path/to/project/bin/example.pl
'File::Spec'       => '3.88',
'File::Spec::Unix' => '3.88',
'strict'           => '1.12',

This is useful for comparing a source file's declared imports, but it can omit dependencies required by those imported modules in turn. Choose recursive mode for a deployment inventory and no-recurse mode for a narrow inspection. Checkpoint: if the two lists differ, that is normally the recursion boundary, not proof either command failed.

4. Scan a one-liner or runtime-loaded code

For Perl code held in a shell argument, use -e:

$ scandeps -e 'use File::Basename; print basename("/tmp/example.txt")'
'File::Basename' => '2.86',
'strict'         => '1.12',
...

Quoting matters here. Keep the Perl program in single quotes so the shell does not expand its variables or interpret its punctuation. If the code's imports depend on compilation or execution, -c compiles it and -x executes it in addition to static scanning:

$ scandeps -c /path/to/trusted/script.pl
$ scandeps -x --xargs='--input /path/to/trusted/data' /path/to/trusted/script.pl

Warning: --xargs is only meaningful with -x, and its value is split with Perl's shell-word parser and supplied as @ARGV. Treat -x as code execution, not passive inspection: use a disposable, trusted script and data, review side effects first, and never run it as root. There is no undo for a program's side effects, so choose a read-only test or a temporary working directory.

5. Include core modules only when the report needs them

Core modules are normally excluded from the output and from recursive search. Add -B, or --bundle, when the dependency inventory must include them:

$ scandeps -B /path/to/trusted/script.pl
'File::Spec' => '3.88',
'strict'     => '1.12',
...

Use this for a report describing everything the program relies on, including modules supplied by Perl itself. Do not confuse "core" with "available in every Perl release": the version values still matter, and an application may need a newer module than the target host provides.

6. Inspect files and missing dependencies

Verbose mode, -V or --verbose, prints files found during scanning, dependency relationships and availability information:

$ scandeps -V -R /path/to/trusted/script.pl
# File/Spec.pm [module]
# script.pl [data]
# strict.pm [module]
#
# Legend: [C]ore [X]ternal [S]ubmodule [?]NotOnCPAN
'File::Spec' => '0', #    ? # script.pl
'strict'     => '0', #    ? # script.pl

Output varies with the script, the installed modules and whether CPANPLUS is present. The verbose report also warns about dependencies it could not find; investigate those names against the code and the target environment, since the manpage explicitly allows for false positives. A missing module here is not an instruction to install an arbitrary package.

-T, or --modtree, asks CPANPLUS for module distribution information when CPANPLUS is installed. That is metadata lookup, not installation. On a minimal system the option may be unavailable or unable to name distributions, so do not build an automated release step around it without testing that dependency first.

7. Cache a repeat scan deliberately

For a stable, trusted tree, -C or --cachedeps writes a dependency cache and reuses it later:

$ scandeps -C /tmp/scandeps-cache /path/to/trusted/script.pl
$ test -s /tmp/scandeps-cache && echo 'cache created'
cache created

The cache file is created if absent. Keep it beside a controlled build workspace, not in a shared location with an untrusted writer. Invalidate or choose a new cache when the Perl version, module tree or source changes: stale dependency information can make a report look complete while describing the wrong environment. The cache is ordinary generated state, so remove it with your normal temporary-file cleanup once the scan has been checked.

8. Turn the result into a review, not an assumption

Before using the list in packaging or deployment, compare it with the source's intentional dependencies and the target Perl version. Re-run with -V when a module is surprising, and with -R when you need to separate direct imports from recursive findings. If the script loads a module by name at runtime, test that path with -c or a carefully controlled -x run.

Keep the original source, command line and package version with the report. If a scan fails, first check the input path and read permission:

$ test -r /path/to/trusted/script.pl && echo readable
readable
$ perl -c /path/to/trusted/script.pl
/path/to/trusted/script.pl syntax OK

Do not solve an unreadable input by running the whole scan with sudo. Fix ownership or permissions through your normal administration process, and only elevate for that specific change if it is authorised. The scan itself should normally remain unprivileged.

Done means