Files
rspade_system/app/RSpade/CodeQuality/Rules/PHP/MassAssignment_CodeQualityRule.php
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 02:08:33 +00:00

200 lines
7.1 KiB
PHP
Executable File

<?php
namespace App\RSpade\CodeQuality\Rules\PHP;
use PhpParser\Node;
use PhpParser\NodeFinder;
use PhpParser\ParserFactory;
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
use App\RSpade\Core\Manifest\Manifest;
class MassAssignment_CodeQualityRule extends CodeQualityRule_Abstract
{
public function get_id(): string
{
return 'PHP-MASS-01';
}
public function get_name(): string
{
return 'Mass Assignment Property Check';
}
public function get_description(): string
{
return 'Prohibits use of $fillable and enforces $guarded = ["*"] in models';
}
public function get_file_patterns(): array
{
return ['*.php'];
}
public function get_default_severity(): string
{
return 'critical';
}
/**
* Whether this rule is called during manifest scan
*
* EXCEPTION: This rule has been explicitly approved to run at manifest-time because
* mass assignment protection is critical for security and must be enforced immediately.
*/
public function is_called_during_manifest_scan(): bool
{
return true; // Explicitly approved for manifest-time checking
}
/**
* Process file during manifest update to extract mass assignment metadata
*/
public function on_manifest_file_update(string $file_path, string $contents, array $metadata = []): ?array
{
// Skip Model_Abstract itself
if (str_contains($file_path, 'Model_Abstract.php')) {
return null;
}
// Skip Rsx_Model_Abstract.php - it needs empty $fillable to satisfy Laravel requirements
if (str_contains($file_path, 'Rsx_Model_Abstract.php')) {
return null;
}
// Skip if not a model file
if (!isset($metadata['class'])) {
return null;
}
// Check if this is a model using proper inheritance check
if (!Manifest::php_is_subclass_of($metadata['class'], 'Rsx_Model_Abstract')) {
return null; // Not a model class
}
// Parse the file to check for mass assignment properties
$parser = (new ParserFactory())->createForNewestSupportedVersion();
try {
$ast = $parser->parse($contents);
if (!$ast) {
return null;
}
} catch (\Exception $e) {
return null;
}
$nodeFinder = new NodeFinder();
$properties = $nodeFinder->findInstanceOf($ast, Node\Stmt\Property::class);
$violations = [];
foreach ($properties as $property) {
foreach ($property->props as $prop) {
$prop_name = $prop->name->toString();
// Check for $fillable property
if ($prop_name === 'fillable') {
$violations[] = [
'type' => 'fillable',
'line' => $property->getLine(),
'property' => $prop_name
];
}
// Check for $guarded property (unless it's set to ['*'])
if ($prop_name === 'guarded') {
$is_star_guarded = false;
if ($prop->default instanceof Node\Expr\Array_) {
if (count($prop->default->items) === 1) {
$item = $prop->default->items[0];
if ($item && $item->value instanceof Node\Scalar\String_ && $item->value->value === '*') {
$is_star_guarded = true;
}
}
}
if (!$is_star_guarded) {
$violations[] = [
'type' => 'guarded',
'line' => $property->getLine(),
'property' => $prop_name
];
}
}
}
}
if (!empty($violations)) {
return ['mass_assignment_violations' => $violations];
}
return null;
}
public function check(string $file_path, string $contents, array $metadata = []): void
{
// Skip Model_Abstract itself
if (str_contains($file_path, 'Model_Abstract.php')) {
return;
}
// Skip Rsx_Model_Abstract.php - it needs empty $fillable to satisfy Laravel requirements
if (str_contains($file_path, 'Rsx_Model_Abstract.php')) {
return;
}
// Skip if not a model file
if (!isset($metadata['class'])) {
return;
}
// Check if this is a model using proper inheritance check
if (!Manifest::php_is_subclass_of($metadata['class'], 'Rsx_Model_Abstract')) {
return; // Not a model class
}
// Check for mass assignment violations in code quality metadata
if (isset($metadata['code_quality_metadata']['PHP-MASS-01']['mass_assignment_violations'])) {
$violations = $metadata['code_quality_metadata']['PHP-MASS-01']['mass_assignment_violations'];
// Throw on first violation
foreach ($violations as $violation) {
$type = $violation['type'];
$line = $violation['line'];
$class_name = $metadata['class'];
if ($type === 'fillable') {
$error_message = "Code Quality Violation (PHP-MASS-01) - Prohibited Mass Assignment Property\n\n";
$error_message .= "Model class '{$class_name}' has a \$fillable property\n\n";
$error_message .= "File: {$file_path}\n";
$error_message .= "Line: {$line}\n\n";
$error_message .= "CRITICAL: Mass assignment is prohibited in RSX.\n\n";
$error_message .= "Resolution:\n";
$error_message .= "Remove the \$fillable property and assign fields explicitly:\n";
$error_message .= "\$model->field = \$value;\n";
$error_message .= "\$model->save();\n\n";
$error_message .= "This ensures data integrity and security by requiring explicit field assignment.";
} else {
$error_message = "Code Quality Violation (PHP-MASS-01) - Incorrect Guard Configuration\n\n";
$error_message .= "Model class '{$class_name}' has a customized \$guarded property\n\n";
$error_message .= "File: {$file_path}\n";
$error_message .= "Line: {$line}\n\n";
$error_message .= "CRITICAL: The \$guarded property should not be customized.\n\n";
$error_message .= "Resolution:\n";
$error_message .= "Remove the \$guarded property entirely.\n";
$error_message .= "Model_Abstract handles mass assignment protection automatically.";
}
throw new \App\RSpade\CodeQuality\RuntimeChecks\YoureDoingItWrongException(
$error_message,
0,
null,
$file_path,
$line
);
}
}
}
}