Security Architecture¶
Attune's security surface is a set of composable primitives in
attune.security (re-exported from attune.memory.security) plus an
Agent-SDK-backed SecurityAuditWorkflow that scans a codebase. This
page describes what actually ships in the source tree.
Overview¶
The security module exposes four primitive families and one workflow:
- PII scrubbing —
PIIScrubber,PIIPattern,PIIDetection. - Secrets detection —
SecretsDetector,detect_secrets(),SecretType,SecretDetection. - Audit logging —
AuditLogger,AuditEvent,SecurityViolation,Severity. - Path validation —
_validate_file_path()(private helper used by the CLI to block path traversal). - Workflow —
SecurityAuditWorkflowruns a multi-subagent audit against a directory or file.
All public symbols are importable from either
attune.security or attune.memory.security — the former re-exports
the latter for convenience.
from attune.security import (
PIIScrubber,
SecretsDetector,
AuditLogger,
AuditEvent,
SecurityViolation,
Severity,
detect_secrets,
)
PII Protection¶
PIIScrubber detects PII in text and returns a tuple of
(sanitized_text, detections). Each detection is a PIIDetection
dataclass with the matched text, position, replacement token, and
confidence.
Built-in patterns¶
PIIScrubber._init_default_patterns() ships nine patterns:
| Pattern name | Replacement | Notes |
|---|---|---|
email |
[EMAIL] |
RFC 5322 simplified |
ssn |
[SSN] |
Excludes invalid area numbers (000/666/9xx) |
phone |
[PHONE] |
US + international formats |
credit_card |
[CC] |
Visa / MC / Amex / Discover |
ipv4 |
[IP] |
Octet-validated |
ipv6 |
[IP] |
Simplified IPv6 |
address |
[ADDRESS] |
US street address heuristic |
name |
[NAME] |
Title-prefixed names; disabled by default (high false-positive rate) |
mrn |
[MRN] |
Medical Record Number |
patient_id |
[PATIENT_ID] |
Patient identifier (Patient ID: / PID-) |
Usage¶
from attune.security import PIIScrubber
scrubber = PIIScrubber() # name detection disabled by default
text = "Email user@example.com or call 555-123-4567 (MRN: 1234567)."
sanitized, detections = scrubber.scrub(text)
print(sanitized)
# "Email [EMAIL] or call [PHONE] (MRN: [MRN])."
for d in detections:
print(d.pii_type, d.replacement, d.confidence)
PIIDetection.to_audit_safe_dict() returns a dict that omits the
matched value — safe to forward to an audit log.
Custom patterns¶
scrubber.add_custom_pattern(
name="employee_id",
pattern=r"EMP-\d{6}",
replacement="[EMPLOYEE_ID]",
confidence=1.0,
description="Company employee identifier",
)
Default patterns can be disabled with disable_pattern(name) and
re-enabled with enable_pattern(name). Custom patterns are removed
with remove_custom_pattern(name).
Secrets Detection¶
SecretsDetector scans content for hardcoded credentials using
compiled regex patterns plus optional Shannon-entropy fallback. The
detector never stores the matched secret value — only metadata
(type, severity, line/column, redacted context snippet).
Supported secret types¶
From SecretType (see secrets_types.py):
- API keys:
ANTHROPIC_API_KEY,OPENAI_API_KEY,AWS_ACCESS_KEY,AWS_SECRET_KEY,GITHUB_TOKEN,SLACK_TOKEN,STRIPE_KEY,GENERIC_API_KEY - Passwords:
PASSWORD,BASIC_AUTH - Private keys:
RSA_PRIVATE_KEY,SSH_PRIVATE_KEY,EC_PRIVATE_KEY,PGP_PRIVATE_KEY,TLS_CERTIFICATE_KEY - Tokens:
JWT_TOKEN,OAUTH_TOKEN,BEARER_TOKEN - Databases:
DATABASE_URL,CONNECTION_STRING - Heuristic:
HIGH_ENTROPY_STRING
Severity model¶
Severity is an enum with four levels:
| Level | Typical members |
|---|---|
| CRITICAL | Private keys, AWS access/secret keys |
| HIGH | API keys (Anthropic, OpenAI, GitHub, Slack, Stripe), passwords, database URLs |
| MEDIUM | OAuth tokens, JWT tokens, bearer tokens |
| LOW | High-entropy strings (heuristic catch) |
Usage¶
from attune.security import SecretsDetector, detect_secrets
# One-shot:
detections = detect_secrets(source_code)
# Reusable instance with custom thresholds:
detector = SecretsDetector(
enable_entropy_analysis=True,
entropy_threshold=4.5,
min_entropy_length=20,
)
detections = detector.detect(source_code)
for d in detections:
print(
d.secret_type.value,
d.severity.value,
f"line {d.line_number} col {d.column_start}",
d.context_snippet, # secret value replaced with [REDACTED]
)
SecretDetection.to_dict() is JSON-safe. Custom patterns are added
via detector.add_custom_pattern(name, pattern, severity) where
severity is "critical" | "high" | "medium" | "low".
Audit Logging¶
AuditLogger writes append-only JSON-Lines (.jsonl) events to a
log directory, with optional size-based rotation and age-based
cleanup. The constructor accepts:
AuditLogger(
log_dir=None, # defaults to platform_utils.get_default_log_dir()
log_filename="audit.jsonl",
max_file_size_mb=100,
retention_days=365,
enable_rotation=True,
enable_console_logging=False,
)
The log directory is created with 0o700 permissions; on init
failure it falls back to ./logs.
Event types¶
Logging methods on AuditLogger (mixed in from
AuditLogMethodsMixin):
log_llm_request(user_id, empathy_level, provider, model, memory_sources, pii_count=0, secrets_count=0, ...)— records an LLM API call with per-request size, duration, and PII/secret counts.log_pattern_store(user_id, pattern_id, pattern_type, classification, pii_scrubbed=0, secrets_detected=0, ...)— records storage of a MemDocs pattern.log_pattern_retrieve(user_id, pattern_id, classification, access_granted=True, ...)— records pattern retrieval and unauthorized-access attempts.log_security_violation(user_id, violation_type, severity, details, ...)— records a security policy violation.
log_llm_request and log_pattern_store automatically escalate to
log_security_violation when secrets_count > 0 or when sensitive
classifications are stored unencrypted.
Event shape¶
Every event is an AuditEvent dataclass:
AuditEvent(
event_id="evt_<12-hex>", # auto-generated
timestamp="<UTC ISO-8601>", # auto-generated
version="1.0",
event_type="llm_request", # or store_pattern / retrieve_pattern / security_violation
user_id="...",
session_id="...",
status="success", # or failed / blocked
error="",
data={...}, # event-specific fields
)
to_dict() flattens data into the top level for easier querying.
Querying logs¶
AuditLogger.query() (from AuditQueryMixin) supports filtering by
event_type, user_id, status, date range, and arbitrary
field/value filters:
events = logger.query(
event_type="llm_request",
user_id="user@example.com",
start_date=datetime(2026, 1, 1),
end_date=datetime(2026, 1, 31),
limit=1000,
)
Path Validation¶
Internal API. The
_validate_file_pathhelper has a leading underscore — treat it as internal. The signature and behavior may change between releases. It is documented here because it is the single source of truth for path-traversal defense across the codebase; prefer calling it via the higher-level workflows rather than depending on its signature directly.
attune.security.path_validation._validate_file_path(path, allowed_dir=None)
is the internal guard the CLI uses to block path-traversal attacks
(CWE-22) before running workflows against user-supplied paths.
- Rejects empty paths, non-string paths, and paths containing null bytes.
- Resolves the path with
Path.resolve()and (optionally) asserts it lies underallowed_dir. - Refuses to operate on platform system directories
(e.g.
/etc,/private/etc,/usr/bin,/binon Unix;C:\Windows\System32,Program Fileson Windows).
The leading underscore is intentional: this is a private primitive
used internally by attune.cli_commands.workflow_commands.cmd_workflow_run,
not a public API. Callers outside the CLI should not import it.
SecurityAuditWorkflow¶
SecurityAuditWorkflow (slug: security-audit) is the public,
SDK-native security audit. It delegates to four Claude Agent SDK
subagents and synthesizes their findings into a WorkflowResult:
- vuln-scanner — injection flaws,
eval/exec, XSS, path traversal, command injection, insecure deserialization. - secret-detector — hardcoded credentials, API keys, tokens, private keys, database credentials.
- auth-reviewer — missing auth checks, broken access control, insecure session management, privilege-escalation risks.
- remediation-planner — prioritized fix plan grouped by effort.
Run from the CLI¶
attune workflow run security-audit --path src/
attune workflow run security-audit --path src/ --depth deep
Supported --depth values are quick, standard (default), and
deep. Depth maps to a max_turns budget for the orchestrator
(10 / 20 / 40 respectively) and engages extended thinking on deep.
Run from Python¶
import asyncio
from attune.workflows.security_audit import SecurityAuditWorkflow
async def run_audit():
workflow = SecurityAuditWorkflow()
result = await workflow.execute(path="src/", depth="standard")
print(result.final_output)
print(result.summary)
asyncio.run(run_audit())
Output¶
The orchestrator's synthesis covers three sections —
Summary (a security score 0–100 + 2-3 sentence executive
summary), Security (consolidated findings by severity), and
Suggestions (prioritized remediation). Per-subagent transcripts
are recovered separately and appended under
## Subagent findings so the orchestrator's summary cannot silently
drop their detail.
The returned WorkflowResult carries final_output, summary,
success, stages, cost_report, metadata (with the resolved
path, depth, and subagent transcripts), plus optional error /
error_type / transient fields when the SDK call fails.
See also¶
- Wizards — the wizard runtime; the
securitybuiltin delegates toSecurityAuditWorkflow. - CLI Reference —
attune workflow run security-auditinvocation and flags.