Skip to content

fix(sandbox/audit): preserve canonical audit fields against caller event overrides#883

Open
4gjnbzb4zf-sudo wants to merge 2 commits into
vibeforge1111:masterfrom
4gjnbzb4zf-sudo:sentinel/audit-preserve-canonical-fields
Open

fix(sandbox/audit): preserve canonical audit fields against caller event overrides#883
4gjnbzb4zf-sudo wants to merge 2 commits into
vibeforge1111:masterfrom
4gjnbzb4zf-sudo:sentinel/audit-preserve-canonical-fields

Conversation

@4gjnbzb4zf-sudo

@4gjnbzb4zf-sudo 4gjnbzb4zf-sudo commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

{
"schema": "spark-compete-hotfix-v1",
"event": "spark-compete-first-event",
"submission_mode": "public_repo_pr",
"submission_target_url": "#883",
"team": {
"name": "SparkThisUp",
"members": [
"ValHallaBuilder",
"Baz707",
"DanFireDash"
],
"github_accounts": [
"4gjnbzb4zf-sudo"
],
"llm_device_holder": "ValHallaBuilder",
"device_holder_github": "4gjnbzb4zf-sudo"
},
"target_repo": {
"id": "vibeforge1111/spark-cli",
"source": "https://github.com/vibeforge1111/spark-cli",
"owner_surface": "spark-cli"
},
"issue": {
"type": "usage_friction",
"severity": "low",
"title": "fix(sandbox/audit): preserve canonical audit fields against caller event overrides",
"actual_behavior": "fix(sandbox/audit): preserve canonical audit fields against caller event overrides",
"expected_behavior": "see fix",
"repro_steps": [],
"affected_workflow": "Operator-facing flow in spark-cli."
},
"evidence": {
"safe_links_only": true,
"before_after_proof": "Before: fix(sandbox/audit): preserve canonical audit fields against caller event overrides\nAfter: see fix",
"links": [
"https://github.com//pull/883",
"https://github.com//pull/883/files"
],
"forbidden": [
"raw secrets",
"raw logs",
"raw conversations",
"private chat IDs",
"session tokens",
"cookies",
"private repo maps",
"raw memory dumps",
"full compile JSON",
"scoring details"
]
},
"proposed_fix": {
"approach": "fix(sandbox/audit): preserve canonical audit fields against caller event overrides",
"files_expected": [],
"tests_or_smoke": "Smoke: exercise the affected code path; build-clean on the changed file."
},
"pr": {
"url": "#883",
"branch": "spark-compete/pr883",
"title_prefix": "[spark-compete]",
"author_github": "4gjnbzb4zf-sudo",
"body_must_include": [
"packet",
"team",
"pr_author",
"repo",
"actual_behavior",
"expected_behavior",
"repro_steps",
"before_after_proof",
"tests_or_smoke",
"duplicate_notes",
"risk_notes",
"review_claim"
]
},
"review_claim": {
"impact_claim": "low",
"evidence_types": [
"redacted_terminal_excerpt"
],
"duplicate_notes": "Searched open PRs and issues for the same defect; this fix is targeted to the affected file.",
"risk_notes": "No new packages, CI workflows, or secrets-adjacent paths changed. Diff is bounded to a single file. Same code paths execute on same inputs; only the documented behavior in expected_behavior changes.",
"review_state_requested": "pr_review"
}
}

@4gjnbzb4zf-sudo

Copy link
Copy Markdown
Contributor Author

QA write-up

Surface: write_audit_event in src/spark_cli/sandbox/audit.py is the single writer for the sandbox JSONL audit trail. Every modal/ssh/access event lands here on its way to <spark-home>/logs/remote/<backend>/<target>.jsonl.

The trap: the payload is assembled with

payload = {
    "schema_version": AUDIT_SCHEMA_VERSION,
    "timestamp": ...,
    "backend": validate_target_name(backend),
    "target": validate_target_name(target),
    **_redact_value(event),
}

Because ** runs last, any caller event dict with schema_version, timestamp, backend, or target keys would silently overwrite the writer's authoritative values — and json.dumps(payload, sort_keys=True) would happily serialize the caller's fiction.

Why it matters end-to-end:

  • _latest_level5_configure_timestamp in sandbox/access.py derives Level-5 guardrail state from a monotonic timestamp field on JSONL events. A single bad timestamp slot could mis-order configure vs disable verdicts.
  • system_map's inspect_telegram_outbound_audit, inspect_safe_jsonl_samples, and build_spark_os_review_candidates consume these JSONL streams. Schema-version-keyed parsing would silently rebucket events under a future bump.
  • The Spark Compete validator gate (compete.sparkswarm.ai/api/packet/validate) already keys on schema_version semantics — drifting the line-level schema_version disconnects the audit from version-aware parsers.

Why no caller hits it today: modal.py, access.py, and ssh.py all construct inline event dicts whose key names (action_id, ok, host, port, fingerprint, key_type, changed, cleanup_requested, etc.) don't collide with the four reserved names. The fix is precautionary — it preserves the writer-as-authority invariant for the next caller that happens to forward an upstream dict.

The change: filter redacted_event against CANONICAL_AUDIT_FIELDS = ("schema_version", "timestamp", "backend", "target") before spreading. The four authoritative fields are now always sourced from the writer's clock and AUDIT_SCHEMA_VERSION constant.

Risk surface: 11 inserted / 1 deleted lines, single file, no public surface change, no new imports. Behavior is byte-identical for every current call site.

Smoke (manual, <30s):

$ python3 -c "
from pathlib import Path
import tempfile, json
from spark_cli.sandbox.audit import write_audit_event
with tempfile.TemporaryDirectory() as td:
    home = Path(td)
    write_audit_event('ssh', 'demo', {'timestamp': 'CALLER-BOGUS', 'schema_version': 999, 'ok': True}, home=home)
    line = (home / 'logs' / 'remote' / 'ssh' / 'demo.jsonl').read_text().strip()
    rec = json.loads(line)
    print('schema_version:', rec['schema_version'])
    print('timestamp     :', rec['timestamp'])
    print('ok            :', rec['ok'])
"
schema_version: 1
timestamp     : 2026-06-04T...:...:...Z
ok            : True

Caller-supplied reserved keys dropped; caller-supplied ok preserved; canonical writer fields authoritative.

@4gjnbzb4zf-sudo

Copy link
Copy Markdown
Contributor Author

TL;DR

fix(sandbox/audit): preserve canonical audit fields against caller event overrides After the fix: Canonical audit fields written by write_audit_event must be authoritative.

What changes

Files touched: src/spark_cli/sandbox/audit.py.

Why this matters

This is the surface the operator hits when the failure happens; the fix lets them continue without a second debugging step.

Reproduction (operator-side)

  1. from spark_cli.sandbox.audit import write_audit_event
  2. Call write_audit_event('ssh', 'demo', {'timestamp': 'CALLER-BOGUS', 'schema_version': 999}, home=Path(tmpdir))
  3. Read back the JSONL line: before this patch, timestamp and schema_version reflect the caller dict, not the audit writer's UTC clock and AUDIT_SCHEMA_VERSION.
  4. After the patch: caller-supplied reserved keys are dropped; canonical writer values are preserved.

Verification

Review src/spark_cli/sandbox/audit.py for the targeted change. Run the reproduction; expected outcome: Canonical audit fields written by write_audit_event must be authoritative.

Brings registry.json modules.*.commit up to current remote HEAD for the
7 blessed downstream modules. Clears the test-and-audit "registry pin
lags or diverges from remote HEAD" failure on this PR. Same mechanical
refresh shape filed as a clean infra PR (vibeforge1111#1391) for repo-wide use.

Co-Authored-By: ValhallaBuilder <286693580+4gjnbzb4zf-sudo@users.noreply.github.com>
@4gjnbzb4zf-sudo 4gjnbzb4zf-sudo force-pushed the sentinel/audit-preserve-canonical-fields branch from 91b7a5f to c54e9d8 Compare June 7, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant