Skip to content

[codex] Add encrypted backup file bundles#38

Merged
steipete merged 16 commits into
mainfrom
codex/encrypted-files
Jun 19, 2026
Merged

[codex] Add encrypted backup file bundles#38
steipete merged 16 commits into
mainfrom
codex/encrypted-files

Conversation

@steipete

@steipete steipete commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add additive encrypted file entries to backup manifests
  • stream each source through hashing, gzip, and age encryption in one pass
  • content-deduplicate identical files while reusing unchanged encrypted blobs
  • keep logical paths, plaintext sizes, and content hashes inside an encrypted index
  • expose only random opaque media object IDs in the clear manifest
  • safely restore current or historical file bundles with traversal, symlink, hash, and size checks
  • keep existing row-only backup APIs and manifests compatible

Validation

  • go test ./...
  • go test -race ./...
  • go vet ./...
  • deadcode
  • autoreview: clean after fixing the reported hash/encryption TOCTOU

@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed June 19, 2026, 2:12 PM ET / 18:12 UTC.

Summary
The PR branch adds encrypted file-bundle backup and restore APIs, a Manifest.Files field, encrypted file indexes, file object cleanup, historical file restore helpers, and tests in the backup package.

Reproducibility: yes. from source inspection: a root that is itself a directory symlink passes the initial os.Stat, then filepath.WalkDir presents the root symlink and the PR skips it. I did not run a filesystem reproduction because this review is read-only.

Review metrics: 2 noteworthy metrics.

  • PR surface: 6 files changed, 1028 additions, 6 deletions. The branch introduces a sizable new backup file-bundle path in a shared Go library, so compatibility and proof matter before merge.
  • Exported backup surface: 8 new exported identifiers plus Manifest.Files. The new public backup API and persisted manifest field affect downstream consumers beyond this repository's unit tests.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🧂 unranked krab
Patch quality: 🦪 silver shellfish
Result: blocked until real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Reject or safely resolve symlinked collection roots and add focused coverage.
  • [P1] Add redacted real backup/restore output, logs, a terminal screenshot, recording, or linked artifact; redact private paths, IP addresses, endpoints, keys, phone numbers, and other sensitive details.
  • After adding proof, update the PR body so ClawSweeper can re-review automatically; if it does not, ask a maintainer to comment @clawsweeper re-review.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body lists validation commands but no redacted terminal output, logs, screenshot, recording, or linked artifact from a real backup/restore run. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] A symlinked media/cache root can look successful while producing an empty file bundle and deleting previously tracked encrypted file objects during stale cleanup.
  • [P1] The new stored Manifest.Files field, exported APIs, and reserved data/files/** shard namespace are compatibility-sensitive for downstream apps that already consume row-only backup manifests.
  • [P1] The PR body still lists validation commands without real backup/restore output, logs, screenshot, recording, or linked artifact.

Maintainer options:

  1. Fix symlinked roots and refresh proof (recommended)
    Update CollectFiles to fail visibly or resolve root directory symlinks, add focused coverage, and then provide redacted output from a real backup/restore run.
  2. Pause for compatibility validation
    Hold merge until maintainers see upgrade proof for existing row-only backups and the new file-bundle manifest shape in downstream app smoke tests.

Next step before merge

  • [P1] Missing real behavior proof requires contributor or maintainer follow-up, and the symlink repair should not be routed as an automated repair marker while the proof gate is still unmet.

Security
Cleared: No concrete confidentiality, traversal, CI, dependency, or supply-chain regression was found; the symlinked-root issue is tracked as backup correctness and compatibility risk.

Review findings

  • [P2] Reject symlinked collection roots before writing manifests — backup/files.go:70-74
  • [P3] Drop the release-owned changelog edit — CHANGELOG.md:5
Review details

Best possible solution:

Reject or safely resolve symlinked collection roots before any manifest write, keep the file-bundle manifest additive, remove the release-owned changelog edit, and require redacted real backup/restore plus downstream compatibility proof before merge.

Do we have a high-confidence way to reproduce the issue?

Yes, from source inspection: a root that is itself a directory symlink passes the initial os.Stat, then filepath.WalkDir presents the root symlink and the PR skips it. I did not run a filesystem reproduction because this review is read-only.

Is this the best way to solve the issue?

No; the provider-neutral feature belongs in crawlkit, but the patch should fail visibly or safely resolve symlinked roots before writing a manifest and pruning file objects.

Full review comments:

  • [P2] Reject symlinked collection roots before writing manifests — backup/files.go:70-74
    When root is a symlink to a directory, os.Stat succeeds but filepath.WalkDir visits only the symlink entry, and this branch skips it. That can turn a configured media/cache backup into an empty file bundle and then stale cleanup can remove previously tracked encrypted file objects, so reject or safely resolve a symlinked root before manifest writes.
    Confidence: 0.94
  • [P3] Drop the release-owned changelog edit — CHANGELOG.md:5
    This repository treats CHANGELOG.md as release-owned for normal PRs. Keep the release note context in the PR body or commit message and let the release process update the changelog.
    Confidence: 0.88

Overall correctness: patch is incorrect
Overall confidence: 0.9

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 797628aa9708.

Label changes

Label justifications:

  • P2: This is a normal-priority shared-library feature with a concrete backup correctness blocker but no current shipped production regression.
  • merge-risk: 🚨 compatibility: The PR adds exported backup APIs, reserves a new shard namespace, and changes the stored backup manifest shape that downstream apps must tolerate.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦪 silver shellfish.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body lists validation commands but no redacted terminal output, logs, screenshot, recording, or linked artifact from a real backup/restore run. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

What I checked:

  • Repository policy: AGENTS.md was read fully; its shared-library boundary and additive API compatibility guidance apply to this backup API review. (AGENTS.md:1, 797628aa9708)
  • Boundary fit: docs/boundary.md places snapshot sidecars and encrypted historical snapshot restore mechanics in crawlkit, so the feature direction fits if compatibility is preserved. (docs/boundary.md:42, 797628aa9708)
  • Not implemented on current main: Current main has WriteSnapshot but no WriteSnapshotWithFiles, CollectFiles, RestoreFiles, FileEntry, fileIndexTable, or backup data/files implementation. (backup/backup.go:55, 797628aa9708)
  • Latest release check: The latest release tag v0.12.2 also lacks the encrypted file-bundle symbols, so the PR is not superseded by a shipped release. (94bbcd72fa6f)
  • Latest PR head: GitHub reports latest PR head 5880b7f with six changed files and the file-bundle implementation in backup/files.go. (5880b7fd6d0e)
  • Symlink-root blocker: CollectFiles calls os.Stat on the root but filepath.WalkDir still sees a symlink root as a symlink entry, so lines 70-74 skip it rather than rejecting or resolving it. (backup/files.go:70, 5880b7fd6d0e)

Likely related people:

  • steipete: Current-main blame and commit history show this person introduced the encrypted backup helpers and recent durable archive mechanics that this PR extends. (role: feature owner and recent area contributor; confidence: high; commits: 55196d74e0e6, 3ff7edef8b33, 6e14735bb248; files: backup/backup.go, backup/history.go, backup/backup_test.go)
  • vincentkoc: A recent hardening commit touched backup encryption helpers and tests, making this a useful secondary routing signal for backup safety review. (role: adjacent hardening contributor; confidence: medium; commits: 2b7d5222adea; files: backup/backup.go, backup/backup_test.go, backup/crypto.go)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f70d9cb225

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread backup/files.go
Comment on lines +52 to +56
if entry.Type()&os.ModeSymlink != 0 {
if entry.IsDir() {
return filepath.SkipDir
}
return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject symlinked collection roots instead of returning empty

When the configured root itself is a symlink to a directory, filepath.WalkDir visits only that symlink, entry.IsDir() is false, and this branch returns nil, so CollectFiles returns an empty file list. A downstream backup configured to a symlinked media/cache directory would then write a manifest with no file entries and remove the previously tracked file bundle blobs as stale. Handle source == root separately by resolving the root or returning an error rather than silently skipping it.

Useful? React with 👍 / 👎.

Comment thread backup/files.go Outdated
if !info.Mode().IsRegular() {
return nil, fmt.Errorf("backup file is not regular: %s", file.Source)
}
tmpPath, hashValue, size, encryptedSize, err := encryptFileTemp(ctx, file.Source, cfg.Repo, cfg.Recipients)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Hash before encrypting reusable file blobs

For unchanged files from the previous manifest, and for duplicate content later in the same snapshot, this call still writes a full gzip+age temp blob before the written/oldByHash reuse checks below discard it. On a large media backup, an otherwise no-op run can fail with ENOSPC or take hours because it needs room and CPU for a second encrypted copy of each unchanged file. Compute the plaintext hash/size first and only encrypt when the hash is not already reusable.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 19, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9c1c822ef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread backup/files.go Outdated
out = append(out, entry)
continue
}
shard := path.Join("data/files", hashValue[:2], hashValue+".gz.age")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Version file blobs when recipients rotate

When reuseEncrypted is false because the recipient set changed, an unchanged file still maps to the same hash-only data/files/<hash>.gz.age path, so the rename below overwrites the blob referenced by the old manifest before the new manifest is safely written. If the process crashes or a later write fails during recipient rotation, the previous manifest remains on disk but its file object is now encrypted for the new recipients; the row-shard path avoids this by using a versioned path when !reuseEncrypted.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Jun 19, 2026
@steipete

Copy link
Copy Markdown
Contributor Author

Validation complete:

  • go test ./...
  • go test -race ./...
  • go vet ./...
  • Windows cross-compile for ./backup
  • Structured whole-branch autoreview: no accepted/actionable findings
  • GitHub checks green: Linux, Windows, CodeQL, secret scan, analysis

Review-driven fixes covered path privacy, source/restore symlink confinement, encrypted-index namespace collisions, legacy cleanup compatibility, recipient-only writes, Windows test portability, and canonical slash-only shard paths.

@steipete
steipete merged commit 28d58de into main Jun 19, 2026
6 checks passed
@steipete
steipete deleted the codex/encrypted-files branch June 19, 2026 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant