Add IMPORTABLE_MODEL_NAMES to RecordSet and verify polymorphic type
columns during import check against this allowlist before calling
constantize. This prevents arbitrary class instantiation from
untrusted import ZIP data.
BlobRecordSet#import_batch preserved blob keys from exported ZIP files
verbatim. A crafted ZIP with a traversal key like "../../config/deploy.yml"
would create a blob whose key resolves to an arbitrary filesystem path
when served by ActiveStorage's DiskService, allowing an attacker to read
local files.
Fix: generate fresh blob keys on import, discarding whatever key was in
the ZIP. This is safe because nothing else in the import pipeline
references blobs by key — attachments use blob_id, ActionText uses GIDs
based on record IDs, and only FileRecordSet used the old key for file
data lookup.
Update FileRecordSet to build an old_key→blob_id mapping from the blob
JSON metadata in the ZIP, then look up blobs by ID instead of by key.
Both check_record and import_batch are now fail-closed: they raise
IntegrityError for unmapped storage files, missing blobs, and duplicate
keys in the export.
Backfill test coverage for BlobRecordSet and FileRecordSet import, and
add a round-trip test verifying blob data survives export/import with
regenerated keys.
The convert_gids_to_sgids method resolved GIDs globally and minted
valid SGIDs without verifying the record belongs to the importing
account. This adds the same account_id check that the export path
already has, plus RecordNotFound handling for invalid GIDs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ActionTextRichTextRecordSet now calls check_associations_dont_exist to
ensure imported ActionText records only reference records within the
same import, matching the behavior of the parent RecordSet class.
ZipKit creates a placeholder record when add_file is called, this record is then filled with the content that's passxed to write. But if the blob's file doesn't exist then an error is rased and the placeholder is never filled. This results in a ZIP that has a place in its catalog for the blob, but no contant for it, which makes it invalid.
Without this exporting, deleting and then re-imporing an account would reuse the old cache entries since the IDs are the same, but those cache entries will likely have the old account ID and would break