fix: document security features and harden broker recovery

parent 2c79932f
...@@ -254,6 +254,56 @@ Important `runpod_config` fields: ...@@ -254,6 +254,56 @@ Important `runpod_config` fields:
Dashboard support includes runtime refresh, status inspection, public catalog import, and persisted state tracking. Dashboard support includes runtime refresh, status inspection, public catalog import, and persisted state tracking.
## Security Filters and Prompt Analysis
AISBF includes native request-time prompt analysis and content-safety controls that can be enabled globally and overridden at provider, rotation, autoselect, or model level.
### Prompt-security controls
The `prompt_security` feature group in `aisbf.json` controls:
- `security_scan` - enables local prompt scanning before upstream execution
- `context_lens` - enables prompt composition analytics and risk telemetry capture
- `block_high_risk_prompts` - blocks requests whose local prompt analysis resolves to `high` risk
- `persist_prompt_text` - stores raw prompt text when explicitly enabled
- `redact_before_persist` - keeps redaction enabled before persistence to avoid storing sensitive content in plain text by default
- `risk_threshold` - controls the blocking threshold, defaulting to `high`
Default shipped posture:
- prompt-security scanning: disabled
- Context Lens analytics: disabled
- block-high-risk prompts: disabled
- persist raw prompt text: disabled
- redact-before-persist: enabled
### Request-time behavior
When enabled, AISBF performs local prompt analysis before proxying the request upstream:
- scans prompts using regex and heuristic detectors for suspicious prompt-injection and policy-evasion patterns
- computes a risk level and aggregate risk score
- builds a composition summary including prompt shape, dominant role, system-prompt presence, and tool usage posture
- stores redacted summaries in prompt analytics tables for later dashboard inspection
- can stop execution locally when `block_high_risk_prompts` is enabled and the resolved risk level is `high`
### Content classification filters
AISBF also supports request classification flags for:
- NSFW-sensitive traffic via `enable_nsfw_classification`
- privacy-sensitive traffic via `enable_privacy_classification`
These controls can be applied on providers, rotations, autoselect configurations, and model entries so routing decisions can respect the sensitivity of the content being processed.
### Dashboard visibility
Prompt-security and analytics controls are exposed in the dashboard settings and resource editors:
- global defaults can be configured from dashboard settings
- provider/model editors expose tri-state overrides for prompt security and Context Lens analytics
- rotation/autoselect editors expose inherited or explicit overrides for the same controls
- prompt analysis results appear in the prompt analytics dashboard when scanning or Context Lens capture is enabled
## AISBF Studio ## AISBF Studio
AISBF Studio is the dashboard-native multimodal workspace exposed at `/dashboard/studio`. AISBF Studio is the dashboard-native multimodal workspace exposed at `/dashboard/studio`.
...@@ -307,6 +357,17 @@ AISBF includes a built-in marketplace for sharing configured resources between u ...@@ -307,6 +357,17 @@ AISBF includes a built-in marketplace for sharing configured resources between u
- Settlement support for usage-based sharing - Settlement support for usage-based sharing
- User export controls and market visibility filtering - User export controls and market visibility filtering
### Publishing model
Listings can be created from:
- full provider configurations
- specific provider/model pairs
- rotations
- autoselect resources
Each listing stores a sanitized configuration snapshot so secrets and local credential material are not exposed through the market export path.
### Imported references ### Imported references
Users can import market listings as references instead of duplicating the underlying configuration. Users can import market listings as references instead of duplicating the underlying configuration.
...@@ -317,6 +378,35 @@ Reference behavior: ...@@ -317,6 +378,35 @@ Reference behavior:
- Availability is tied to the source listing state - Availability is tied to the source listing state
- Dashboard UI clearly distinguishes imported resources from locally owned ones - Dashboard UI clearly distinguishes imported resources from locally owned ones
## CoderAI Integration
AISBF supports CoderAI both as a directly reachable provider and as a brokered remote runtime.
### Transport modes
- direct HTTP mode for OpenAI-compatible endpoints
- direct WebSocket bridge mode for framed request/response routing
- broker mode for outbound-only NAT traversal where the CoderAI worker connects into AISBF
### Broker features
- provider-scoped registration tokens
- global and user-scoped broker endpoints
- persisted broker session snapshots in `~/.aisbf/coderai_broker_sessions.json`
- dashboard session visibility with owner, client ID, transport, endpoint, Studio endpoints, and performance telemetry
- Studio-native proxy forwarding for non-chat endpoints and long-running jobs
### CoderAI dashboard workflow
- create or edit a `coderai` provider
- choose direct transport or broker mode
- assign a stable `client_id`
- generate or rotate the `registration_token`
- connect the remote worker to the broker endpoint
- inspect connection health and advertised capabilities from the providers page
For protocol-level implementation details, see `docs/coderai-integration.md`.
## Wallet System ## Wallet System
AISBF includes a comprehensive unified wallet system that manages user fiat balances for subscription payments and provides multiple top-up methods. AISBF includes a comprehensive unified wallet system that manages user fiat balances for subscription payments and provides multiple top-up methods.
......
...@@ -78,6 +78,7 @@ The dashboard provides: ...@@ -78,6 +78,7 @@ The dashboard provides:
- Provider configuration and API key management - Provider configuration and API key management
- RunPod runtime controls and CoderAI broker session monitoring - RunPod runtime controls and CoderAI broker session monitoring
- Rotation and autoselect model setup - Rotation and autoselect model setup
- Prompt security controls, Context Lens analytics, and NSFW/privacy routing filters
- AISBF Studio multimodal workflows and pipeline bindings - AISBF Studio multimodal workflows and pipeline bindings
- User wallet management and top-up options - User wallet management and top-up options
- Token usage analytics, broker telemetry, and cost tracking - Token usage analytics, broker telemetry, and cost tracking
...@@ -85,6 +86,27 @@ The dashboard provides: ...@@ -85,6 +86,27 @@ The dashboard provides:
- SSL/TLS and TOR configuration - SSL/TLS and TOR configuration
- Multi-user administration - Multi-user administration
## Featured Capabilities
### Market
- Publish providers, single models, rotations, and autoselect configurations to the built-in AISBF marketplace
- Import published resources as locked references instead of cloning their full configuration locally
- Track listing activity, usage settlement, revenue, and admin-side market visibility from the dashboard
### CoderAI
- Use `coderai` providers in direct HTTP mode, direct WebSocket bridge mode, or NAT-friendly broker mode
- Register remote workers with provider-scoped tokens and inspect broker session status from the dashboard
- Forward Studio-native endpoints over the CoderAI bridge so chat, multimodal, and long-running jobs can work through the same connection
### Security Filters
- Enable prompt-security scanning to detect suspicious prompt patterns before upstream execution
- Enable Context Lens analytics to capture prompt composition metadata, risk summaries, and redacted evidence
- Enable NSFW and privacy classification so AISBF can route or restrict traffic based on content sensitivity
- Optionally block high-risk prompts and keep persisted prompt text disabled by default while redaction remains enabled
## API Usage ## API Usage
### Basic Chat Completion ### Basic Chat Completion
...@@ -119,6 +141,7 @@ For complete documentation, configuration guides, and API reference: ...@@ -119,6 +141,7 @@ For complete documentation, configuration guides, and API reference:
- **[📚 Full Documentation](https://git.nexlab.net/nexlab/aisbf/blob/master/DOCUMENTATION.md)** - Comprehensive user and developer guide - **[📚 Full Documentation](https://git.nexlab.net/nexlab/aisbf/blob/master/DOCUMENTATION.md)** - Comprehensive user and developer guide
- **[🔧 Installation Guide](https://git.nexlab.net/nexlab/aisbf/blob/master/DOCUMENTATION.md#installation)** - Detailed setup instructions - **[🔧 Installation Guide](https://git.nexlab.net/nexlab/aisbf/blob/master/DOCUMENTATION.md#installation)** - Detailed setup instructions
- **[⚙️ Configuration](https://git.nexlab.net/nexlab/aisbf/blob/master/DOCUMENTATION.md#configuration)** - All configuration options - **[⚙️ Configuration](https://git.nexlab.net/nexlab/aisbf/blob/master/DOCUMENTATION.md#configuration)** - All configuration options
- **[🛡️ Security Filters](https://git.nexlab.net/nexlab/aisbf/blob/master/DOCUMENTATION.md#security-filters-and-prompt-analysis)** - Prompt security, Context Lens analytics, and content classification
- **[🎛️ Studio Guide](https://git.nexlab.net/nexlab/aisbf/blob/master/DOCUMENTATION.md#aisbf-studio)** - Multimodal Studio, bindings, and pipelines - **[🎛️ Studio Guide](https://git.nexlab.net/nexlab/aisbf/blob/master/DOCUMENTATION.md#aisbf-studio)** - Multimodal Studio, bindings, and pipelines
- **[🛒 Marketplace](https://git.nexlab.net/nexlab/aisbf/blob/master/DOCUMENTATION.md#marketplace-and-references)** - Publishing, imports, and settlements - **[🛒 Marketplace](https://git.nexlab.net/nexlab/aisbf/blob/master/DOCUMENTATION.md#marketplace-and-references)** - Publishing, imports, and settlements
- **[🤖 CoderAI Broker](https://git.nexlab.net/nexlab/aisbf/blob/master/docs/coderai-integration.md)** - Broker protocol and integration reference - **[🤖 CoderAI Broker](https://git.nexlab.net/nexlab/aisbf/blob/master/docs/coderai-integration.md)** - Broker protocol and integration reference
......
...@@ -8,6 +8,7 @@ import asyncio ...@@ -8,6 +8,7 @@ import asyncio
from collections import deque from collections import deque
import json import json
import logging import logging
import os
import time import time
import uuid import uuid
from dataclasses import dataclass, field from dataclasses import dataclass, field
...@@ -235,8 +236,28 @@ class CoderAIBroker: ...@@ -235,8 +236,28 @@ class CoderAIBroker:
], ],
"updated_at": time.time(), "updated_at": time.time(),
} }
with open(self._state_path, 'w') as f: temp_path = self._state_path.with_suffix(self._state_path.suffix + '.tmp')
with open(temp_path, 'w') as f:
json.dump(payload, f, indent=2) json.dump(payload, f, indent=2)
f.flush()
os.fsync(f.fileno())
os.replace(temp_path, self._state_path)
def _quarantine_invalid_state_file(self) -> Optional[Path]:
if not self._state_path.exists():
return None
timestamp = int(time.time())
quarantine_path = self._state_path.with_name(f"{self._state_path.name}.corrupt.{timestamp}")
counter = 1
while quarantine_path.exists():
quarantine_path = self._state_path.with_name(f"{self._state_path.name}.corrupt.{timestamp}.{counter}")
counter += 1
try:
self._state_path.replace(quarantine_path)
return quarantine_path
except Exception:
logger.exception("Failed to quarantine invalid CoderAI broker session state file")
return None
def _load_persisted_sessions(self) -> None: def _load_persisted_sessions(self) -> None:
if not self._state_path.exists(): if not self._state_path.exists():
...@@ -245,7 +266,15 @@ class CoderAIBroker: ...@@ -245,7 +266,15 @@ class CoderAIBroker:
with open(self._state_path) as f: with open(self._state_path) as f:
payload = json.load(f) payload = json.load(f)
except Exception as e: except Exception as e:
logger.warning(f"Failed to load persisted CoderAI broker sessions: {e}") quarantine_path = self._quarantine_invalid_state_file()
if quarantine_path is not None:
logger.warning(
"Failed to load persisted CoderAI broker sessions: %s. Moved invalid state file to %s",
e,
quarantine_path,
)
else:
logger.warning(f"Failed to load persisted CoderAI broker sessions: {e}")
return return
for raw_session in payload.get("sessions") or []: for raw_session in payload.get("sessions") or []:
......
...@@ -2908,7 +2908,7 @@ class DatabaseManager: ...@@ -2908,7 +2908,7 @@ class DatabaseManager:
def _json_extract(column: str, path: str) -> str: def _json_extract(column: str, path: str) -> str:
if self.db_type == 'sqlite': if self.db_type == 'sqlite':
return f"json_extract({column}, '$.{path}')" return f"json_extract({column}, '$.{path}')"
return f"JSON_UNQUOTE(JSON_EXTRACT(CAST({column} AS JSON), '$.{path}'))" return f"JSON_UNQUOTE(JSON_EXTRACT({column}, '$.{path}'))"
role_expr = _json_extract('summary_json', 'composition.largest_segment_role') role_expr = _json_extract('summary_json', 'composition.largest_segment_role')
shape_expr = _json_extract('summary_json', 'composition.prompt_shape') shape_expr = _json_extract('summary_json', 'composition.prompt_shape')
......
...@@ -2,11 +2,12 @@ import asyncio ...@@ -2,11 +2,12 @@ import asyncio
import json import json
import sys import sys
from pathlib import Path from pathlib import Path
from types import SimpleNamespace
import pytest import pytest
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from aisbf.coderai_broker import broker from aisbf.coderai_broker import broker, CoderAIBroker
from aisbf.config import ProviderConfig, config from aisbf.config import ProviderConfig, config
from aisbf.database import DatabaseRegistry from aisbf.database import DatabaseRegistry
...@@ -330,3 +331,34 @@ def test_user_registration_rejects_global_provider_token(monkeypatch): ...@@ -330,3 +331,34 @@ def test_user_registration_rejects_global_provider_token(monkeypatch):
config.providers.pop("coderai-global-only", None) config.providers.pop("coderai-global-only", None)
else: else:
config.providers["coderai-global-only"] = original_provider config.providers["coderai-global-only"] = original_provider
def test_load_persisted_sessions_quarantines_corrupt_state_file(tmp_path):
broker_instance = CoderAIBroker()
broker_instance._state_path = tmp_path / "coderai_broker_sessions.json"
broker_instance._state_path.write_text('{"sessions": [invalid]}', encoding="utf-8")
broker_instance._cache = SimpleNamespace(
broker_node_id=lambda: "node-test",
broker_set=lambda *args, **kwargs: None,
broker_get=lambda *args, **kwargs: None,
broker_delete=lambda *args, **kwargs: None,
)
broker_instance._load_persisted_sessions()
assert not broker_instance._state_path.exists()
quarantined = list(tmp_path.glob("coderai_broker_sessions.json.corrupt.*"))
assert len(quarantined) == 1
def test_persist_sessions_locked_writes_state_atomically(tmp_path):
broker_instance = CoderAIBroker()
broker_instance._state_path = tmp_path / "coderai_broker_sessions.json"
broker_instance._sessions_by_id = {}
broker_instance._persist_sessions_locked()
assert broker_instance._state_path.exists()
assert not (tmp_path / "coderai_broker_sessions.json.tmp").exists()
payload = json.loads(broker_instance._state_path.read_text(encoding="utf-8"))
assert payload["sessions"] == []
...@@ -346,6 +346,44 @@ def test_record_prompt_analysis_run_uses_mysql_last_insert_id_fallback(): ...@@ -346,6 +346,44 @@ def test_record_prompt_analysis_run_uses_mysql_last_insert_id_fallback():
assert '"has_tools": true' in insert_params[-1] assert '"has_tools": true' in insert_params[-1]
def test_prompt_analysis_details_mysql_json_extract_uses_text_column_directly():
from aisbf.database import DatabaseManager
class CursorStub:
def __init__(self):
self.executed = []
self._fetchall_calls = 0
def execute(self, sql, params=None):
self.executed.append((sql, params))
def fetchall(self):
self._fetchall_calls += 1
return []
def fetchone(self):
return (0, 0, 0, 0)
class ConnStub:
def __init__(self, cursor):
self._cursor = cursor
def cursor(self):
return self._cursor
manager = DatabaseManager.__new__(DatabaseManager)
manager.db_type = "mysql"
cursor = CursorStub()
manager._get_connection = lambda: MysqlConnCtxStub(ConnStub(cursor))
now = __import__("datetime").datetime.now()
manager.get_prompt_analysis_details(start=now, end=now)
executed_sql = "\n".join(sql for sql, _ in cursor.executed)
assert "JSON_UNQUOTE(JSON_EXTRACT(summary_json, '$.composition.largest_segment_role'))" in executed_sql
assert "CAST(summary_json AS JSON)" not in executed_sql
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_handle_chat_completion_uses_cache_when_provider_override_enables_it(monkeypatch): async def test_handle_chat_completion_uses_cache_when_provider_override_enables_it(monkeypatch):
request = RequestStub() request = RequestStub()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment