Repository navigation
feat(flow): encrypt every PHI file at rest under one sealed index - #1354
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… output sealed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed runs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…deQL findings, gitleaks false positive - open_set no longer hashes the passphrase for its cache key; it compares the passphrase in constant time against the cached entry. - recorder keeps declared secret parameter NAMES as _withheld_param_names. - .gitleaksignore: fingerprint for the Workflow.load(key=key, ...) false positive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # llms-full.txt
# Conflicts: # .gitleaksignore # llms-full.txt # packages/flow/openadapt_flow/recorder.py # packages/flow/public-artifacts.json
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
An external review found that
openadapt-flow sealencrypted onlyworkflow.jsonand the template crops. Every other bundle file stayed plaintext, and so did recordings and run directories. This PR encrypts every file that can hold patient data, binds each set of files to one authenticated index, and keeps the commands that read these files working without writing plaintext.New: sealed sets (
openadapt_flow/sealed_set.py). A bundle, a recording or a run directory can be a sealed set. Each holds a header with the scrypt salt and an AES-256-GCM index of every file and its SHA-256. Data files are stored as<name>.enc. Opening a set authenticates the index, then refuses:Writing into a set refuses a file the inventory doesn't know. The KDF and AEAD are the existing
cryptographyprimitives. The key is derived once per directory; each file gets a fresh random nonce.Inventory.
BUNDLE_FILESandRECORDING_FILESclassify every file a bundle or recording can contain. They are printed indocs/phi_at_rest.md, and a test keeps that table in step with the code. Onlymanifest.json(digests and provenance) stays plaintext in a bundle.seal. Encrypts the whole bundle in memory, with no plaintext staging copy. It refuses unknown files, verifies the result as a reader would, and prints the sealed file-set digest.record. WithOPENADAPT_RECORDING_KEY, the web recorder writes only ciphertext from the first frame: frames, events, metadata, network capture and--verifyrows. It falls back toOPENADAPT_BUNDLE_KEY, so encryption is on by default when that key is set.--no-encryptopts out. Desktop recorders refuse an explicit recording key, because openadapt-capture writes a raw SQLite/video session that Flow can't encrypt yet.Runs. A run of a sealed bundle writes a sealed run directory. That covers the report, step frames, identity and resolution crops, transition evidence, durable checkpoints, attended pause state and undo records. The durable store's own encrypted files are adopted as ciphertext. A plaintext file written by any component makes the run refuse to seal its index. Approval-gate runs, attended drafts and business-decision states are refused before they act, because those stores still write plaintext.
Commands. These read sealed or encrypted input in memory and keep their output sealed:
compile,induce,scaffold-verifierseal,certify,qualify,admitrun,replay,approve,resume,undoWithout the key they refuse up front and name the variable. Any other command given a sealed path refuses rather than decrypt to disk. A bare
Workflow.save()into a sealed bundle stays sealed.Docs. Updated the README passage,
phi_at_rest.md,PRIVACY.md,LIMITS.mdand the CAPABILITIES row. Each says exactly what is encrypted and what isn't: data in memory, the app's own storage, file names and sizes, structural lock and manifest files, desktop recordings, admission-root records, and legacysave(encrypt=True)bundles.Evidence
tests/test_sealed_at_rest.pyhas 21 tests. A process audit hook records every file the Python process writes, renames or deletes, and checks each one for plaintext markers, temp files included. The end-to-end test:No plaintext reaches the disk. Other tests cover:
Cost (
benchmark/at_rest/, 150 actions, 87 KB synthetic 1280x800 frames, measured with other load on the machine):Not in this PR (follow-ups)
check mark,visualize,explain,teach,emit-skillandpushon sealed input. They refuse today.save(encrypt=True)bundle still decrypts on a bare re-save. Existing tests pin that behavior.Test plan
tests/test_sealed_at_rest.py(21 passed)ruff check/ruff formaton changed files🤖 Generated with Claude Code