Security

Mass Assignment Protection

Mass assignment is one of the most common security vulnerabilities in ORM systems. WPZylos Model provides two mechanisms to protect against it.

Always Define $fillable or $guarded

// Good: Good: Explicit whitelist
class User extends Model
{
    protected array $fillable = ['name', 'email', 'bio'];
}

// Good: Good: Explicit blacklist
class User extends Model
{
    protected array $guarded = ['id', 'is_admin', 'role'];
}

// Bad: Dangerous: No protection
class User extends Model
{
    // $fillable and $guarded are both empty!
}

Guard Everything by Default

If you want maximum security:

protected array $guarded = ['*'];

Use forceFill() only in trusted contexts:

// Only in admin controllers or internal logic
$user->forceFill(['is_admin' => true]);

Hidden Attributes

Prevent sensitive data from leaking in API responses:

class User extends Model
{
    protected array $hidden = [
        'password',
        'api_key',
        'secret_token',
        'remember_token',
    ];
}

// $user->toArray() and $user->toJson() will not include hidden attributes
echo json_encode($user); // Safe — no password in output

SQL Injection

The Model layer delegates all SQL generation to the QueryBuilder from wpzylos-database, which uses WordPress's $wpdb->prepare() for parameterized queries. This provides SQL injection protection by default.

However, be careful with:

  • Never interpolate user input directly into raw SQL
  • Always use the query builder methods (where(), insert(), update())
  • Validate and sanitize user input before passing to models
// Good: Safe: uses parameterized queries
$user = User::where('email', $userInput)->first();

// Good: Safe: mass assignment is filtered
$user = User::create($request);

// Bad: Dangerous: never do this
$wpdb->query("SELECT * FROM users WHERE email = '{$userInput}'");

Input Validation

Always validate data before passing to models:

// Validate before creating
$name  = sanitize_text_field($_POST['name'] ?? '');
$email = sanitize_email($_POST['email'] ?? '');

if (empty($name) || !is_email($email)) {
    wp_send_json_error('Invalid input');
}

$user = User::create([
    'name'  => $name,
    'email' => $email,
]);

Mutators for Data Sanitization

Use mutators to automatically sanitize data:

class User extends Model
{
    public function setEmailAttribute($value): void
    {
        $this->attributes['email'] = sanitize_email(strtolower(trim($value)));
    }

    public function setNameAttribute($value): void
    {
        $this->attributes['name'] = sanitize_text_field($value);
    }
}

Soft Deletes and Data Retention

When using soft deletes, remember:

  • Soft-deleted records still exist in the database
  • Ensure compliance with data retention policies (GDPR, etc.)
  • Use forceDelete() when permanent removal is required
  • Consider scheduled cleanup of old soft-deleted records

Event-Based Security

Use model events for access control:

class Post extends Model
{
    protected static function boot(): void
    {
        static::deleting(function (Post $post) {
            $currentUserId = get_current_user_id();
            if ($post->author_id !== $currentUserId && !current_user_can('delete_others_posts')) {
                return false; // Prevent unauthorized deletion
            }
        });
    }
}

Reporting Vulnerabilities

If you discover a security vulnerability, please report it responsibly:

Email: [email protected]

See security.md for full details.