Skip to content

Privacy Configs and Skill Privacy Extraction/Restore ​

This page explains OpenViking privacy configs and how they work with skill write-time extraction and read-time restore.

Goal ​

Privacy configs separate sensitive values (such as api_key, token, base_url) from skill body content to reduce plaintext storage of recognized values in SKILL.md, with version lookup and switching. Extraction relies on a model and replaces only recognized, matched spans; it does not guarantee discovery of every sensitive value.

Core goals:

  • Auto-extract sensitive values at write time and replace with placeholders
  • Auto-restore values from the active config at read time
  • Provide version query, switching, and auditability

Core Objects and Storage Layout ​

Privacy configs are keyed by category + target_key.

  • category: config category (currently skill uses skill)
  • target_key: target identifier (usually the skill name)

Storage layout in user space:

viking://user/{user_id}/privacy/{category}/{target_key}/
├── .meta.json                 # metadata (active_version/latest_version/labels, etc.)
├── current.json               # active version snapshot
└── history/
    ├── version_1.json
    ├── version_2.json
    └── ...

current.json and history/version_x.json are full values snapshots.


Version Semantics ​

upsert ​

  • Each upsert treats incoming values as the candidate snapshot
  • If values is identical to current values, no new version is created
  • Otherwise a new version is created and activated
  • New keys are allowed (no unknown-key rejection)
  • values is a full snapshot, not a patch; omitted old keys are absent from the new version

activate ​

  • Sets a historical version as active (writes back to current.json)
  • Updates active_version in .meta.json
  • Does not rotate or revoke credentials at an external service; it only selects the values OpenViking uses

Skill Privacy Extraction (Write Path) ​

When adding a skill via add_skill, OpenViking runs extraction + placeholderization.

add_skill
  -> SkillProcessor._sanitize_skill_privacy
    -> extract_skill_privacy_values (LLM extracts values)
    -> placeholderize_skill_content_with_blocks (replace plaintext with placeholders)
    -> privacy.upsert(category="skill", target_key=skill_name, values=...)
  -> write placeholderized SKILL.md

Key Points ​

  1. Extraction source: model returns JSON; values is used as privacy key-value pairs.
  2. Content replacement: matched plaintext is replaced by placeholders:
    • {{ov_privacy:skill:{skill_name}:{field_name}}}
  3. Block mapping captured:
    • original_content_blocks
    • replacement_content_blocks
  4. Persisted content: matched spans become placeholders; unmatched content remains unchanged. This processes the skill body, not every attached file.

Skill Restore on Read (Load Path) ​

When reading SKILL.md, FSService.read attempts placeholder restoration automatically.

fs.read(uri)
  -> get_skill_name_from_uri(uri)
  -> privacy.get_current(category="skill", target_key=skill_name)
  -> restore_skill_content(content, skill_name, current.values)

URI Matching ​

Current matching is suffix-based: /skills/{name}/SKILL.md, so it supports user-scoped skill paths such as:

  • viking://~/skills/{name}/SKILL.md
  • viking://user/{user_id}/skills/{name}/SKILL.md

restore Rules ​

restore_skill_content behavior:

  1. Placeholder exists in content and value exists and is non-empty
    -> replace directly.

  2. Placeholder exists in content but value is missing/empty
    -> keep placeholder; add item to unresolved_entries.

  3. Config key is non-empty but not referenced by any placeholder in content
    -> add to extra-config notice (Configured but not referenced in content).

  4. If unresolved_entries or extra-config entries exist, append notice block:

    • [Privacy Config Notice]
    • Missing config: ... (if any)
    • Configured but not referenced in content: ... (if any)

Current implementation only runs restore when a current privacy config exists for that skill. If no current config exists, no notice is appended.


read returns restored values, and extra-config notices also include values. Privacy APIs and historical snapshots can return full values as well. This feature does not redact responses or encrypt storage; see Storage Encryption for encryption.

Relationship with CLI/API ​

  • Management plane: Privacy API/CLI for versions (query, write, rollback)
  • Content plane: read automatically restores placeholders
  • Separation of concerns: content files hold placeholders; privacy service holds sensitive values and versions

PrivacySanitizer does not infer sensitivity from field names. String values in the active Privacy catalog participate in automatic content scanning and reference replacement only when they contain at least 6 Unicode characters. Shorter values can still be stored, referenced, and explicitly resolved, but are excluded from global substring replacement to reduce false positives in ordinary text. Python owns catalog loading, reference generation, and cache policy; matcher construction, leftmost-longest matching, and text rewriting run in RAGFS's Rust Aho-Corasick implementation through a reusable PyO3 matcher that releases the GIL while scanning. The matcher catalog is cached per user and process-local revision; local changes invalidate it immediately, and a 3600-second TTL bounds staleness after changes made by another worker or node.

Using a GitHub Personal Access Token (PAT) as an example:

bash
# Create or rotate the PAT; credential/github is the target and pat is the field
ov privacy upsert credential github --field-pat 'github_pat_...'

# Inspect current targets and fields without exposing values
ov privacy tree --category credential --references

# Omit the field when pat is the target's only field
ov privacy reference credential github
ov privacy resolve credential github

# Equivalent explicit-field forms
ov privacy reference credential github pat
ov privacy resolve credential github pat
ov privacy resolve '{{ov_privacy:alice:viking://~/privacy/credential/github#/values/pat}}'

# Inspect/activate history, or remove pat (the final field also removes the target)
ov privacy versions credential github
ov privacy activate credential github 1
ov privacy delete credential github pat --yes

# Scan one explicit URI; the default only reports, while --fix replaces plaintext PATs
ov privacy scan --uri viking://~/resources/project-a

Benefits ​

  • Reduces plaintext sensitive data exposure in skill content
  • Version history supports configuration changes after external credential rotation
  • Transparent to callers: read returns restored executable skill text
  • Notice block improves troubleshooting when config is incomplete or over-provisioned

Open source under the AGPL-3.0 License. Font licenses