Claude-Agent

 view release on metacpan or  search on metacpan

t/12-sdk-validation.t  view on Meta::CPAN

        '/home/user',
        '/Users/username',
        '/root',
        '/home/user/workspace',
    );

    my @unsafe_paths = (
        '/home/user/../etc',
        '/home/user/../../root',
        '/..',
        '/home/..',
    );

    for my $path (@safe_paths) {
        my $is_safe = ($path !~ m{/\.\./} && $path !~ m{/\.\.\z});
        ok($is_safe, "safe path: '$path'");
    }

    for my $path (@unsafe_paths) {
        my $is_unsafe = ($path =~ m{/\.\./} || $path =~ m{/\.\.\z});
        ok($is_unsafe, "unsafe path detected: '$path'");
    }
};

# =============================================================================
# Control character sanitization tests
# =============================================================================

subtest 'Control character sanitization' => sub {
    # From Query.pm: s/[[:cntrl:]]/ /g

    my $sanitize = sub {
        my ($str) = @_;
        $str =~ s/[[:cntrl:]]/ /g;
        return $str;
    };

    is($sanitize->('normal text'), 'normal text', 'normal text unchanged');
    is($sanitize->("line\nbreak"), 'line break', 'newline replaced');
    is($sanitize->("with\ttab"), 'with tab', 'tab replaced');
    is($sanitize->("null\x00byte"), 'null byte', 'null replaced');
    is($sanitize->("carriage\rreturn"), 'carriage return', 'CR replaced');
    is($sanitize->("bell\x07sound"), 'bell sound', 'bell replaced');
    is($sanitize->("escape\x1bseq"), 'escape seq', 'escape replaced');

    # Verify normal printable chars preserved
    my $printable = join('', map { chr } 32..126);
    is($sanitize->($printable), $printable, 'printable ASCII preserved');
};

# =============================================================================
# JSON::Lines safety tests
# =============================================================================

subtest 'JSON structure validation' => sub {
    use JSON::Lines;

    my $jsonl = JSON::Lines->new;

    # Valid JSON line
    my @decoded = $jsonl->decode('{"type":"test"}');
    is(scalar @decoded, 1, 'single object decoded');
    is($decoded[0]->{type}, 'test', 'type key extracted');

    # Array in JSON Lines
    @decoded = $jsonl->decode('[1,2,3]');
    is(scalar @decoded, 1, 'array decoded as single item');
    is_deeply($decoded[0], [1,2,3], 'array content correct');

    # Multiple lines (though JSON::Lines takes one at a time)
    my $line1 = '{"a":1}';
    my $line2 = '{"b":2}';
    @decoded = $jsonl->decode($line1);
    is($decoded[0]->{a}, 1, 'first line decoded');
    @decoded = $jsonl->decode($line2);
    is($decoded[0]->{b}, 2, 'second line decoded');
};

# =============================================================================
# PERL5LIB filtering tests (from SDKServer)
# =============================================================================

subtest 'PERL5LIB path filtering' => sub {
    use Cwd qw(abs_path);
    use File::Spec;

    # Simulate the filtering logic from SDKServer
    my $filter_path = sub {
        my ($path) = @_;
        return 0 unless defined $path;
        return 0 unless $path =~ m{^/};
        return 0 unless -d $path;  # Must exist
        my $real = abs_path($path);
        return 0 unless $real;
        return 0 unless $real =~ m{^/};
        return 0 if $real =~ m{/\.\.(/|$)};  # No parent traversal
        return 1;
    };

    # Test paths that exist
    ok($filter_path->('/tmp'), '/tmp accepted');
    ok($filter_path->('/'), '/ accepted');

    # Test relative paths
    ok(!$filter_path->('relative'), 'relative path rejected');
    ok(!$filter_path->('./relative'), './relative rejected');

    # Test undef
    ok(!$filter_path->(undef), 'undef rejected');

    # Test non-existent paths (should fail -d check)
    ok(!$filter_path->('/definitely/not/existing/path/12345'), 'non-existent rejected');
};

done_testing();



( run in 0.524 second using v1.01-cache-2.11-cpan-9581c071862 )