Claude-Agent

 view release on metacpan or  search on metacpan

lib/Claude/Agent/MCP/SDKRunner.pm  view on Meta::CPAN


    # Validate version if provided - semver-like format
    die "Invalid version format\n"
        if defined($version) && length($version) && $version !~ /^[a-zA-Z0-9._-]{1,50}$/;

    # Limit tools_json size to prevent memory exhaustion (1MB limit)
    die "tools_json too large (max 1MB)\n" if length($tools_json) > 1_000_000;

    my ($tools) = $state->{jsonl}->decode($tools_json);

    # Validate decoded structure: must be an array of tool definitions
    die "Invalid tools_json: expected array\n" unless ref $tools eq 'ARRAY';
    for my $tool (@$tools) {
        die "Invalid tool definition: expected hash with 'name' key\n"
            unless ref $tool eq 'HASH' && defined $tool->{name};
    }

    # Build tool lookup
    my %tool_by_name = map { $_->{name} => $_ } @$tools;

    # Validate socket ownership before connecting (defense-in-depth)

lib/Claude/Agent/Query.pm  view on Meta::CPAN

    # For ref prompts (streaming input), caller will send messages via send_user_message
    return;
}

sub _handle_line {
    my ($self, $line) = @_;

    return unless defined $line && length $line;

    # Use JSON::Lines decode method for single line
    my @decoded = $self->_jsonl->decode($line);
    if ($log->is_trace) {
        $log->trace(sprintf("Raw line length: %d", length($line)));
        $log->trace(sprintf("Raw line: %s", $line));
        $log->trace(sprintf("Decoded %d objects", scalar(@decoded)));
        $log->trace(sprintf("JSON::Lines buffer remaining: %d chars", length($self->_jsonl->remaining)));
        if ($self->_jsonl->remaining) {
            $log->trace(sprintf("Buffer content (first 200): %s", substr($self->_jsonl->remaining, 0, 200)));
        }
    }

    # Guard against buffer overflow from accumulated malformed data
    # Configurable threshold via environment variable, default 1MB, max 10MB
    # Note: Typical JSON messages from Claude CLI are 1-50KB; tool results (especially
    # large file reads or grep outputs) may be 100KB-1MB. The minimum of 100KB ensures

lib/Claude/Agent/Query.pm  view on Meta::CPAN

        $self->_jsonl(JSON::Lines->new(
            utf8     => 1,
            error_cb => sub {
                my ($action, $error, $data) = @_;
                $log->trace(sprintf("JSON::Lines %s error: %s", $action, $error));
                return;
            },
        ));
    }

    return unless @decoded;

    for my $data (@decoded) {
        if ($log->is_trace) {
            $log->trace(sprintf("Decoded item ref type: %s", ref($data) // "not a ref"));
            if (ref $data eq 'HASH') {
                $log->trace(sprintf("Hash keys: %s", join(", ", keys %$data)));
            }
        }
        next unless defined $data && ref $data eq 'HASH';
        $log->trace(sprintf("Message type in data: %s", $data->{type} // "undef"))
            if $log->is_trace;
        next unless exists $data->{type};  # Skip malformed/partial JSON data

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

# =============================================================================
# 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;



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