Skip to content

Fix auditable-tables shield crashing on insert_all/upsert_all#158

Merged
rosa merged 1 commit into
masterfrom
fix-insert-all-auditable-shield
May 29, 2026
Merged

Fix auditable-tables shield crashing on insert_all/upsert_all#158
rosa merged 1 commit into
masterfrom
fix-insert-all-auditable-shield

Conversation

@rosa

@rosa rosa commented May 29, 2026

Copy link
Copy Markdown
Member

Problem

The auditable-tables shield (ProtectedAuditableTables) wraps the connection's query methods and does:

sql = args.first
if Console1984.command_executor.executing_user_command? && sql.b =~ auditable_tables_regexp

It assumes args.first is a SQL String. But exec_insert_all receives an ActiveRecord::InsertAll, which doesn't respond to #b. So any insert_all/upsert_all run from an audited console raises:

NoMethodError: undefined method 'b' for an instance of ActiveRecord::InsertAll

It's invisible in normal web/job traffic because the check is short-circuited by executing_user_command? — it only bites in the console. In practice it blows up routine maintenance: e.g. updating a record whose callbacks insert_all (assignment summaries, backlinks, …) dies mid-way, sometimes rolling back the whole transaction.

Fix

Coerce the argument before the auditable-tables check. For a non-String, use its target table name (InsertAll#model.table_name) so the regexp still correctly detects — and forbids — access to audited tables. The underlying adapter still receives the original argument untouched, and string SQL behavior is unchanged.

def auditable_sql(sql)
  string = sql.is_a?(String) ? sql : (sql.try(:model)&.table_name || sql.to_s)
  string.b
end

Tests

Added test/ext/active_record/protected_auditable_tables_test.rb:

  • an InsertAll-shaped arg for a non-auditable table is allowed and passed through untouched (the regression);
  • an InsertAll for an auditable table is still forbidden;
  • string SQL is still checked against auditable tables (unchanged).

Full suite: 86 runs, 374 assertions, 0 failures, 0 errors. bin/rubocop clean.

🤖 Generated with Claude Code

The connection shield assumes its first argument is a SQL String and
calls `sql.b`, but `exec_insert_all` receives an ActiveRecord::InsertAll,
which doesn't respond to `#b`. So any `insert_all`/`upsert_all` run from
an audited console raised `NoMethodError: undefined method 'b' for an
instance of ActiveRecord::InsertAll`. It only surfaced in the console
because the check is guarded by `executing_user_command?`.

Coerce the argument before the auditable-tables check: for a non-String,
use its target table name (`InsertAll#model.table_name`) so the regexp
still detects access to audited tables. The underlying adapter still
receives the original argument untouched, and string SQL is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 29, 2026 12:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes a console-only crash in the ActiveRecord auditable-tables shield when exec_insert_all receives an ActiveRecord::InsertAll object (which doesn’t implement #b) by normalizing the “SQL” input used for the auditable-table regexp check.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Changes:

  • Introduced auditable_sql(sql) to safely derive a String for audited-table detection (supports String and InsertAll-like objects via model.table_name fallback).
  • Updated the guarded adapter method wrappers to use auditable_sql(sql) instead of calling #b directly on the first argument.
  • Added regression tests covering InsertAll-shaped args for both auditable and non-auditable tables, plus existing string-SQL behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
lib/console1984/ext/active_record/protected_auditable_tables.rb Normalizes the first adapter argument into a String before applying the auditable-table regexp check to avoid #b crashes on non-String inputs.
test/ext/active_record/protected_auditable_tables_test.rb Adds targeted tests ensuring exec_insert_all works with InsertAll-like inputs and still blocks access to auditable tables.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@rosa rosa merged commit 8d916f2 into master May 29, 2026
31 checks passed
@rosa rosa deleted the fix-insert-all-auditable-shield branch May 29, 2026 13:34
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