Skip to content

Conversation

@koriym
Copy link
Member

@koriym koriym commented Dec 28, 2025

Summary

Add Psalm taint annotations to enable static security analysis for SQL injection detection.

Changes

  • @psalm-taint-escape sql on:
    • PerformSqlInterface::perform() - Interface declaration
    • PerformSql::perform() - Prepared statement execution via Aura.Sql
    • PerformTemplatedSql::perform() - Templated SQL execution
    • SqlQueryInterface methods (getRow, getRowList, exec, getCount, getPages)
    • SqlQuery implementation of all query methods

The escape annotation indicates that values passed to these methods are safely escaped via PDO prepared statements with parameter binding, preventing SQL injection attacks.

Test Plan

  • Run ./vendor/bin/psalm --taint-analysis to verify annotations work
  • Existing tests pass

Summary by Sourcery

Annotate SQL execution and query methods with Psalm taint-escape metadata for SQL injection analysis.

New Features:

  • Add Psalm @psalm-taint-escape sql annotations to SQL execution and query interfaces and implementations to support static taint analysis.

Enhancements:

  • Align SqlQuery and PerformSql/PerformTemplatedSql implementations with their interfaces by documenting taint behavior for SQL-related methods.

Summary by CodeRabbit

  • Chores

    • Improved static analysis annotations to strengthen SQL safety checks.
  • Tests

    • Updated test database schemas (table names, column types/sets) used by the test suite.

✏️ Tip: You can customize this high-level summary in your review settings.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 28, 2025

Reviewer's Guide

Adds Psalm taint analysis annotations to SQL query and execution interfaces/implementations so that calls performing parameterized SQL are treated as escaping the sql taint, enabling static detection of unsafe SQL composition elsewhere.

Sequence diagram for taint-escaped SQL execution via SqlQuery and PerformSql

sequenceDiagram
    actor User
    participant InputSource
    participant SqlQuery
    participant PerformSql as PerformSqlInterface_impl
    participant PDO as ExtendedPdoInterface
    participant PDOStatement

    User->>InputSource: provide_untrusted_parameters()
    InputSource->>SqlQuery: getRow(sqlId, values)
    SqlQuery->>PerformSql: perform(pdo, sqlId, sql, values)
    PerformSql->>PDO: prepare_and_execute(sql, values)
    PDO-->>PerformSql: PDOStatement
    PerformSql-->>SqlQuery: PDOStatement
    SqlQuery-->>InputSource: result_row
Loading

Class diagram for SQL query and execution interfaces with Psalm taint-escape annotations

classDiagram
    class SqlQueryInterface {
        +getRow(string sqlId, array values, FetchInterface fetch) array|object|null
        +getRowList(string sqlId, array values, FetchInterface fetch) array
        +exec(string sqlId, array values, FetchInterface fetch) void
        +getCount(string sqlId, array values) int
        +getPages(string sqlId, array values, int perPage, string queryTemplate, string entity) PagesInterface
        <<psalm_taint_escape_sql>> getRow
        <<psalm_taint_escape_sql>> getRowList
        <<psalm_taint_escape_sql>> exec
        <<psalm_taint_escape_sql>> getCount
        <<psalm_taint_escape_sql>> getPages
    }

    class SqlQuery {
        +exec(string sqlId, array values, FetchInterface fetch) void
        +getRow(string sqlId, array values, FetchInterface fetch) array|object|null
        +getRowList(string sqlId, array values, FetchInterface fetch) array
        +getCount(string sqlId, array values) int
        +getPages(string sqlId, array values, int perPage, string queryTemplate, string entity) PagesInterface
        <<psalm_taint_escape_sql>> exec
        <<psalm_taint_escape_sql>> getRow
        <<psalm_taint_escape_sql>> getRowList
        <<psalm_taint_escape_sql>> getCount
        <<psalm_taint_escape_sql>> getPages
    }

    SqlQueryInterface <|.. SqlQuery

    class PerformSqlInterface {
        +perform(ExtendedPdoInterface pdo, string sqlId, string sql, array values) PDOStatement
        <<psalm_taint_escape_sql>> perform
    }

    class PerformSql {
        +perform(ExtendedPdoInterface pdo, string sqlId, string sql, array values) PDOStatement
        <<psalm_taint_escape_sql>> perform
    }

    class PerformTemplatedSql {
        +perform(ExtendedPdoInterface pdo, string sqlId, string sql, array values) PDOStatement
        <<psalm_taint_escape_sql>> perform
    }

    PerformSqlInterface <|.. PerformSql
    PerformSqlInterface <|.. PerformTemplatedSql

    class ExtendedPdoInterface
    class PDOStatement
    class FetchInterface
    class PagesInterface

    SqlQuery --> PerformSqlInterface
    PerformSqlInterface --> ExtendedPdoInterface
    PerformSqlInterface --> PDOStatement
Loading

File-Level Changes

Change Details Files
Mark SQL query interface methods as escaping sql taint for Psalm taint analysis.
  • Annotate SqlQueryInterface::getRow() with @psalm-taint-escape sql.
  • Annotate SqlQueryInterface::getRowList() with @psalm-taint-escape sql.
  • Annotate SqlQueryInterface::exec() with @psalm-taint-escape sql and expand its docblock.
  • Annotate SqlQueryInterface::getCount() with @psalm-taint-escape sql and expand its docblock.
  • Annotate SqlQueryInterface::getPages() with @psalm-taint-escape sql and expand its docblock.
src/SqlQueryInterface.php
Mark SqlQuery implementation methods as escaping sql taint to match the interface and actual parameterized behavior.
  • Add @psalm-taint-escape sql to SqlQuery::exec() docblock.
  • Add @psalm-taint-escape sql to SqlQuery::getRow() docblock.
  • Add @psalm-taint-escape sql to SqlQuery::getRowList() docblock.
  • Add @psalm-taint-escape sql to SqlQuery::getCount() docblock.
  • Add @psalm-taint-escape sql to SqlQuery::getPages() docblock.
src/SqlQuery.php
Mark low-level SQL execution helpers as escaping sql taint to indicate they use prepared statements with bound parameters.
  • Annotate PerformSqlInterface::perform() with @psalm-taint-escape sql in the interface docblock.
  • Annotate PerformSql::perform() implementation with @psalm-taint-escape sql.
  • Annotate PerformTemplatedSql::perform() implementation with @psalm-taint-escape sql.
src/PerformSqlInterface.php
src/PerformSql.php
src/PerformTemplatedSql.php

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 28, 2025

📝 Walkthrough

Walkthrough

Added @psalm-taint-escape sql docblock annotations to several SQL-related public methods (PerformSql, PerformTemplatedSql, SqlQuery and their interfaces) to signal taint-escape for static analysis; no signature or runtime behavior changes.

Changes

Cohort / File(s) Summary
SQL Execution Implementations & Interface
src/PerformSql.php, src/PerformSqlInterface.php, src/PerformTemplatedSql.php
Added /** @psalm-taint-escape sql */ above the public perform() methods' docblocks. No signature or runtime changes.
SQL Query Implementation & Interface
src/SqlQuery.php, src/SqlQueryInterface.php
Added /** @psalm-taint-escape sql */ annotations to docblocks for exec(), getRow(), getRowList(), getCount(), and getPages() methods. No signature or runtime changes.
Tests — SQL fixtures
tests/sql/create_promise.sql, tests/sql/create_todo.sql
Modified test SQL fixtures: table renames between promise/todo, changed id type to TEXT, and added/removed time column in respective files.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Possibly related PRs

Suggested reviewers

  • NaokiTsuchiya

Poem

🐰 I nudged the docs where taint would roam,
A tiny tag to call SQL home.
Static eyes now nod and say,
"This string is safe — carry on, away!" 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding Psalm taint annotations for SQL injection analysis. It directly matches the primary objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 92.86% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-psalm-taint-annotations

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cae33b1 and bde10bf.

📒 Files selected for processing (7)
  • src/PerformSql.php
  • src/PerformSqlInterface.php
  • src/PerformTemplatedSql.php
  • src/SqlQuery.php
  • src/SqlQueryInterface.php
  • tests/sql/create_promise.sql
  • tests/sql/create_todo.sql
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/PerformSqlInterface.php
  • src/SqlQueryInterface.php
  • src/PerformSql.php
  • src/SqlQuery.php
  • src/PerformTemplatedSql.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (2)
tests/sql/create_promise.sql (1)

1-6: Verify that schema changes align with test requirements.

The PR objectives focus on adding Psalm taint annotations to PHP methods but don't mention these SQL schema changes (table rename from todo to promise, id type change, and addition of time column). Please confirm these test schema modifications are intentional and support the taint analysis testing.

Additionally, consider whether the id column should have a PRIMARY KEY constraint to better reflect typical database usage patterns in tests.

tests/sql/create_todo.sql (1)

1-5: Verify coordinated schema changes between test files.

This file creates a todo table (inverse of the promise table in create_promise.sql). Like create_promise.sql, these schema changes aren't mentioned in the PR objectives, which focus on Psalm taint annotations. Please confirm:

  1. These companion schema modifications are intentional and serve distinct test scenarios
  2. The coordination between the two test SQL files is correct for your testing strategy
  3. The absence of constraints (e.g., PRIMARY KEY on id) is appropriate for your test cases

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • Double-check whether these methods should be marked as @psalm-taint-escape sql on the whole function (i.e. all their outputs) versus more granular annotations on specific parameters/return values, since they are primarily sinks for tainted input rather than sanitizers that produce safe SQL strings.
  • For PerformTemplatedSql::perform() in particular, verify that all templating paths enforce bound parameters and never perform string interpolation into the SQL query; if any templating mode allows raw concatenation, this method should not be globally marked as @psalm-taint-escape sql.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Double-check whether these methods should be marked as `@psalm-taint-escape sql` on the whole function (i.e. all their outputs) versus more granular annotations on specific parameters/return values, since they are primarily *sinks* for tainted input rather than sanitizers that produce safe SQL strings.
- For `PerformTemplatedSql::perform()` in particular, verify that all templating paths enforce bound parameters and never perform string interpolation into the SQL query; if any templating mode allows raw concatenation, this method should not be globally marked as `@psalm-taint-escape sql`.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Add @psalm-taint-escape sql annotations to indicate that values passed
to SQL query methods are safely escaped via prepared statements.

Annotated classes:
- PerformSqlInterface::perform() - interface declaration
- PerformSql::perform() - prepared statement execution
- PerformTemplatedSql::perform() - templated SQL execution
- SqlQueryInterface - all query methods (getRow, getRowList, exec, getCount, getPages)
- SqlQuery - implementation of all query methods

These annotations enable Psalm's taint analysis to understand that user
input flowing through these methods is properly escaped when used with
prepared statements.
@koriym koriym force-pushed the add-psalm-taint-annotations branch from cae33b1 to bde10bf Compare December 28, 2025 11:18
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/PerformTemplatedSql.php (1)

22-30: @psalm-taint-escape sql annotation is misleading—method concatenates non-tainted values, not escapes taint.

The method doesn't escape SQL input via str_replace(). It concatenates application-controlled values: $sqlId is an internal identifier (never user input) and $sql is file content. While this is safe, the annotation suggests active escaping when the real safety comes from these values being non-tainted by design.

Recommended fix: Replace the annotation with parameter-level taint sink declarations:

/**
 * @psalm-taint-escape sql
 * @psalm-taint-sink sql $sqlId
 * @psalm-taint-sink sql $sql
 */

This documents that $sqlId and $sql must never contain tainted user input, which is the actual security requirement.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc592e0 and cae33b1.

📒 Files selected for processing (5)
  • src/PerformSql.php
  • src/PerformSqlInterface.php
  • src/PerformTemplatedSql.php
  • src/SqlQuery.php
  • src/SqlQueryInterface.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (12)
src/SqlQuery.php (5)

66-83: Annotation looks correct assuming trusted SQL files and parameterized values.

The @psalm-taint-escape sql annotation is appropriate for the same reasons as exec().


85-97: Annotation looks correct assuming trusted SQL files and parameterized values.

The @psalm-taint-escape sql annotation is appropriate for the same reasons as the other query methods.


99-108: Annotation looks correct assuming trusted SQL files and parameterized values.

The @psalm-taint-escape sql annotation is appropriate since ExtendedPdoAdapter should use parameterized queries for the $values array.


202-217: Annotation looks correct assuming trusted SQL files and parameterized values.

The @psalm-taint-escape sql annotation is appropriate here as the pager factory and Pages class should use parameterized queries.


55-64: @psalm-taint-escape sql annotation is correct; path traversal risk with $sqlId is mitigated by design.

The annotation appropriately marks SQL escaping since PDO prepared statements with parameter binding handle the $values safely. Regarding the path traversal concern: $sqlId is directly interpolated into the file path at line 117 without validation, making ../ sequences technically possible (e.g., $sqlId = '../../../etc/passwd' would attempt to read /etc/passwd.sql). However, this risk is mitigated by the architecture—$sqlId is hardcoded within application code (seen across tests: 'todo_add', 'todo_item', etc.), not sourced from user input or external configuration. Additionally, only .sql files can be accessed due to the hardcoded .sql extension in the path format. The design assumes trusted SQL identifiers, which is reasonable in this context.

src/PerformSqlInterface.php (1)

12-24: Interface annotation aligns with implementations.

The @psalm-taint-escape sql annotation on the interface is consistent with the annotations added to implementing classes (PerformSql and PerformTemplatedSql).

Note: The semantic correctness of this annotation depends on all implementations properly escaping SQL. See concerns raised in PerformTemplatedSql.php review.

src/PerformSql.php (1)

13-21: Annotation is appropriate for PDO prepared statement execution.

The @psalm-taint-escape sql annotation is correct here since $pdo->perform() uses PDO prepared statements that safely bind the $values array parameters.

Note: This assumes $sql contains only SQL with placeholders, not user-controlled content directly concatenated into the SQL string. Based on the codebase, $sql comes from trusted .sql files, which makes this safe.

src/SqlQueryInterface.php (5)

9-16: Interface annotation aligns with implementation.

The @psalm-taint-escape sql annotation is consistent with the implementation in SqlQuery.php.


18-25: Interface annotation aligns with implementation.

The @psalm-taint-escape sql annotation is consistent with the implementation in SqlQuery.php.


27-32: Interface annotation aligns with implementation.

The @psalm-taint-escape sql annotation is consistent with the implementation in SqlQuery.php.


34-39: Interface annotation aligns with implementation.

The @psalm-taint-escape sql annotation is consistent with the implementation in SqlQuery.php.


41-47: Interface annotation aligns with implementation.

The @psalm-taint-escape sql annotation is consistent with the implementation in SqlQuery.php.

@koriym koriym merged commit 0f43c1b into 1.x Dec 28, 2025
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants