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 )