Security¶
OpenJarvis มีชั้นความปลอดภัยที่สแกน prompt และผลลัพธ์ของโมเดลหา secret,
ข้อมูลระบุตัวตนส่วนบุคคล (PII) และ path ไฟล์ที่ sensitive ระบบนี้ออกแบบให้
ประกอบกันได้: scanner รันเป็น pipeline และ wrapper GuardrailsEngine วางไว้
หน้า inference backend ใดๆ ก็ได้ โดยไม่ต้องเปลี่ยนวิธีทำงานของโค้ดส่วนที่เหลือ
การรีวิวความปลอดภัยสามชั้น¶
OpenJarvis แยกท่าทีของ host, ขอบเขตข้อมูลระดับแอปพลิเคชัน และ guardrail prompt ตอน runtime ออกจากกัน:
| ชั้น | คำสั่ง / component | สิ่งที่ตรวจสอบ |
|---|---|---|
| Host scan | jarvis scan |
การเข้ารหัสดิสก์, cloud-sync agent, port ของ engine ที่เปิดออก, เครื่องมือเข้าถึงระยะไกล |
| Data-boundary scan | jarvis scan --data-boundaries |
Inference, memory, trace, channel, tools และที่เก็บข้อมูลในเครื่องที่ตั้งค่าไว้ |
| Runtime guardrails | GuardrailsEngine / BoundaryGuard |
Secret, PII และการละเมิดนโยบายไฟล์ใน prompt และผลลัพธ์แบบ live |
ใช้ host scan ก่อนเก็บข้อมูล sensitive ไว้บนเครื่อง ใช้ data-boundary scan
เพื่อยืนยันว่า config.toml ของคุณเป็นแบบ local-only, รองรับ cloud หรือแบบผสม
ใช้ BoundaryGuard ระหว่าง inference เมื่อคุณต้องการ redact หรือบล็อกแบบ live
ดู Data Boundary Scan สำหรับเครื่องมือวินิจฉัย config ของแอปพลิเคชัน และ BoundaryGuard ด้านล่างสำหรับการสแกน ตอน runtime
ภาพรวม¶
โมดูลความปลอดภัยมีองค์ประกอบสี่ตัวที่ใช้งานแยกอิสระได้:
-
GuardrailsEngine
ห่อ
InferenceEngineใดๆ ด้วยการสแกนก่อนและหลังเรียก รองรับโหมด WARN, REDACT และ BLOCK -
SecretScanner
ตรวจจับ API key, token, password และ connection string ในข้อความ
-
PIIScanner
ตรวจจับอีเมล, เลขประกันสังคม (SSN), หมายเลขบัตรเครดิต, เบอร์โทรศัพท์ และ IP สาธารณะ
-
File Policy
บล็อกการเข้าถึง
.env,*.pem,id_rsaและไฟล์ credential อื่นๆ
GuardrailsEngine¶
GuardrailsEngine ห่อ InferenceEngine ใดๆ และสแกนทั้งข้อความ input และ
เนื้อหา output มันไม่ได้ลงทะเบียนใน EngineRegistry — คุณสร้างมันโดยตรง
ด้วยการห่อ instance ของ engine ที่มีอยู่แล้ว
โหมด¶
| โหมด | ค่าคงที่ | พฤติกรรม |
|---|---|---|
| Warn | RedactionMode.WARN |
เผยแพร่ event SECURITY_ALERT แต่ปล่อยข้อความผ่านโดยไม่เปลี่ยนแปลง ค่าเริ่มต้น |
| Redact | RedactionMode.REDACT |
แทนที่สิ่งที่ตรงกันด้วย [REDACTED:pattern_name] ก่อนส่งเข้า/ออกจาก engine |
| Block | RedactionMode.BLOCK |
สร้าง SecurityBlockError ทันทีเมื่อพบสิ่งที่ตรงกัน |
การใช้งานพื้นฐาน¶
from openjarvis.engine.ollama import OllamaEngine
from openjarvis.security.guardrails import GuardrailsEngine
from openjarvis.security.types import RedactionMode
from openjarvis.core.types import Message, Role
engine = OllamaEngine()
guarded = GuardrailsEngine(engine) # (1)!
messages = [Message(role=Role.USER, content="My API key is sk-abc123xyz")]
response = guarded.generate(messages, model="qwen3:8b")
# The key is logged as a warning but the text is passed unchanged
print(response["content"])
- ค่าเริ่มต้นคือ
mode=RedactionMode.WARN,scan_input=True,scan_output=True
from openjarvis.engine.ollama import OllamaEngine
from openjarvis.security.guardrails import GuardrailsEngine
from openjarvis.security.types import RedactionMode
from openjarvis.core.types import Message, Role
engine = OllamaEngine()
guarded = GuardrailsEngine(engine, mode=RedactionMode.REDACT) # (1)!
messages = [Message(role=Role.USER, content="My key is sk-abc123xyz, help me debug")]
response = guarded.generate(messages, model="qwen3:8b")
# Input sent to engine: "My key is [REDACTED:openai_key], help me debug"
- Pattern ที่ sensitive ในข้อความ input จะถูกแทนที่ก่อนถึงโมเดล
from openjarvis.engine.ollama import OllamaEngine
from openjarvis.security.guardrails import GuardrailsEngine, SecurityBlockError
from openjarvis.security.types import RedactionMode
from openjarvis.core.types import Message, Role
engine = OllamaEngine()
guarded = GuardrailsEngine(engine, mode=RedactionMode.BLOCK)
try:
messages = [Message(role=Role.USER, content="AKIA1234567890ABCDEF")]
guarded.generate(messages, model="qwen3:8b")
except SecurityBlockError as exc:
print(f"Blocked: {exc}")
# Blocked: Security scan blocked input: 1 finding(s) detected
พารามิเตอร์ของ Constructor¶
| พารามิเตอร์ | ชนิด | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
engine |
InferenceEngine |
— | Inference engine ที่ถูกห่อ |
scanners |
list[BaseScanner] |
[SecretScanner(), PIIScanner()] |
Scanner ที่จะรัน |
mode |
RedactionMode |
WARN |
การกระทำเมื่อพบสิ่งที่ตรงกัน |
scan_input |
bool |
True |
สแกนข้อความ input |
scan_output |
bool |
True |
สแกนเนื้อหา output |
bus |
EventBus |
None |
Event bus สำหรับ event ด้านความปลอดภัย |
การผสานกับ Event Bus¶
เมื่อระบุ bus ไว้ GuardrailsEngine จะเผยแพร่ event ทุกครั้งที่มีผลการสแกน:
| Event | เมื่อไหร่ |
|---|---|
SECURITY_ALERT |
พบสิ่งที่ตรงกันในโหมด WARN หรือ REDACT |
SECURITY_BLOCK |
พบสิ่งที่ตรงกันในโหมด BLOCK |
คุณ subscribe event เหล่านี้ด้วย AuditLogger เพื่อสร้าง security event log
แบบถาวรได้ ดู Audit Logger ด้านล่าง
Scanner กำหนดเอง¶
คุณส่งชุด subclass ของ BaseScanner ใดๆ เพื่อจำกัดหรือขยายการสแกนได้:
from openjarvis.security.guardrails import GuardrailsEngine
from openjarvis.security.scanner import SecretScanner
from openjarvis.security.types import RedactionMode
# Only scan for secrets, skip PII
guarded = GuardrailsEngine(
engine,
scanners=[SecretScanner()],
mode=RedactionMode.REDACT,
)
Streaming¶
สำหรับการเรียกแบบ streaming GuardrailsEngine.stream() จะส่ง token แบบ
real-time แล้วสแกนย้อนหลัง (post-hoc) บนผลลัพธ์ที่สะสมไว้เพื่อการบันทึก log
เนื่องจาก token ถูกส่งให้ผู้เรียกไปแล้วก่อนที่การสแกนจะเสร็จ โหมด BLOCK จะ
ใช้ได้เฉพาะฝั่ง input ระหว่าง streaming เท่านั้น
Streaming กับโหมด BLOCK
SecurityBlockError จะถูก raise ได้เฉพาะก่อนที่ stream จะเริ่ม (สำหรับการสแกน input) เท่านั้น การบล็อก output ระหว่าง streaming เป็นไปไม่ได้ — ใช้โหมด REDACT หากคุณต้องทำความสะอาดผลลัพธ์ของโมเดลในสถานการณ์ streaming
SecretScanner¶
SecretScanner ตรวจจับ API key, token, password และ credential อื่นๆ ด้วย
regex pattern แต่ละ pattern มี ThreatLevel กำกับไว้
เอกสารอ้างอิง Pattern¶
| ชื่อ Pattern | ระดับภัยคุกคาม | สิ่งที่ตรงกัน |
|---|---|---|
openai_key |
CRITICAL | sk- ตามด้วยตัวอักษร/ตัวเลข 20+ ตัว |
anthropic_key |
CRITICAL | sk-ant- ตามด้วยตัวอักษร 20+ ตัว |
aws_access_key |
CRITICAL | AKIA ตามด้วยตัวพิมพ์ใหญ่/ตัวเลข 16 ตัว |
github_token |
CRITICAL | ghp_, gho_, ghs_, ghr_, github_pat_ ตามด้วยตัวอักษร 36+ ตัว |
stripe_key |
CRITICAL | sk_live_, sk_test_, pk_live_, pk_test_ ตามด้วยตัวอักษร 20+ ตัว |
private_key |
CRITICAL | header ของ PEM private key -----BEGIN PRIVATE KEY----- |
password_assignment |
HIGH | password = "...", passwd: "..." ฯลฯ |
db_connection_string |
HIGH | URL แบบ postgres://, mysql://, mongodb://, redis:// |
slack_token |
HIGH | xoxb-, xoxp-, xoxo-, xoxr-, xoxs- ตามด้วย token |
generic_api_key |
HIGH | api_key = "...", secret_key = "...", auth_token = "..." |
การใช้งานโดยตรง¶
from openjarvis.security.scanner import SecretScanner
scanner = SecretScanner()
# Scan text
result = scanner.scan("My key is sk-abc123xyz789 and it is secret")
print(result.clean) # False
print(result.highest_threat) # ThreatLevel.CRITICAL
for finding in result.findings:
print(f" {finding.pattern_name}: {finding.description} at [{finding.start}:{finding.end}]")
# Redact text
clean = scanner.redact("Token: sk-abc123xyz789")
print(clean) # Token: [REDACTED:openai_key]
PIIScanner¶
PIIScanner ตรวจจับข้อมูลระบุตัวตนส่วนบุคคลด้วย regex pattern ที่ปรับให้
เหมาะกับรูปแบบทั่วไปของสหรัฐฯ
เอกสารอ้างอิง Pattern¶
| ชื่อ Pattern | ระดับภัยคุกคาม | สิ่งที่ตรงกัน |
|---|---|---|
us_ssn |
CRITICAL | เลขประกันสังคมรูปแบบ XXX-XX-XXXX |
credit_card_visa |
CRITICAL | หมายเลขบัตร Visa (16 หลัก ขึ้นต้นด้วย 4) |
credit_card_mastercard |
CRITICAL | หมายเลขบัตร Mastercard (16 หลัก ขึ้นต้นด้วย 51–55) |
credit_card_amex |
CRITICAL | หมายเลขบัตร Amex (15 หลัก ขึ้นต้นด้วย 34 หรือ 37) |
email |
MEDIUM | ที่อยู่อีเมลมาตรฐาน |
us_phone |
MEDIUM | เบอร์โทรศัพท์สหรัฐฯ ในรูปแบบทั่วไป |
ipv4_public |
LOW | ที่อยู่ IPv4 สาธารณะ (ไม่รวมช่วง RFC1918) |
ที่อยู่ IP แบบส่วนตัว
Pattern ipv4_public ตั้งใจไม่รวมช่วงส่วนตัว (10.x.x.x, 172.16–31.x.x, 192.168.x.x, 127.x.x.x) ที่อยู่ IP ภายในไม่ถูกจัดว่า sensitive เป็นค่าเริ่มต้น
การใช้งานโดยตรง¶
from openjarvis.security.scanner import PIIScanner
scanner = PIIScanner()
text = "Contact john@example.com or call 555-867-5309"
result = scanner.scan(text)
for finding in result.findings:
print(f"{finding.pattern_name}: threat={finding.threat_level.value}")
# email: threat=medium
# us_phone: threat=medium
clean = scanner.redact(text)
print(clean)
# Contact [REDACTED:email] or call [REDACTED:us_phone]
File Policy¶
โมดูล file policy ป้องกันการเข้าถึงไฟล์ credential และ key มันถูกใช้ภายใน
โดย FileReadTool และเส้นทางการนำเข้าของ memory แต่คุณใช้มันโดยตรงก็ได้
Pattern ไฟล์ที่ Sensitive¶
frozenset DEFAULT_SENSITIVE_PATTERNS มี glob pattern ต่อไปนี้:
| Pattern | คำอธิบาย |
|---|---|
.env, .env.*, *.env |
ไฟล์ environment variable |
.secret, *.secrets |
ไฟล์ secret ทั่วไป |
credentials.* |
ไฟล์ credential |
*.pem, *.key |
ใบรับรอง TLS/SSL และ private key |
*.p12, *.pfx, *.jks |
ไฟล์ PKCS และ Java keystore |
id_rsa, id_ed25519 |
ไฟล์ SSH private key |
.htpasswd |
ไฟล์ password ของ Apache |
.pgpass |
ไฟล์ password ของ PostgreSQL |
.netrc |
ไฟล์ credential ของ FTP/SSH |
การใช้งาน¶
from pathlib import Path
from openjarvis.security.file_policy import is_sensitive_file, filter_sensitive_paths
# Check a single file
print(is_sensitive_file(".env")) # True
print(is_sensitive_file("server.key")) # True
print(is_sensitive_file("README.md")) # False
# Filter a list of paths
paths = [
Path("README.md"),
Path(".env"),
Path("src/main.py"),
Path("server.pem"),
]
safe = filter_sensitive_paths(paths)
print(safe) # [PosixPath('README.md'), PosixPath('src/main.py')]
การผสานกับ FileReadTool¶
FileReadTool ในตัวเรียก is_sensitive_file() โดยอัตโนมัติก่อนอ่าน path
ใดๆ ความพยายามอ่านไฟล์ sensitive จะสร้าง error แทนที่จะคืนเนื้อหาไฟล์
พฤติกรรมนี้ปิดไม่ได้ในระดับ tool — ตั้งค่าเอเจนต์ให้ไม่มี FileReadTool
หากคุณต้องการการเข้าถึงไฟล์แบบไม่จำกัด
Audit Logger¶
AuditLogger บันทึก security event ลงฐานข้อมูล SQLite แบบ append-only มัน
subscribe event bus เพื่อจับ event โดยอัตโนมัติได้ หรือคุณเรียก log() ด้วย
ตนเองก็ได้
การผสานกับ Event Bus (อัตโนมัติ)¶
from openjarvis.core.events import EventBus
from openjarvis.security.audit import AuditLogger
from openjarvis.security.guardrails import GuardrailsEngine
from openjarvis.security.types import RedactionMode
from openjarvis.engine.ollama import OllamaEngine
bus = EventBus()
# AuditLogger subscribes to SECURITY_SCAN, SECURITY_ALERT, SECURITY_BLOCK
audit = AuditLogger(db_path="~/.openjarvis/audit.db", bus=bus)
engine = OllamaEngine()
guarded = GuardrailsEngine(
engine,
mode=RedactionMode.WARN,
bus=bus,
)
# Security events are now persisted automatically
การบันทึกด้วยตนเอง¶
import time
from openjarvis.security.audit import AuditLogger
from openjarvis.security.types import SecurityEvent, SecurityEventType
audit = AuditLogger(db_path="./audit.db")
event = SecurityEvent(
event_type=SecurityEventType.SECRET_DETECTED,
timestamp=time.time(),
findings=[],
content_preview="sk-...",
action_taken="redacted",
)
audit.log(event)
การ Query Audit Log¶
from openjarvis.security.audit import AuditLogger
audit = AuditLogger(db_path="~/.openjarvis/audit.db")
# Recent events
events = audit.query(limit=20)
# Filter by event type
secret_events = audit.query(event_type="secret_detected")
# Filter by time range
import time
recent = audit.query(since=time.time() - 3600) # last hour
# Count total events
print(f"Total events: {audit.count()}")
audit.close()
Configuration¶
การตั้งค่าความปลอดภัยอยู่ในหมวด [security] ของ ~/.openjarvis/config.toml
[security]
enabled = true
scan_input = true
scan_output = true
mode = "warn" # "warn" | "redact" | "block"
secret_scanner = true
pii_scanner = true
audit_log_path = "~/.openjarvis/audit.db"
enforce_tool_confirmation = true
เอกสารอ้างอิง Configuration¶
| คีย์ | ชนิด | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
enabled |
bool |
true |
เปิดใช้งานระบบย่อยความปลอดภัย |
scan_input |
bool |
true |
สแกนข้อความ input ของผู้ใช้ |
scan_output |
bool |
true |
สแกนเนื้อหา output ของโมเดล |
mode |
str |
"warn" |
การกระทำเมื่อพบสิ่งที่ตรงกัน: warn, redact หรือ block |
secret_scanner |
bool |
true |
รัน SecretScanner บนข้อความทั้งหมด |
pii_scanner |
bool |
true |
รัน PIIScanner บนข้อความทั้งหมด |
audit_log_path |
str |
~/.openjarvis/audit.db |
Path ของ audit log แบบ SQLite |
enforce_tool_confirmation |
bool |
true |
config loader ยอมรับค่านี้ได้แต่ ยังไม่บังคับใช้จริงในตอนนี้ ดู System Access ว่าเมื่อไหร่ prompt จะเกิดขึ้นจริง |
เริ่มจาก Warn แล้วค่อยเข้มงวดขึ้น
mode = "warn" เป็นจุดเริ่มต้นที่ดี ช่วยให้คุณสังเกตว่า pattern ไหนถูก trigger โดยไม่รบกวนการใช้งานปกติ สลับไปเป็น "redact" เมื่อคุณมั่นใจแล้วว่า scanner ไม่ได้สร้าง false positive มากเกินไปสำหรับงานของคุณ
เขียน Scanner กำหนดเอง¶
Implement BaseScanner แล้วส่ง instance ให้ GuardrailsEngine:
import re
from openjarvis.security._stubs import BaseScanner
from openjarvis.security.types import ScanFinding, ScanResult, ThreatLevel
class InternalUrlScanner(BaseScanner):
"""Detect internal service URLs that should not be shared externally."""
scanner_id = "internal_urls"
PATTERN = re.compile(r"https?://internal\.[a-z0-9.-]+\.[a-z]{2,}")
def scan(self, text: str) -> ScanResult:
findings = []
for match in self.PATTERN.finditer(text):
findings.append(ScanFinding(
pattern_name="internal_url",
matched_text=match.group(0),
threat_level=ThreatLevel.MEDIUM,
start=match.start(),
end=match.end(),
description="Internal service URL",
))
return ScanResult(findings=findings)
def redact(self, text: str) -> str:
return self.PATTERN.sub("[REDACTED:internal_url]", text)
# Use with GuardrailsEngine
from openjarvis.security.guardrails import GuardrailsEngine
from openjarvis.security.types import RedactionMode
guarded = GuardrailsEngine(
engine,
scanners=[InternalUrlScanner()],
mode=RedactionMode.REDACT,
)
Data Boundary Scan¶
ดู Data Boundary Scan สำหรับเครื่องมือวินิจฉัย config
ของแอปพลิเคชัน (jarvis scan --data-boundaries)
ดูเพิ่มเติม¶
- Data Boundary Scan — เครื่องมือวินิจฉัย config ของแอปพลิเคชันและที่เก็บข้อมูลในเครื่อง (
jarvis scan --data-boundaries) - Architecture: Security — การออกแบบ pipeline, การไหลของ event และการผสาน file policy
- API Reference: Security — signature ของคลาสและฟังก์ชันแบบเต็ม
- Tools —
FileReadToolใช้ file policy อย่างไร - Configuration — เอกสารอ้างอิง config แบบเต็ม