Skip to content

08 · Security & Monitoring ​

Eighth installment of the architecture deep-dive: tool approval gates, sensitive information detection, encrypted storage, authentication, Monitor / Daily Monitor, and Cron / Schedule / Todo / Reminder. Corresponds to ARCHITECTURE.md §9–§10.


§9.1 Security Model ​

9.1.1 Risk Levels ​

src/tools/security.py defines tool risk levels:

python
_LEVELS = {"low": 0, "medium": 1, "high": 2, "super": 99}
LevelValueDescriptionApproval Rule
low0Read-only / harmlessNo approval
medium1Write files / editApproved by default
high2Execute code / delete / networkMust be approved
super99Extremely high riskAlways approved (user threshold cannot override)

9.1.2 User Threshold ​

python
"low"    → Approve all tools (low + medium + high)
"medium" → Approve medium + high    ← Default
"high"   → Approve only high        ← Most permissive

FAMILIARS_APPROVAL_DISABLED=1 → Completely skip the approval gate (for unattended batch runs).

9.1.3 Score-based Dynamic Escalation ​

Each tool has a base score, plus additive points for PII/credential types found in tool arguments:

[0, 30)  → "low"
[30, 60) → "medium"
[60, 90) → "high"
90+      → "super"

Tool Base Scores ​

python
TOOL_BASE_SCORE: dict[str, int] = {
    "write":                45,   # medium; content can escalate
    "edit":                 40,
    "write_csv":            40,
    "write_memory":         40,
    "delete_file":          65,   # high
    "move_or_rename":       65,
    "download":             65,
    "execute_shell_script": 70,
    "execute_python_file":  70,
    "execute_node_file":    70,
    "run_pip":              70,
    "run_npm":              70,
    "run_git":              65,
    "ssh_exec":             70,
    "sqlite_query":         65,
    "http_request":         45,
    "scp_upload":           65,
    "send_email":           65,
    "social_send":          65,
    "keyboard_type":        65,
    "browser_type":         65,
}

Sensitive Argument Bonuses ​

python
SENSITIVE_ARG_SCORE: dict[str, int] = {
    "aws-access-key":       50,
    "aws-secret-key":       50,
    "github-pat":           50,
    "github-fine-grained":  50,
    "openai-key":           50,
    "anthropic-key":        50,
    "google-api-key":       50,
    "slack-token":          40,
    "private-key-pem":      50,
    # ...
}

Example: write(path, content) containing a bank card number → 45 + 45 = 90 → escalated to super.

9.1.4 requires_approval Wrapper ​

python
# src/tools/security.py
def requires_approval(func):
    """Decorator that wraps tool methods.
    
    1. Look up UserConfig.security_level → compare with tool risk
    2. tool_risk >= user_threshold → send ApprovalRequest to CommHub
    3. Block waiting for user approval (with timeout)
    4. Approved → execute original method; rejected/timeout → PermissionError
    """

§9.2 Sensitive Information Detection (src/tools/sensitive_detector.py) ​

9.2.1 Three-Layer Detection ​

Layer 1: gitleaks  → High-confidence regex/entropy detection (credentials, tokens, keys)
Layer 2: presidio  → Microsoft-Presidio PII (PERSON, EMAIL, PHONE, IP, ...)
Layer 3: mini-LLM  → Last-resort fallback (project codenames, custom IDs, employer names)

9.2.2 Finding Data Structure ​

python
@dataclass
class Finding:
    value: str      # Literal substring (plaintext, never logged)
    start: int
    end: int
    kind: str       # "credential" | "pii" | "custom"
    rule: str       # Detection rule id
    layer: str      # "gitleaks" | "presidio" | "mini_llm"
    score: float = 1.0

9.2.3 Gitleaks Fallback Regex ​

python
_GITLEAKS_FALLBACK: list[tuple[str, re.Pattern]] = [
    ("aws-access-key",       re.compile(r"\bAKIA[0-9A-Z]{16}\b")),
    ("aws-secret-key",       re.compile(r"(?i)aws[^\s'\"]{0,20}['\"][0-9a-zA-Z/+]{40}['\"]")),
    ("github-pat",           re.compile(r"\bghp_[A-Za-z0-9]{36}\b")),
    ("openai-key",           re.compile(r"\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b")),
    ("anthropic-key",        re.compile(r"\bsk-ant-[A-Za-z0-9_-]{20,}\b")),
    ("slack-token",          re.compile(r"\bxox[abprs]-[A-Za-z0-9-]{10,}\b")),
    ("google-api-key",       re.compile(r"\bAIza[0-9A-Za-z_-]{35}\b")),
    ("private-key-pem",      re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----...")),
    ("jwt",                  re.compile(r"\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b")),
    # ...
]

9.2.4 Submission Flow ​

After detection, each finding is paired with 1-2 sentences of context → named by the mini model (key + type + aliases) → encrypted as KV → stored in workspace/SECRET.json → recallable via the get_user_secret tool.

Plaintext is never preserved in logs or persistent storage.

§9.3 Encrypted Storage (src/base/crypto.py) ​

hostname + username + salt_bytes
    → HKDF (SHA-256, info=b"familiars-secrets-v1")
    → 32-byte Fernet key
    → AES-128-CBC + HMAC-SHA256 encryption
    → workspace/SECRET.json
python
_SALT_FILENAME = ".salt"
_KEY_INFO = b"familiars-secrets-v1"

def _derive_fernet_key() -> bytes:
    salt = _load_or_create_salt()        # os.urandom(32)
    identity = f"{platform.node()}|{os.getenv('USER', ...)}"
    hkdf = HKDF(algorithm=hashes.SHA256(), length=32, salt=salt, info=_KEY_INFO)
    raw_key = hkdf.derive(identity.encode("utf-8"))
    return base64.urlsafe_b64encode(raw_key)

Security invariants:

  • Salt file is useless without machine identity
  • Machine identity is useless without salt
  • The HKDF info string is a versioned crypto primitive → changing it invalidates all existing secrets

§9.4 Authentication (src/base/auth/) ​

src/base/auth/
├── __init__.py          # get_auth_manager()
├── app.py               # AuthManager implementation
├── manager.py           # Authentication management logic
├── models.py            # Authentication data models
└── device.py            # Device fingerprint (macOS has a real implementation; Windows only has hostname)

9.4.1 Bridge Token ​

python
# src/base/bridge_token.py
def verify_bridge_token(token: str) -> bool:
    """Verify X-Bridge-Token header or ?token= query parameter."""

# Stored in workspace/.bridge_token
# Auto-generated on first startup

9.4.2 Device Fingerprint ​

src/base/auth/device.py:

  • macOS: real implementation (Keychain / IOKit)
  • Windows: hostname fingerprint only (unstable)

§9.5 Local Monitor ​

9.5.1 Social Platform Listeners ​

PlatformImplementationModule
WeChatwcferry.Wcfsrc/utils/daily_tools.py
QQUI Automationsrc/utils/daily_tools.py
FeishuWebhooksrc/bridge/routes/feishu.py

9.5.2 Screen / Microphone ​

  • Requires OS authorization + in-app approval
  • macOS: Quartz, AppKit.NSWorkspace, ApplicationServices
  • Windows: pyautogui, uiautomation

§9.6 Daily Monitor ​

python
# src/utils/daily_monitor/__init__.py
class DailyMonitorTools:
    """Proactively collect daily context:
    - Message aggregation
    - Schedule summary
    - Web information
    → Feeds the agent to produce "today's summary / tomorrow's suggestions"
    """

§9.7 Cron / Schedule / Todo / Reminder ​

9.7.1 APScheduler Integration ​

python
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.interval import IntervalTrigger

9.7.2 Report Generation ​

  • Daily / weekly / monthly / annual reports → APScheduler cron
  • The old write_report() has been replaced by the manager_review skill
  • Triggered by per-agent dialogue-turn thresholds

9.7.3 Centralized Data ​

All scheduling data is stored in familiars.db:

TablePurpose
cron_jobsCron definitions
cron_runsCron run records
schedulesSchedules
todosTodos
remindersReminders
watch_jobsMonitor jobs

The frontend reads and writes the same dataset via REST endpoints:

  • POST /api/cron — Cron CRUD
  • POST /api/schedule — Schedule CRUD
  • POST /api/todo — Todo CRUD
  • GET /api/reminders/stream — Reminder SSE

Next: 09-Ecosystem Overview →

Familiars · 多 Agent AI 桌面应用