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
6 changes: 4 additions & 2 deletions nodes/src/nodes/store_pinecone/IGlobal.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
# This class controls the data shared between all threads for the task
# ------------------------------------------------------------------------------

import hashlib
import os
import re
from typing import Any, Dict
Expand All @@ -45,8 +46,9 @@ def _open_store(self, logical_type: str, conn_config: Dict[str, Any], bag: Dict[
return Store(logical_type, conn_config, bag)

def _sub_key(self) -> str:
"""Return the transform sub-key: host/port/collection."""
return f'{self.store.host}/{self.store.port}/{self.store.collection}'
"""Return the transform sub-key without leaking the Pinecone API key."""
account = hashlib.sha256((self.store.apikey or '').encode()).hexdigest()[:12]
return f'{account}/{self.store.profile}/{self.store.collection}'

def _probe_connection(self, config: Dict[str, Any]) -> None:
"""
Expand Down
2 changes: 2 additions & 0 deletions nodes/src/nodes/store_pinecone/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ The tool path runs on the control plane and does not pass through the pipeline's

Set `apikey` to your Pinecone API key. The key is used during both config validation (HTTP client) and runtime data operations (gRPC client). There are no additional auth modes - Pinecone uses API-key-only authentication.

Changing the API key, profile, or index changes the transform key used to track ingested objects. Existing Pinecone pipelines may re-ingest once after upgrading from versions that keyed only by index name.

---

<!-- ROCKETRIDE:GENERATED:PARAMS START -->
Expand Down
59 changes: 59 additions & 0 deletions nodes/test/store_pinecone/test_transform_sub_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import importlib.util
import sys
from pathlib import Path
from types import ModuleType, SimpleNamespace


class StoreGlobalBase:
pass


ai = ModuleType('ai')
ai_common = ModuleType('ai.common')
ai_common_store = ModuleType('ai.common.store')
ai_common_store.StoreGlobalBase = StoreGlobalBase
rocketlib = ModuleType('rocketlib')
rocketlib.warning = lambda message: None

path = Path(__file__).parents[2] / 'src/nodes/store_pinecone/IGlobal.py'
spec = importlib.util.spec_from_file_location('store_pinecone_IGlobal', path)
assert spec is not None
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
stubs = {
'ai': ai,
'ai.common': ai_common,
'ai.common.store': ai_common_store,
'rocketlib': rocketlib,
}
originals = {name: sys.modules.get(name) for name in stubs}
sys.modules.update(stubs)
try:
spec.loader.exec_module(module)
finally:
for name, original in originals.items():
if original is None:
del sys.modules[name]
else:
sys.modules[name] = original
IGlobal = module.IGlobal


def _sub_key(apikey: str, profile: str = 'serverless-dense', collection: str = 'shared-index') -> str:
glb = IGlobal.__new__(IGlobal)
glb.store = SimpleNamespace(apikey=apikey, profile=profile, collection=collection)
return glb._sub_key()


def test_sub_key_distinguishes_accounts_without_leaking_api_key() -> None:
first = _sub_key('pcsk_first_secret')
second = _sub_key('pcsk_second_secret')

assert first != second
assert first.endswith('/serverless-dense/shared-index')
assert 'pcsk_first_secret' not in first
assert 'pcsk_second_secret' not in second


def test_sub_key_includes_profile_and_collection() -> None:
assert _sub_key('pcsk_secret', 'pod-based', 'other-index').endswith('/pod-based/other-index')
Loading