Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: "CodeQL"

on: [pull_request]

jobs:
lint:
name: CodeQL
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Run CodeQL
env:
COMPOSER_ROOT_VERSION: dev-main
run: |
docker run --rm -v $PWD:/app -w /app -e COMPOSER_ROOT_VERSION=$COMPOSER_ROOT_VERSION composer:2.8 sh -c \
"git config --global --add safe.directory /app && composer install --profile --ignore-platform-reqs && composer check"

20 changes: 20 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: "Linter"

on: [pull_request]

jobs:
lint:
name: Linter
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Run Linter
env:
COMPOSER_ROOT_VERSION: dev-main
run: |
docker run --rm -v $PWD:/app -w /app -e COMPOSER_ROOT_VERSION=$COMPOSER_ROOT_VERSION composer:2.8 sh -c \
"git config --global --add safe.directory /app && composer install --profile --ignore-platform-reqs && composer lint"

33 changes: 33 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: "Tests"

on: [pull_request]

jobs:
tests:
name: PHP ${{ matrix.php }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.2', '8.3', '8.4', '8.5']

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
extensions: mbstring

- name: Validate composer.json and composer.lock
run: composer validate --strict

- name: Install dependencies
run: composer install --prefer-dist --no-interaction --no-progress

- name: Run unit tests
run: composer test

4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
vendor/
/.phpunit.cache
var/

113 changes: 111 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,111 @@
# waf
Lite & fast micro PHP WAF rules management that is easy to learn.
# Utopia WAF

Lite & fast micro PHP Web Application Firewall (WAF) rules management library that is **easy to use** and fits naturally inside the [Utopia](https://github.com/utopia-php) ecosystem.

The library ships with:

- A `Condition` builder that mirrors the API of [`Utopia\Database\Query`](https://github.com/utopia-php/database/blob/main/src/Database/Query.php), including JSON parsing helpers and logical operators.
- Action specific rule classes (`Bypass`, `Deny`, `Challenge`, `RateLimit`, `Redirect`).
- A dependency-free `Firewall` orchestrator that evaluates rules against any set of request attributes.
Comment thread
eldadfux marked this conversation as resolved.

## Installation

```bash
composer require utopia-php/waf
```

## Usage

```php
<?php

require_once __DIR__ . '/vendor/autoload.php';

use Utopia\WAF\Condition;
use Utopia\WAF\Firewall;
use Utopia\WAF\Rules\Bypass;
use Utopia\WAF\Rules\Deny;
use Utopia\WAF\Rules\Challenge;
use Utopia\WAF\Rules\RateLimit;
use Utopia\WAF\Rules\Redirect;

$firewall = new Firewall();
$firewall->setAttribute('requestIP', '127.0.0.1');
$firewall->setAttribute('requestMethod', 'GET');
$firewall->setAttribute('requestPath', '/index');
$firewall->setAttribute('headers', [
'X-Country' => 'US',
]);

$firewall->addRule(new Deny([
Condition::equal('ip', ['127.0.0.1']),
Condition::notEqual('path', '/status'),
]));

$firewall->addRule(new Bypass([
Condition::equal('country', ['US']),
Condition::equal('method', ['GET']),
]));
Comment thread
eldadfux marked this conversation as resolved.

$firewall->addRule(new Challenge([
Condition::startsWith('path', '/admin'),
], Challenge::TYPE_CAPTCHA));

$firewall->addRule(new RateLimit([
Condition::equal('method', ['POST']),
], limit: 100, interval: 3600));

$firewall->addRule(new Redirect([
Condition::startsWith('path', '/legacy'),
], location: '/new-home', statusCode: 301));

var_dump($firewall->verify()); // bool(true|false)

if ($rule = $firewall->getLastMatchedRule()) {
echo 'Matched action: ' . $rule->getAction();
}
```

### Building Conditions

Conditions can be created fluently or by parsing JSON definitions:

```php
$condition = Condition::and([
Condition::equal('ip', ['10.0.0.1']),
Condition::notEqual('path', '/health'),
]);

$json = $condition->toString();
$parsed = Condition::parse($json);
```

Available operators mirror the database query builder: `equal`, `notEqual`, `lessThan`, `greaterThan`, `contains`, `between`, `startsWith`, `endsWith`, `isNull`, `and`, `or`, and more.

### Rate Limiting

`RateLimit` rules only store the metadata required for external throttling (`limit` + `interval`). Once a rate limit rule matches, the firewall returns `true` and exposes the matched rule via `getLastMatchedRule()` so you can call any third-party rate limiter with the provided metadata.

```php
$firewall->addRule(new RateLimit([
Condition::equal('ip', ['203.0.113.12']),
], limit: 500, interval: 60));

if ($firewall->verify()) {
$matched = $firewall->getLastMatchedRule();
if ($matched instanceof RateLimit) {
// Invoke your preferred rate limiter here using $matched->getLimit() and $matched->getInterval()
}
}
```

### Testing Locally

```bash
composer install
composer test
```

## License

MIT
33 changes: 33 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "utopia-php/waf",
"description": "Lite and extensible Web Application Firewall rules management library for the Utopia PHP ecosystem.",
"type": "library",
"license": "MIT",
"require": {
"php": ">=8.2"
},
"require-dev": {
"laravel/pint": "^1.18",
"phpstan/phpstan": "^1.11",
"phpunit/phpunit": "^11.0"
},
"autoload": {
"psr-4": {
"Utopia\\WAF\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Utopia\\WAF\\Tests\\": "tests/"
}
},
"scripts": {
"check": "vendor/bin/phpstan analyse -c phpstan.neon --memory-limit 512M",
"lint": "vendor/bin/pint --test",
"format": "vendor/bin/pint",
"test": "vendor/bin/phpunit --configuration phpunit.xml"
},
"minimum-stability": "stable",
"prefer-stable": true
}

Loading