Skip to content

Governance controls

ZettelForge applies four layered controls every time content enters memory: a content size limit, a prompt injection check, optional PII detection, and a write-time anomaly gate. All four are configured under the governance block in config.yaml.

Control order at remember() time

When you call mm.remember(content), the controls run in this sequence:

  1. Content size check — rejects content that exceeds governance.limits.max_content_length.
  2. Prompt injection block — rejects content that contains high-confidence instruction-injection patterns.
  3. PII detection (disabled by default) — logs, redacts, or blocks content containing personally identifiable information.
  4. Memory anomaly gate (audit mode by default) — scores the candidate note against recent trusted notes; blocks or quarantines when the anomaly score exceeds the calibration threshold.

An exception in any step halts storage. The content never reaches the backend store unless all enabled controls pass.


Config reference

All keys live under the governance top-level block in config.yaml.

Top-level keys

Key Default Description
enabled true Master switch for governance controls. Setting to false disables structural validation; PII and defense have their own switches.
min_content_length 1 Minimum byte length for stored content. Content shorter than this raises GovernanceViolationError.

Environment override: none (use config file).


governance.limits — operation limits

DoS mitigation. Values of 0 disable the corresponding limit.

Key Default Env var Description
max_content_length 52428800 (50 MB) ZETTELFORGE_LIMITS_MAX_CONTENT_LENGTH Maximum byte length of content passed to remember(). Content exceeding this raises GovernanceViolationError.
recall_timeout_seconds 30.0 ZETTELFORGE_LIMITS_RECALL_TIMEOUT Wall-clock timeout for recall() queries. 0 means no timeout.

governance.pii — PII detection

Disabled by default. Requires pip install zettelforge[pii] (Microsoft Presidio + spaCy). When the package is not installed, ZettelForge logs a warning at startup and continues without PII enforcement — no core functionality breaks.

Key Default Env var Description
enabled false ZETTELFORGE_PII_ENABLED Enable PII detection before remember().
action "log" ZETTELFORGE_PII_ACTION What to do when PII is detected: log (warn, pass through), redact (replace with placeholder), block (raise before storage).
redact_placeholder "[REDACTED]" Replacement text for redacted PII spans. Used only when action=redact.
entities [] (all) Presidio entity types to detect. Empty list detects all supported types minus the CTI allowlist.
language "en" Language code passed to Presidio.
nlp_model "en_core_web_sm" spaCy model used by Presidio. Downloaded automatically on first use.

CTI allowlist. IP addresses (IP_ADDRESS), URLs (URL), and domain names (DOMAIN_NAME) are excluded from PII detection by default. These are legitimate threat indicators, not personally identifiable information. The allowlist applies when entities is empty (detect-all mode). When you specify an explicit entities list, only the listed types are detected and the allowlist is not applied.

Actions:

  • log — Presidio detects PII, logs entity types and confidence scores (never the actual text), and passes content through to storage unchanged.
  • redact — Detected PII spans are replaced with the redact_placeholder before storage.
  • block — Any PII detection raises GovernanceViolationError and content is not stored.

governance.memory_defense — write-time anomaly detection

A MemSAD-style anomaly gate that scores each candidate note against recent trusted notes before writing. Default mode is audit: anomalies are logged but writes proceed. Switch to block or quarantine once you have a clean calibration corpus.

Key Default Env var Description
enabled true ZETTELFORGE_MEMORY_DEFENSE_ENABLED Enable the anomaly gate.
mode "audit" ZETTELFORGE_MEMORY_DEFENSE_MODE audit logs anomalies; block raises MemoryAnomalyError; quarantine writes the note to a JSONL file and then raises.
min_calibration_notes 50 ZETTELFORGE_MEMORY_DEFENSE_MIN_CALIBRATION Minimum number of reference notes required to compute the calibration baseline. Below this count the gate passes all writes through with reason calibration_insufficient.
max_reference_notes 50 Maximum reference notes used per evaluation. Ordered by most recently created.
kappa 2.0 ZETTELFORGE_MEMORY_DEFENSE_KAPPA Sensitivity multiplier. Threshold = calibration mean + (kappa × calibration std dev). Lower kappa = more sensitive.
lexical_weight 0.25 Weight of the lexical divergence term in the composite score.
ngram_size 3 Character n-gram size for the lexical Jensen-Shannon divergence term.
monitored_domains [] (all) CTI domains to monitor. Empty list monitors all domains. Notes with a domain not in this list pass through without evaluation.
quarantine_path "" Path to the quarantine JSONL file. Default: <data_dir>/quarantine/memory_anomalies.jsonl.
quarantine_raw_content true Include raw note content in quarantine records. Set to false to omit content from quarantine logs.

Anomaly scoring

The gate computes a composite score for each candidate note:

memsad_score = 0.5 × max_cosine_similarity(candidate, refs)
             + 0.5 × mean_cosine_similarity(candidate, refs)

lexical_jsd  = Jensen-Shannon divergence over character n-grams
             (candidate vs. pooled reference corpus)

score = memsad_score + (lexical_weight × lexical_jsd)

The threshold is calibrated leave-one-out from the reference set:

calibration_scores = [score(note_i, all_others) for note_i in reference_notes]
threshold = mean(calibration_scores) + kappa × std_dev(calibration_scores)

A note is flagged when score > threshold. In audit mode the gate logs the decision and allows the write. In block or quarantine mode it raises MemoryAnomalyError.

Cosine similarity uses the 768-dimension fastembed embeddings stored in the note's embedding vector. Notes without a valid embedding vector bypass scoring with reason candidate_embedding_unavailable.

Short-circuit conditions

The gate returns action=audit, flagged=False without scoring when:

Condition Reason field
enabled=false disabled
Domain not in monitored_domains (list non-empty) domain_not_monitored
Candidate embedding vector missing or all-zero candidate_embedding_unavailable
Fewer than min_calibration_notes valid reference notes calibration_insufficient
Leave-one-out calibration produces no scores calibration_unscorable

Prompt injection detection

Every call to remember() runs require_no_prompt_injection(content) before content reaches the backend. Detection is deterministic regex-based matching — no LLM call. The check is always active when governance.enabled=true and has no separate config switch.

Seven pattern categories are detected, all at high severity:

Category What it matches
direct_instruction_override "ignore/disregard/override/bypass … previous/system instructions"
role_takeover "you are now / act as / pretend to be … system/admin/root"
system_prompt_exfiltration "reveal/print/leak/dump … system/hidden prompt"
secret_exfiltration "send/exfiltrate … api_key/token/secret/password" (within 80 chars)
tool_instruction_smuggling "call/invoke/use/execute the tool/function/api/mcp/shell"
retrieval_poisoning "when this note is retrieved …" or "always/only answer/respond/say"
role_delimiter_smuggling Lines starting with system: / developer:, XML role tags, BEGIN_SYSTEM_PROMPT

Any high-severity match raises PromptInjectionError, which GovernanceValidator converts to GovernanceViolationError. Content is not stored.


Exceptions

Exception Module When raised
GovernanceViolationError zettelforge.governance_validator Content size exceeded; structural validation failed; prompt injection detected; PII blocked (action=block).
PromptInjectionError zettelforge.prompt_security High-confidence injection pattern detected. Converted to GovernanceViolationError by the validator.
MemoryAnomalyError zettelforge.memory_defense Anomaly gate fires with mode=block or mode=quarantine. Contains a decision attribute with full MemoryAnomalyDecision fields.

Minimal hardening config

Enable PII redaction and switch the anomaly gate to block mode once your store has 50+ calibration notes:

governance:
  limits:
    max_content_length: 1048576  # 1 MB per note
    recall_timeout_seconds: 15.0
  pii:
    enabled: true
    action: redact
  memory_defense:
    enabled: true
    mode: block
    kappa: 2.0

Or with environment variables:

export ZETTELFORGE_PII_ENABLED=true
export ZETTELFORGE_PII_ACTION=redact
export ZETTELFORGE_MEMORY_DEFENSE_MODE=block
export ZETTELFORGE_LIMITS_MAX_CONTENT_LENGTH=1048576

Do not switch memory_defense.mode to block before you have at least min_calibration_notes notes in the store. Before that threshold the gate always passes writes through and produces no calibration baseline.