File Coverage

blib/lib/Carp/Parse/Redact.pm
Criterion Covered Total %
statement 50 50 100.0
branch 17 22 77.2
condition 5 9 55.5
subroutine 7 7 100.0
pod 1 1 100.0
total 80 89 89.8


line stmt bran cond sub pod time code
1             package Carp::Parse::Redact;
2              
3 4     4   42295 use warnings;
  4         84  
  4         225  
4 4     4   27 use strict;
  4         9  
  4         119  
5              
6 4     4   26 use Carp;
  4         11  
  4         369  
7 4     4   3229 use Carp::Parse;
  4         52285  
  4         149  
8 4     4   3038 use Carp::Parse::CallerInformation::Redacted;
  4         12  
  4         100  
9 4     4   3661 use Data::Validate::Type;
  4         7143  
  4         2668  
10              
11              
12             =head1 NAME
13              
14             Carp::Parse::Redact - Parse a Carp stack trace into an array of caller information, while redacting sensitive function parameters out.
15              
16              
17             =head1 DESCRIPTION
18              
19             Carp produces a stacktrace that includes caller arguments; this module parses
20             each line of the stack trace to extract its arguments and redacts out the
21             sensitive information contained in the function arguments for each caller.
22              
23              
24             =head1 VERSION
25              
26             Version 1.1.5
27              
28             =cut
29              
30             our $VERSION = '1.1.5';
31              
32              
33             =head1 DEFAULTS FOR REDACTING SENSITIVE DATA
34              
35             =head2 Redacting using hash keys
36              
37             By default, this module will redact values for which the argument name is:
38              
39             =over 4
40              
41             =item * password
42              
43             =item * passwd
44              
45             =item * cc_number
46              
47             =item * cc_exp
48              
49             =item * ccv
50              
51             =back
52              
53             You can easily change this list when parsing a stack trace by passing the
54             argument I when calling C.
55              
56             =cut
57              
58             my $DEFAULT_ARGUMENTS_REDACTED =
59             [
60             qw(
61             password
62             passwd
63             cc_number
64             cc_exp
65             ccv
66             )
67             ];
68              
69              
70             =head2 Redacting using regular expressions
71              
72             By default, this module will redact subroutine arguments in the stack traces
73             that match the following patterns:
74              
75             =over 4
76              
77             =item * Credit card numbers (VISA, MasterCard, American Express, Diners Club, Discover, JCB)
78              
79             =back
80              
81             =cut
82              
83             my $DEFAULT_REGEXP_REDACTED =
84             [
85             # Credit card patterns.
86             qr/
87             \b
88             (?:
89             # VISA starts with 4 and has 13 or 16 digits
90             4 [0-9]{12} (?:[0-9]{3})?
91             |
92             # MasterCard start with
93             # 51 through 55 and has 16 digits
94             5[1-5] [0-9]{14}
95             |
96             # American Express starts
97             # with 34 or 37 and has 15 digits
98             3[47] [0-9]{13}
99             |
100             # Diners Club starts with
101             # 300 through 305
102             # or 36 or 38 and has 14 digits in either case
103             3 (?:0[0-5]|[68][0-9]) [0-9]{11}
104             |
105             # Discover starts with
106             # 6011 or 65 and has 16 digits
107             6 (?:011|5[0-9]{2}) [0-9]{12}
108             |
109             # JCB starts with
110             # 2131 or 1800 and has 15 digits
111             # or starts with 35 and has 16 digits
112             (?:2131|1800|35[0-9]{3}) [0-9]{11}
113             )
114             \b
115             /x,
116             ];
117              
118              
119             =head1 SYNOPSIS
120              
121             # Retrieve a Carp stack trace with longmess(). This is tedious, but you will
122             # normally be using this module in a context where the stacktrace is already
123             # generated for you and you want to parse it, so you won't have to go through
124             # this step.
125             sub test3 { return Carp::longmess("Test"); }
126             sub test2 { return test3(); }
127             sub test1 { return test2(); }
128             my $stack_trace = test1();
129            
130             # Parse the Carp stack trace.
131             # The call takes an optional list of arguments to redact, if you don't want
132             # to use the default.
133             use Carp::Parse::Redact;
134             my $redacted_parsed_stack_trace = Carp::Parse::Redact::parse_stack_trace(
135             $stack_trace,
136             sensitive_argument_names => #optional
137             [
138             'password',
139             'passwd',
140             'cc_number',
141             'cc_exp',
142             'ccv',
143             ],
144             sensitive_regexp_patterns => #optional
145             [
146             qr/^\d{16}$/,
147             ]
148             );
149            
150             use Data::Dump qw( dump );
151             foreach my $caller_information ( @$parsed_stack_trace )
152             {
153             # Print the arguments for each caller.
154             say dump( $caller->get_redacted_arguments_list() );
155             }
156              
157              
158             =head1 FUNCTIONS
159              
160             =head2 parse_stack_trace()
161              
162             Parse a stack trace produced by C into an arrayref of
163             C objects and redact out the sensitive
164             information from each function caller arguments.
165              
166             my $redacted_parsed_stack_trace = Carp::Parse::Redact::parse_stack_trace( $stack_trace );
167            
168             my $redacted_parsed_stack_trace = Carp::Parse::Redact::parse_stack_trace(
169             $stack_trace,
170             sensitive_argument_names => #optional
171             [
172             password
173             passwd
174             cc_number
175             cc_exp
176             ccv
177             ],
178             sensitive_regexp_patterns => #optional
179             [
180             qr/^\d{16}$/,
181             ]
182             );
183              
184             The first argument, a stack trace, is required. Optional parameters:
185              
186             =over 4
187              
188             =item * sensitive_argument_names
189              
190             An arrayref of argument names to redact, when they are found in hashes of
191             arguments in the stack trace. If not set, see the list of defaults used at the
192             top of this documentation.
193              
194             =item * sensitive_regexp_patterns
195              
196             An arrayref of regular expressions. If an argument in the list of subroutine
197             calls in the stack trace matches any of the patterns, it will be redacted.
198             If not set, see the list of defaults used at the top of this documentation.
199              
200             =back
201              
202             =cut
203              
204             sub parse_stack_trace
205             {
206 3     3 1 413 my ( $stack_trace, %args ) = @_;
207            
208             # Verify parameters.
209 3   66     27 my $sensitive_argument_names = delete( $args{'sensitive_argument_names'} ) || $DEFAULT_ARGUMENTS_REDACTED;
210 3 50       21 croak "'sensitive_argument_names' must be an arrayref"
211             if !Data::Validate::Type::is_arrayref( $sensitive_argument_names );
212            
213 3   33     112 my $sensitive_regexp_patterns = delete( $args{'sensitive_regexp_patterns'} ) || $DEFAULT_REGEXP_REDACTED;
214 3 50       14 croak "'sensitive_regexp_patterns' must be an arrayref"
215             if !Data::Validate::Type::is_arrayref( $sensitive_regexp_patterns );
216            
217 3 50       75 croak "The following parameters are not supported: " . Data::Dump::dump( %args )
218             if scalar( keys %args ) != 0;
219            
220             # Make a hash of arguments to redact.
221 11         34 my $arguments_redacted =
222             {
223 3         11 map { $_ => 1 }
224             @$sensitive_argument_names
225             };
226            
227             # Get the parsed stack trace from Carp::Parse.
228 3         22 my $parsed_stack_trace = Carp::Parse::parse_stack_trace( $stack_trace );
229            
230             # Redact sensitive information.
231 3         1523 my $redacted_parsed_stack_trace = [];
232 3 50       8 foreach my $caller_information ( @{ $parsed_stack_trace || [] } )
  3         17  
233             {
234             # Scan for hash keys matching our list of sensitive argument names.
235 12         20 my $redact_next = 0;
236 12         20 my $redacted_arguments_list = [];
237 12 100       16 foreach my $argument ( @{ $caller_information->get_arguments_list() || [] } )
  12         38  
238             {
239 30 100       83 if ( $redact_next )
240             {
241 6         11 push( @$redacted_arguments_list, '[redacted]' );
242 6         9 $redact_next = 0;
243             }
244             else
245             {
246 24         31 push( @$redacted_arguments_list, $argument );
247 24 100 66     120 $redact_next = 1 if defined( $argument ) && $arguments_redacted->{ $argument };
248             }
249             }
250            
251             # Scan all arguments against patterns to redact sensitive information
252             # that wouldn't have been passed in a hash.
253 12         81 foreach my $argument ( @$redacted_arguments_list )
254             {
255 30 50       61 next unless defined( $argument );
256 30 100       63 next if $argument eq '[redacted]';
257            
258 24         26 my $matches_pattern = 0;
259 24         36 foreach my $regexp ( @$DEFAULT_REGEXP_REDACTED )
260             {
261 24 100       133 next unless $argument =~ $regexp;
262 1         2 $matches_pattern = 1;
263 1         2 last;
264             }
265            
266 24 100       60 $argument = '[redacted]'
267             if $matches_pattern;
268             }
269            
270             push(
271 12         42 @$redacted_parsed_stack_trace,
272             Carp::Parse::CallerInformation::Redacted->new(
273             {
274             arguments_string => $caller_information->get_arguments_string(),
275             arguments_list => $caller_information->get_arguments_list(),
276             redacted_arguments_list => $redacted_arguments_list,
277             line => $caller_information->get_line(),
278             },
279             ),
280             );
281             }
282            
283 3         56 return $redacted_parsed_stack_trace;
284             }
285              
286              
287             =head1 AUTHOR
288              
289             Kate Kirby, C<< >>.
290              
291             Guillaume Aubert, C<< >>.
292              
293              
294             =head1 BUGS
295              
296             Please report any bugs or feature requests to C, or through
297             the web interface at L.
298             I will be notified, and then you'll automatically be notified of progress on
299             your bug as I make changes.
300              
301              
302             =head1 SUPPORT
303              
304             You can find documentation for this module with the perldoc command.
305              
306             perldoc Carp::Parse::Redact
307              
308              
309             You can also look for information at:
310              
311             =over 4
312              
313             =item * RT: CPAN's request tracker
314              
315             L
316              
317             =item * AnnoCPAN: Annotated CPAN documentation
318              
319             L
320              
321             =item * CPAN Ratings
322              
323             L
324              
325             =item * Search CPAN
326              
327             L
328              
329             =back
330              
331              
332             =head1 ACKNOWLEDGEMENTS
333              
334             Thanks to ThinkGeek (L) and its corporate overlords
335             at Geeknet (L), for footing the bill while we eat pizza
336             and write code for them!
337              
338              
339             =head1 COPYRIGHT & LICENSE
340              
341             Copyright 2012 Kate Kirby & Guillaume Aubert.
342              
343             This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation.
344              
345             This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
346              
347             You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/
348              
349             =cut
350              
351             1;