Skip to content
Draft
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
12 changes: 9 additions & 3 deletions .github/workflows/qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,17 @@ jobs:
id: psalm
run: |
set +e
vendor/bin/psalm --output-format=json --show-info=false > psalm.json 2>&1
# Don't redirect stderr to stdout to avoid corrupting JSON output
vendor/bin/psalm --output-format=json --show-info=false > psalm.json
EXIT_CODE=$?

# Generate SARIF for GitHub Security
vendor/bin/psalm --output-format=sarif --show-info=false > psalm.sarif 2>&1 || true
vendor/bin/psalm --output-format=sarif --show-info=false > psalm.sarif || true

# Fix invalid SARIF generated by Psalm (locations with 0)
if [ -f psalm.sarif ]; then
jq 'walk(if type == "object" and .region? then .region |= with_entries(if (.key | test("Line|Column")) and .value < 1 then .value = 1 else . end) else . end)' psalm.sarif > psalm.sarif.tmp && mv psalm.sarif.tmp psalm.sarif
fi

ERRORS=$(jq 'length' psalm.json 2>/dev/null || echo "0")
echo "errors=${ERRORS}" >> $GITHUB_OUTPUT
Expand All @@ -200,7 +206,7 @@ jobs:

- name: Upload Psalm SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: psalm.sarif
category: psalm
Expand Down
32 changes: 32 additions & 0 deletions bad_sarif.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"runs": [
{
"results": [
{
"locations": [
{
"physicalLocation": {
"region": {
"startLine": 0,
"startColumn": 0,
"endLine": 0,
"endColumn": 0
}
}
},
{
"physicalLocation": {
"region": {
"startLine": 10,
"startColumn": 5,
"endLine": 10,
"endColumn": 15
}
}
}
]
}
]
}
]
}
Empty file added psalm.json
Empty file.
3 changes: 3 additions & 0 deletions psalm.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Psalm has detected issues in your platform:

Psalm requires a PHP version ">= 8.3.16". You are running 8.3.6.
3 changes: 3 additions & 0 deletions psalm.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Psalm has detected issues in your platform:

Psalm requires a PHP version ">= 8.3.16". You are running 8.3.6.
Empty file added psalm.stdout
Empty file.
Empty file added psalm_clean.json
Empty file.
Empty file added psalm_verify.json
Empty file.
Empty file added psalm_verify_null.json
Empty file.
38 changes: 33 additions & 5 deletions src/Core/Input/Sanitiser.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ class Sanitiser
*/
protected array $schema = [];

/**
* Cache of pre-merged effective schemas for performance.
*
* @var array<string, array>
*/
protected array $preparedSchema = [];

/**
* Optional logger for audit logging.
*/
Expand Down Expand Up @@ -207,6 +214,8 @@ public function __construct(
$this->normalizeUnicode = $normalizeUnicode;
$this->maxLength = $maxLength;
$this->allowedHtmlTags = $allowedHtmlTags;

$this->prepareSchema();
}

/**
Expand All @@ -230,6 +239,7 @@ public function withSchema(array $schema): static
{
$clone = clone $this;
$clone->schema = $schema;
$clone->prepareSchema();

return $clone;
}
Expand Down Expand Up @@ -449,9 +459,31 @@ protected function applyPresetToFields(string $presetName, array $fields): stati
}
}

$clone->prepareSchema();

return $clone;
}

/**
* Prepare effective schemas for all fields.
*/
protected function prepareSchema(): void
{
$globalSchema = $this->schema['*'] ?? [];
$this->preparedSchema = [];

// Pre-calculate for known fields
foreach ($this->schema as $field => $fieldSchema) {
if ($field === '*') {
continue;
}
$this->preparedSchema[$field] = array_merge($globalSchema, $fieldSchema);
}

// Store global schema for fallback
$this->preparedSchema['*'] = $globalSchema;
}

// =========================================================================
// TRANSFORMATION HOOKS
// =========================================================================
Expand Down Expand Up @@ -688,11 +720,7 @@ protected function filterRecursive(array $input, string $path = ''): array
protected function filterString(string $value, string $path, string $fieldName): string
{
$original = $value;
$fieldSchema = $this->schema[$fieldName] ?? [];
$globalSchema = $this->schema['*'] ?? [];

// Merge global schema with field-specific schema (field takes precedence)
$effectiveSchema = array_merge($globalSchema, $fieldSchema);
$effectiveSchema = $this->preparedSchema[$fieldName] ?? $this->preparedSchema['*'];

// Step 0: Apply before hooks
$value = $this->applyBeforeHooks($value, $fieldName);
Expand Down
32 changes: 32 additions & 0 deletions test.sarif
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"runs": [
{
"results": [
{
"locations": [
{
"physicalLocation": {
"region": {
"startLine": 0,
"startColumn": 0,
"endLine": 0,
"endColumn": 0
}
}
},
{
"physicalLocation": {
"region": {
"startLine": 10,
"startColumn": 5,
"endLine": 10,
"endColumn": 15
}
}
}
]
}
]
}
]
}
32 changes: 32 additions & 0 deletions test_clean.sarif
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"runs": [
{
"results": [
{
"locations": [
{
"physicalLocation": {
"region": {
"startLine": 1,
"startColumn": 1,
"endLine": 1,
"endColumn": 1
}
}
},
{
"physicalLocation": {
"region": {
"startLine": 10,
"startColumn": 5,
"endLine": 10,
"endColumn": 15
}
}
}
]
}
]
}
]
}
Loading