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:
_LEVELS = {"low": 0, "medium": 1, "high": 2, "super": 99}| Level | Value | Description | Approval Rule |
|---|---|---|---|
low | 0 | Read-only / harmless | No approval |
medium | 1 | Write files / edit | Approved by default |
high | 2 | Execute code / delete / network | Must be approved |
super | 99 | Extremely high risk | Always approved (user threshold cannot override) |
9.1.2 User Threshold
"low" → Approve all tools (low + medium + high)
"medium" → Approve medium + high ← Default
"high" → Approve only high ← Most permissiveFAMILIARS_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
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
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
# 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
@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.09.2.3 Gitleaks Fallback Regex
_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_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
# 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 startup9.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
| Platform | Implementation | Module |
|---|---|---|
wcferry.Wcf | src/utils/daily_tools.py | |
| UI Automation | src/utils/daily_tools.py | |
| Feishu | Webhook | src/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
# 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
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.interval import IntervalTrigger9.7.2 Report Generation
- Daily / weekly / monthly / annual reports → APScheduler cron
- The old
write_report()has been replaced by themanager_reviewskill - Triggered by per-agent dialogue-turn thresholds
9.7.3 Centralized Data
All scheduling data is stored in familiars.db:
| Table | Purpose |
|---|---|
cron_jobs | Cron definitions |
cron_runs | Cron run records |
schedules | Schedules |
todos | Todos |
reminders | Reminders |
watch_jobs | Monitor jobs |
The frontend reads and writes the same dataset via REST endpoints:
POST /api/cron— Cron CRUDPOST /api/schedule— Schedule CRUDPOST /api/todo— Todo CRUDGET /api/reminders/stream— Reminder SSE
Next: 09-Ecosystem Overview →
