Skip to content

[Bug] Bundle import can overwrite the audit log #166

Description

@jonathanchang31

Summary

vouch import-apply --on-conflict overwrite accepts a bundle containing audit.log.jsonl and overwrites the KB’s append-only audit log. The bundle format documents safety flags for “no proposed/, no state.db, no audit.log”, but import does not enforce those flags or block forbidden paths.

Reproduction Steps

  1. Create a fresh KB:
    tmp=$(mktemp -d)
    cd "$tmp"
    vouch init
  2. Create a bundle whose manifest includes audit.log.jsonl.
  3. Run:
    vouch import-check audit-overwrite.tar.gz
    vouch import-apply audit-overwrite.tar.gz --on-conflict overwrite
    cat .vouch/audit.log.jsonl

Actual Result

import-check returns ok: true, and import-apply --on-conflict overwrite replaces the real audit history:

-- audit before --
1 .vouch/audit.log.jsonl
{"actor":"ubuntu",...,"event":"kb.init",...}

-- import-check malicious audit bundle --
{
  "bundle_id": "audit-overwrite-demo",
  "conflicts": ["audit.log.jsonl"],
  "issues": [],
  "ok": true
}

-- import-apply overwrite --
{
  "written": ["audit.log.jsonl"],
  "on_conflict": "overwrite"
}

-- audit after overwrite --
2 .vouch/audit.log.jsonl
{"id":"fake","event":"fake.clean.audit","actor":"attacker",...}
{"actor":"ubuntu",...,"event":"bundle.import",...}

The original kb.init audit event is gone.

Expected Result

Bundles containing audit.log.jsonl, state.db, state.db-*, or proposed/* should be rejected by import-check and import-apply, regardless of conflict mode.

Evidence

Baseline project validation passed:

vouch, version 0.1.0
86 passed, 6 skipped in 9.63s

Relevant code:

  • src/vouch/bundle.py:11 documents safety flags: no proposed/, no state.db, no audit.log.
  • src/vouch/bundle.py:96 always emits has_audit_log: False on export.
  • src/vouch/bundle.py:253 checks only path safety/conflicts, not forbidden bundle paths.
  • src/vouch/bundle.py:348 writes any manifest-listed safe relative path, including audit.log.jsonl.

Root Cause

_safe_member_path() only blocks absolute paths, NUL bytes, and .. traversal. It does not enforce the bundle safety contract or restrict import paths to EXPORT_SUBDIRS plus config.yaml.

Security/Business Impact

This destroys audit integrity. A malicious or corrupted bundle can erase prior approvals, rejections, imports, and lifecycle events, then leave only attacker-chosen audit records plus the final import event. For a review-gated KB, this undermines forensic trust and compliance value.

Suggested Fix

Reject forbidden paths during both import_check and import_apply:

FORBIDDEN_IMPORT_PATHS = {"audit.log.jsonl", "state.db"}
FORBIDDEN_PREFIXES = ("state.db-", "proposed/")

Also reject manifests where safety.has_audit_log, safety.has_state_db, or safety.has_proposed is true.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions