Summary
When searching an IndexHNSW index with SearchParameters.sel, the ID selector works correctly when bounded_queue=True.
However, when bounded_queue=False, the selector is ignored and the search may return IDs that are explicitly excluded.
This violates the contract of SearchParameters.sel: when it is non-null, only IDs accepted by the selector should be returned.
Bug type
Incorrect result
Environment
FAISS version: 1.14.2
Python: 3.x
OS: Windows
Installation: pip faiss-cpu
Index: IndexHNSWFlat
Metric: METRIC_L2
Minimal reproducer
import faiss
import numpy as np
xb = np.array(
[
[0.0, 0.0],
[10.0, 0.0],
[20.0, 0.0],
[30.0, 0.0],
],
dtype=np.float32,
)
xq = np.array([[0.0, 0.0]], dtype=np.float32)
index = faiss.IndexHNSWFlat(2, 4, faiss.METRIC_L2)
index.hnsw.efConstruction = 40
index.add(xb)
# Only ID 1 is allowed.
selector = faiss.IDSelectorRange(1, 2)
params = faiss.SearchParametersHNSW(
efSearch=40,
bounded_queue=False,
sel=selector,
)
D, I = index.search(xq, 1, params=params)
print("labels:", I.tolist())
print("distances:", D.tolist())
assert I.tolist() == [[1]]
assert D.tolist() == [[100.0]]
Expected behavior
Because the selector only allows ID 1, the result should be:
labels: [[1]]
distances: [[100.0]]
Every returned label must satisfy:
This requirement should not depend on the value of bounded_queue.
Actual behavior
With bounded_queue=False, the result is:
labels: [[0]]
distances: [[0.0]]
ID 0 is not accepted by the selector, but it is still returned.
Comparison
Using the same index, data, query, and selector, but changing the parameter to:
returns the correct result:
labels: [[1]]
distances: [[100.0]]
Why this is a bug
This is not normal approximate-nearest-neighbor recall behavior.
Even if HNSW fails to find the best allowed candidate, it must not return an ID that is explicitly rejected by the selector. The selector is a hard validity constraint on returned results, not a ranking preference.
In this example:
- ID 0 is explicitly excluded;
- ID 1 is the only allowed ID;
- filtering works when
bounded_queue=True;
- filtering is ignored when
bounded_queue=False.
This indicates a queue-path-specific ID filtering bug.
Impact
Applications that rely on SearchParameters.sel for permission filtering, tenant isolation, metadata filtering, or deleted-record filtering may receive IDs that should not be returned when bounded_queue=False.
This can cause filtering rules to be bypassed and restricted or cross-tenant data to appear in search results.
Summary
When searching an
IndexHNSWindex withSearchParameters.sel, the ID selector works correctly whenbounded_queue=True.However, when
bounded_queue=False, the selector is ignored and the search may return IDs that are explicitly excluded.This violates the contract of
SearchParameters.sel: when it is non-null, only IDs accepted by the selector should be returned.Bug type
Incorrect result
Environment
Minimal reproducer
Expected behavior
Because the selector only allows ID 1, the result should be:
Every returned label must satisfy:
This requirement should not depend on the value of
bounded_queue.Actual behavior
With
bounded_queue=False, the result is:ID 0 is not accepted by the selector, but it is still returned.
Comparison
Using the same index, data, query, and selector, but changing the parameter to:
returns the correct result:
Why this is a bug
This is not normal approximate-nearest-neighbor recall behavior.
Even if HNSW fails to find the best allowed candidate, it must not return an ID that is explicitly rejected by the selector. The selector is a hard validity constraint on returned results, not a ranking preference.
In this example:
bounded_queue=True;bounded_queue=False.This indicates a queue-path-specific ID filtering bug.
Impact
Applications that rely on
SearchParameters.selfor permission filtering, tenant isolation, metadata filtering, or deleted-record filtering may receive IDs that should not be returned whenbounded_queue=False.This can cause filtering rules to be bypassed and restricted or cross-tenant data to appear in search results.