Skip to content
Closed
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
14 changes: 11 additions & 3 deletions lib/aikido/zen/idor/analysis_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ def self.from_json(data)
name: data["column"],
value: data["value"],
is_placeholder: data["is_placeholder"],
placeholder_number: data["placeholder_number"]
placeholder_number: data["placeholder_number"],
is_where: data["is_where"]
)
end

Expand All @@ -46,12 +47,14 @@ def self.from_json(data)
# @param value [String]
# @param is_placeholder [Boolean]
# @param placeholder_number [Integer, nil]
def initialize(table_qualifier:, name:, value:, is_placeholder:, placeholder_number: nil)
# @param is_where [Boolean, nil] Whether this equality originates from a WHERE clause (row-restricting context)
def initialize(table_qualifier:, name:, value:, is_placeholder:, placeholder_number: nil, is_where: nil)
@table_qualifier = table_qualifier
@name = name
@value = value
@is_placeholder = is_placeholder
@placeholder_number = placeholder_number
@is_where = is_where
end

# @return [String, nil]
Expand All @@ -69,13 +72,18 @@ def initialize(table_qualifier:, name:, value:, is_placeholder:, placeholder_num
# @return [Integer, nil]
attr_accessor :placeholder_number

# @return [Boolean, nil] Whether this equality originates from a WHERE clause (row-restricting context).
# nil indicates the native analyzer does not provide this information (older version).
attr_accessor :is_where

def ==(other)
other.is_a?(self.class) &&
other.table_qualifier == table_qualifier &&
other.name == name &&
other.value == value &&
other.is_placeholder == is_placeholder &&
other.placeholder_number == placeholder_number
other.placeholder_number == placeholder_number &&
other.is_where == is_where
end
alias_method :eql?, :==
end
Expand Down
10 changes: 10 additions & 0 deletions lib/aikido/zen/idor/protector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ def protect_filter(dialect, query_result, tenant_id, params)
raise IDOR::Error, "Zen IDOR protection: query on table '#{table.name}' is missing column '#{@config.idor_tenant_column_name}'"
end

# Verify that the tenant equality originates from a WHERE clause (row-restricting context).
# Equalities in SELECT projections, HAVING clauses, or other non-filtering contexts
# do not restrict the result set and must be rejected to prevent IDOR bypasses.
# The is_where field is provided by libzen >= 0.1.75. When present and false,
# we reject the query. When nil (older libzen), we allow it for backward compatibility
# but log a warning that the query cannot be fully validated.
if tenant_column.is_where == false
raise IDOR::Error, "Zen IDOR protection: query on table '#{table.name}' has '#{@config.idor_tenant_column_name}' equality in a non-filtering context (e.g., SELECT projection, HAVING clause)"
end

resolved_tenant_id = tenant_column.value

if tenant_column.is_placeholder
Expand Down
Loading