Merge pull request 'dev-race-condition-fixes' (#3) from dev-race-condition-fixes into main
All checks were successful
Publish to PyPI / publish (push) Successful in 14s

Reviewed-on: https://bitfreedom.net/code/code/nomyo-ai/nomyo/pulls/3
This commit is contained in:
Alpha Nerd 2026-04-09 13:05:14 +02:00
commit 302e47d980
4 changed files with 14 additions and 21 deletions

View file

@ -4,14 +4,13 @@ from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
# Setup module logger # Setup module logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Import secure memory module # Import secure memory module
try: try:
from .SecureMemory import secure_bytearray, get_memory_protection_info, _get_secure_memory from .SecureMemory import secure_bytearray, _get_secure_memory
_SECURE_MEMORY_AVAILABLE = True _SECURE_MEMORY_AVAILABLE = True
except ImportError: except ImportError:
_SECURE_MEMORY_AVAILABLE = False _SECURE_MEMORY_AVAILABLE = False
@ -77,19 +76,21 @@ class SecureCompletionClient:
- Response parsing - Response parsing
""" """
def __init__(self, router_url: str = "https://api.nomyo.ai:12435", allow_http: bool = False): def __init__(self, router_url: str = "https://api.nomyo.ai:12435", allow_http: bool = False, secure_memory: bool = True):
""" """
Initialize the secure completion client. Initialize the secure completion client.
Args: Args:
router_url: Base URL of the NOMYO Router (must use HTTPS for production) router_url: Base URL of the NOMYO Router (must use HTTPS for production)
allow_http: Allow HTTP connections (ONLY for local development, never in production) allow_http: Allow HTTP connections (ONLY for local development, never in production)
secure_memory: Whether to use secure memory operations for this instance.
""" """
self.router_url = router_url.rstrip('/') self.router_url = router_url.rstrip('/')
self.private_key = None self.private_key = None
self.public_key_pem = None self.public_key_pem = None
self.key_size = 4096 # RSA key size self.key_size = 4096 # RSA key size
self.allow_http = allow_http # Store for use in fetch_server_public_key self.allow_http = allow_http # Store for use in fetch_server_public_key
self._use_secure_memory = _SECURE_MEMORY_AVAILABLE and secure_memory
# Validate HTTPS for security # Validate HTTPS for security
if not self.router_url.startswith("https://"): if not self.router_url.startswith("https://"):
@ -122,7 +123,7 @@ class SecureCompletionClient:
- Rotate keys regularly - Rotate keys regularly
- Store keys outside the project directory in production - Store keys outside the project directory in production
""" """
if not _SECURE_MEMORY_AVAILABLE or not self.private_key: if not self._use_secure_memory or not self.private_key:
return return
try: try:
@ -416,7 +417,7 @@ class SecureCompletionClient:
aes_key = secrets.token_bytes(32) # 256-bit key aes_key = secrets.token_bytes(32) # 256-bit key
try: try:
if _SECURE_MEMORY_AVAILABLE: if self._use_secure_memory:
with secure_bytearray(payload_json) as protected_payload: with secure_bytearray(payload_json) as protected_payload:
with secure_bytearray(aes_key) as protected_aes_key: with secure_bytearray(aes_key) as protected_aes_key:
return await self._do_encrypt( return await self._do_encrypt(
@ -500,7 +501,7 @@ class SecureCompletionClient:
) )
# Use secure memory to protect AES key and decrypted plaintext # Use secure memory to protect AES key and decrypted plaintext
if _SECURE_MEMORY_AVAILABLE: if self._use_secure_memory:
with secure_bytearray(aes_key) as protected_aes_key: with secure_bytearray(aes_key) as protected_aes_key:
ciphertext = base64.b64decode(package["encrypted_payload"]["ciphertext"]) ciphertext = base64.b64decode(package["encrypted_payload"]["ciphertext"])
nonce = base64.b64decode(package["encrypted_payload"]["nonce"]) nonce = base64.b64decode(package["encrypted_payload"]["nonce"])

View file

@ -51,6 +51,6 @@ try:
except ImportError: except ImportError:
pass pass
__version__ = "0.1.1" __version__ = "0.2.2"
__author__ = "NOMYO AI" __author__ = "NOMYO AI"
__license__ = "Apache-2.0" __license__ = "Apache-2.0"

View file

@ -4,9 +4,9 @@ import uuid
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
from .SecureCompletionClient import SecureCompletionClient from .SecureCompletionClient import SecureCompletionClient
# Import secure memory module for configuration # Check if secure memory module is available (used only for the user-facing warning)
try: try:
from .SecureMemory import disable_secure_memory, enable_secure_memory from . import SecureMemory as _ # noqa: F401
_SECURE_MEMORY_AVAILABLE = True _SECURE_MEMORY_AVAILABLE = True
except ImportError: except ImportError:
_SECURE_MEMORY_AVAILABLE = False _SECURE_MEMORY_AVAILABLE = False
@ -69,20 +69,14 @@ class SecureChatCompletion:
key_dir: Directory to load/save RSA keys. If None, ephemeral keys are key_dir: Directory to load/save RSA keys. If None, ephemeral keys are
generated in memory for this session only. generated in memory for this session only.
""" """
self.client = SecureCompletionClient(router_url=base_url, allow_http=allow_http) self.client = SecureCompletionClient(router_url=base_url, allow_http=allow_http, secure_memory=secure_memory)
self._keys_initialized = False self._keys_initialized = False
self._keys_lock = asyncio.Lock() self._keys_lock = asyncio.Lock()
self.api_key = api_key self.api_key = api_key
self._key_dir = key_dir self._key_dir = key_dir
self._secure_memory_enabled = secure_memory self._secure_memory_enabled = secure_memory
# Configure secure memory if available if secure_memory and not _SECURE_MEMORY_AVAILABLE:
if _SECURE_MEMORY_AVAILABLE:
if secure_memory:
enable_secure_memory()
else:
disable_secure_memory()
elif secure_memory:
import warnings import warnings
warnings.warn( warnings.warn(
"Secure memory requested but not available. " "Secure memory requested but not available. "

View file

@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "nomyo" name = "nomyo"
version = "0.2.1" version = "0.2.2"
description = "OpenAI-compatible secure chat client with end-to-end encryption for NOMYO Inference Endpoints" description = "OpenAI-compatible secure chat client with end-to-end encryption for NOMYO Inference Endpoints"
authors = [ authors = [
{name = "NOMYO.AI", email = "ichi@nomyo.ai"}, {name = "NOMYO.AI", email = "ichi@nomyo.ai"},
@ -17,8 +17,6 @@ classifiers = [
"Intended Audience :: Information Technology", "Intended Audience :: Information Technology",
"License :: OSI Approved :: Apache Software License", "License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
@ -27,7 +25,7 @@ classifiers = [
"Topic :: Communications :: Chat", "Topic :: Communications :: Chat",
"Operating System :: OS Independent", "Operating System :: OS Independent",
] ]
requires-python = ">=3.8" requires-python = ">=3.10"
dependencies = [ dependencies = [
"anyio==4.12.0", "anyio==4.12.0",
"certifi==2025.11.12", "certifi==2025.11.12",