Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Query Security Bundle Laravel Package

business-decision/query-security-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation Add the bundle via Composer:

    composer require betd/query-security-bundle
    

    Register the bundle in config/bundles.php (Symfony):

    return [
        // ...
        Betd\QuerySecurityBundle\QuerySecurityBundle::class => ['all' => true],
    ];
    
  2. Basic Configuration Configure allowed fields, operators, and filters in config/packages/betd_query_security.yaml:

    betd_query_security:
        allowed_fields: ['id', 'name', 'email']  # Whitelist fields
        allowed_operators: ['=', '!=', '>', '<'] # Whitelist operators
        default_filter: 'id'                     # Fallback filter
    
  3. First Use Case: Secure API Filtering In a Symfony controller, use the QuerySecurity service to sanitize input:

    use Betd\QuerySecurityBundle\Security\QuerySecurity;
    
    class ProductController extends AbstractController
    {
        public function listProducts(Request $request, QuerySecurity $querySecurity)
        {
            $filters = $request->query->all();
            $sanitizedFilters = $querySecurity->sanitize($filters);
    
            // Use sanitizedFilters in Doctrine QueryBuilder
            return $this->render('product/list.html.twig', [
                'products' => $this->getDoctrine()
                    ->getRepository(Product::class)
                    ->findBy($sanitizedFilters),
            ]);
        }
    }
    

Implementation Patterns

1. QueryBuilder Integration

Use the QuerySecurity service to dynamically build secure queries:

use Betd\QuerySecurityBundle\Security\QuerySecurity;
use Doctrine\ORM\QueryBuilder;

class ProductController
{
    public function listProducts(Request $request, QuerySecurity $querySecurity, EntityManagerInterface $em)
    {
        $qb = $em->createQueryBuilder();
        $qb->select('p')
           ->from(Product::class, 'p');

        $filters = $request->query->all();
        $sanitized = $querySecurity->sanitize($filters);

        foreach ($sanitized as $field => $value) {
            $qb->andWhere("p.$field = :$field")
               ->setParameter($field, $value);
        }

        return $this->render('product/list.html.twig', [
            'products' => $qb->getQuery()->getResult(),
        ]);
    }
}

2. API Platform Integration

For API Platform projects, extend AbstractItem/AbstractCollection to auto-sanitize:

use Betd\QuerySecurityBundle\Security\QuerySecurity;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;

class SecureFilter extends AbstractFilter
{
    public function __construct(private QuerySecurity $querySecurity) {}

    protected function filterProperty(string $property, $value, QueryBuilder $qb, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = [])
    {
        $sanitizedValue = $this->querySecurity->sanitize([$property => $value]);
        return parent::filterProperty($property, $sanitizedValue[$property], $qb, $queryNameGenerator, $resourceClass, $operationName, $context);
    }
}

3. Dynamic Field Whitelisting

Override allowed fields per entity using a service:

# config/services.yaml
services:
    Betd\QuerySecurityBundle\Security\QuerySecurity:
        arguments:
            $allowedFields: '%betd_query_security.allowed_fields%'
            $customRules: !tagged 'query_security.rule'

Define custom rules:

// src/Service/CustomFieldRule.php
namespace App\Service;

use Betd\QuerySecurityBundle\Security\Rule\FieldRuleInterface;

class CustomFieldRule implements FieldRuleInterface
{
    public function isAllowed(string $field): bool
    {
        return in_array($field, ['id', 'slug', 'created_at']);
    }
}

Register the rule:

# config/services.yaml
services:
    App\Service\CustomFieldRule:
        tags:
            - { name: 'query_security.rule' }

Gotchas and Tips

Pitfalls

  1. Overly Restrictive Whitelists

    • If allowed_fields is empty, all queries will fail. Always define a default.
    • Fix: Set default_filter: 'id' in config to avoid silent failures.
  2. SQL Injection via Operator Abuse

    • Even with whitelisted operators, malicious input like '; DROP TABLE users-- can slip through if not properly escaped.
    • Fix: Combine with Doctrine’s native parameter binding (as shown in examples).
  3. Case Sensitivity in Field Names

    • Doctrine treats User.name and user.name as different fields. Normalize case in config:
      allowed_fields: ['user.name', 'user.email']  # Explicit casing
      

Debugging Tips

  1. Enable Verbose Logging Add to config/packages/monolog.yaml:

    handlers:
        query_security:
            type: stream
            path: "%kernel.logs_dir%/query_security.log"
            level: debug
            channels: ["query_security"]
    

    Then log sanitization steps in QuerySecurity service.

  2. Test Edge Cases

    • Test with:
      • Empty input arrays.
      • Nested field access (e.g., user.address.city).
      • Special characters (', ", \).

Extension Points

  1. Custom Validators Extend Betd\QuerySecurityBundle\Security\Validator\FieldValidatorInterface to add logic (e.g., regex validation):

    class EmailFieldValidator implements FieldValidatorInterface
    {
        public function validate(string $field, $value): bool
        {
            return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
        }
    }
    

    Register it in services.yaml under query_security.validator.

  2. Dynamic Operator Whitelisting Override allowed_operators per request using a middleware:

    // src/Middleware/DynamicQuerySecurity.php
    public function handle(Request $request, QuerySecurity $security)
    {
        $security->setAllowedOperators($request->headers->get('x-allowed-ops', ['=']));
        return $next($request);
    }
    
  3. Performance Optimization

    • Cache sanitized rules if fields/operators rarely change:
      $querySecurity->setCache(new \Symfony\Component\Cache\Adapter\FilesystemAdapter());
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
comsave/common
alecsammon/php-raml-parser
chrome-php/wrench
lendable/composer-license-checker
typhoon/reflection
mesilov/moneyphp-percentage
mike42/gfx-php
bookdown/themes
aura/view
aura/html
aura/cli
povils/phpmnd
nayjest/manipulator
omnipay/tests
psr-mock/http-message-implementation
psr-mock/http-factory-implementation
psr-mock/http-client-implementation
voku/email-check
voku/urlify
rtheunissen/guzzle-log-middleware