Developer-Dashboard
view release on metacpan or search on metacpan
t/47-owasp-gate.t view on Meta::CPAN
like( $owasp_sow_doc, qr/OWASP-aligned|OWASP-gated/i, 'OWASP SOW doc keeps the safe public-claim wording' );
like( $owasp_sow_doc, qr/Do not claim .*OWASP compliant|blanket .*OWASP compliant/i, 'OWASP SOW doc blocks an unqualified compliance claim before closure' );
like( $owasp_sow_doc, qr/Current Status|Status As Of/i, 'OWASP SOW doc records a concrete current-status section' );
like( $owasp_sow_doc, qr/Completion Criteria|Closure Criteria/i, 'OWASP SOW doc records closure criteria for the stronger claim' );
for my $pattern (
qr/LWP::Simple\|HTTP::Tiny\|JSON::PP\|capture_merged/,
qr/Transient token URLs are disabled\|_transient_url_tokens_allowed\|verify_user\|login_response\|_session_cookie/,
qr/DBI->connect\|\\\\\$dbh->prepare\\\\\(\\\\\$sql\\\\\)\|table_info\|column_info/,
qr/_sanitize_redirect_target\|Location\|redirect/,
qr/dashboards\/public/,
qr/dashboards\/ajax/,
qr/skills\/\.\+\/dashboards/,
qr/system\\\\\(\|exec\\\\\(\|open STDOUT\|open STDERR\|timeout_ms\|alarm\\\\\(/,
qr/prove -lv t\/08-web-update-coverage\.t t\/web_app_static_files\.t t\/17-web-server-ssl\.t/,
)
{
like( $security_checks, $pattern, "SECURITY_CHECKS.md carries required OWASP audit evidence: $pattern" );
}
like( $readme, qr/OWASP ASVS 5\.0|ASVS 5\.0/, 'README exposes the full OWASP verification gate' );
like( $readme, qr/OWASP Top 10/i, 'README exposes the Top 10 cross-check requirement' );
like( $main_pod, qr/OWASP ASVS 5\.0|ASVS 5\.0/, 'main POD exposes the full OWASP verification gate' );
like( $main_pod, qr/OWASP Top 10/i, 'main POD exposes the Top 10 cross-check requirement' );
like( $web_app, qr/sub _sanitize_redirect_target\b/, 'web app still owns redirect target sanitization' );
like( $web_app, qr/HttpOnly; SameSite=Strict/, 'web app keeps strict live session cookie attributes' );
like( $web_server, qr/'X-Frame-Options'\s*=>\s*'DENY'/, 'web server keeps frame denial header' );
like( $web_server, qr/'X-Content-Type-Options'\s*=>\s*'nosniff'/, 'web server keeps nosniff header' );
like( $web_server, qr/'Referrer-Policy'\s*=>\s*'no-referrer'/, 'web server keeps no-referrer policy' );
like( $web_server, qr/'Content-Security-Policy'\s*=>/, 'web server still sets a CSP header' );
like( $auth_module, qr/sub verify_user\b/, 'auth module still owns helper credential verification' );
like( $web_security_t, qr/redirect_to/, 'focused web regression test still covers post-login redirect behavior' );
like( $web_security_t, qr/Content-Security-Policy/, 'focused web regression test still covers security headers' );
like( $static_files_t, qr/\.\.\/\.\.\/\.\.\/etc\/passwd/, 'static-file regression test still covers traversal blocking' );
like( $ssl_t, qr/307/, 'SSL regression test still covers redirect status handling' );
like( $ssl_t, qr/https:\/\//, 'SSL regression test still covers HTTPS redirect targets' );
my $source_code = _code_only_repo_sources();
for my $forbidden (
qr/\bLWP::Simple\b/,
qr/\bHTTP::Tiny\b/,
qr/\bJSON::PP\b/,
qr/\bcapture_merged\b/,
qr/\bDBI->connect\b/,
qr/\$dbh->prepare\(\$sql\)/,
qr/\btable_info\b/,
qr/\bcolumn_info\b/,
)
{
unlike( $source_code, $forbidden, "repo code body avoids forbidden security pattern: $forbidden" );
}
done_testing();
sub _slurp_repo {
my ($relative_path) = @_;
my $path = File::Spec->catfile( $ROOT, split m{/}, $relative_path );
open my $fh, '<:raw', $path or die "Unable to read $path: $!";
local $/;
my $text = <$fh>;
close $fh or die "Unable to close $path: $!";
return $text;
}
sub _code_only_repo_sources {
my @paths;
find(
{
no_chdir => 1,
wanted => sub {
return if !-f $_;
return if $_ !~ /\.(?:pm|pl|t)\z/ && $_ !~ m{/dashboard\z};
return if $_ =~ m{/OLD_CODE/};
push @paths, $File::Find::name;
},
},
File::Spec->catdir( $ROOT, 'lib' ),
File::Spec->catdir( $ROOT, 'bin' ),
);
my @chunks;
for my $path (@paths) {
next if $path eq File::Spec->catfile( $ROOT, 'lib', 'Developer', 'Dashboard.pm' );
my $text = _slurp_absolute($path);
$text =~ s/\n__END__\n.*\z//s;
push @chunks, $text;
}
return join "\n", @chunks;
}
sub _slurp_absolute {
my ($path) = @_;
open my $fh, '<:raw', $path or die "Unable to read $path: $!";
local $/;
my $text = <$fh>;
close $fh or die "Unable to close $path: $!";
return $text;
}
__END__
=pod
=head1 NAME
t/47-owasp-gate.t - enforce full-repository OWASP gate coverage and security invariants
=head1 PURPOSE
This test is the executable regression contract for the repository-wide OWASP
gate. It keeps the ASVS scope, Top 10 cross-check, audit-command evidence, and
core repo-side security invariants from silently drifting apart.
=head1 WHY IT EXISTS
( run in 1.196 second using v1.01-cache-2.11-cpan-71847e10f99 )