Commit Graph

29 Commits

Author SHA1 Message Date
Donal McBreen b79c7897e6 Fix export/import losing custom column colors
record.to_json serializes through model accessors. Column::Colored
overrides the color accessor to return a Color struct, which serializes
as {"name":"Blue","value":"var(--color-card-default)"} instead of the
raw CSS string.

Two fixes:
- Export raw DB values via record.attributes.slice(*attributes).to_json
- Color.for_value parses legacy JSON format so columns imported from
  old exports self-heal when read
2026-04-03 10:05:11 +01:00
Stanko K.R. 49fbb82676 Permit variant records in imports 2026-03-23 12:30:40 +01:00
Daniel Pinto ad1b10368d Fix export size doubling from re-exporting prior export blobs (#2707)
* Exclude export/import blobs and attachments from account exports

Export and import file blobs were being assigned the account's
account_id (via the Current.account default), causing subsequent
exports to include previous export/import ZIP files in the data.
This roughly doubled the export size each time and made imports
fail with an IntegrityError on unrecognized "Account::Export"
record type.

Filter out blobs attached to Export/Import records in
BlobRecordSet, FileRecordSet, and a new AttachmentRecordSet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Use STI base type for export blob exclusion filter

Rails polymorphic_name returns base_class.name for STI models,
so ActiveStorage stores record_type as "Export" rather than the
concrete subclass names. Filter on the base type accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix export filtering for mixed-use Active Storage blobs

Exclude only internal-only blobs for account export metadata/file sets, scope attachment subqueries by account, and add regression coverage for blobs shared between user records and export attachments.

* Simplify export blob filtering

Exports always create fresh blobs, so a blob can never be legitimately
shared between an Export/Import and a real account record. Replace the
internal_only/external split with a single excluded_blob_ids subquery
and drop the contrived shared-blob test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 11:41:22 -04:00
Mike Dalessio 1e40457418 Merge pull request #2686 from basecamp/flavorjones/fix-path-traversal
Fix path traversal in ActiveStorage blob key import
2026-03-10 15:40:48 -04:00
Stanko K.R. b4fd22644c Set the list of importable models based on the record sets in the manifest 2026-03-10 15:33:24 -04:00
Mike Dalessio 8fb57ace01 Validate polymorphic types against importable models allowlist
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.
2026-03-10 15:33:24 -04:00
Mike Dalessio 71b99c1e18 Fix path traversal vulnerability in ActiveStorage blob key import
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.
2026-03-10 11:03:53 -04:00
Stanko K.R. 2f9d4533c5 Relativize absolute links to the instance on export 2026-02-24 08:22:01 +01:00
Stanko K.R. 4eadce8ffe Automatically update account slugs in links 2026-02-24 08:11:47 +01:00
Stanko K.R. 55779f8ac4 Organize the record sets better 2026-02-24 07:36:51 +01:00
Stanko K.R. d8ed2f44b8 Reconcile card count after import 2026-02-24 07:36:20 +01:00
Rosa Gutierrez b742963f7c Add account ownership check to import GID resolution
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>
2026-02-16 11:49:00 +01:00
Stanko K.R. 37d7f5c569 Give more detail about why the import failed 2026-02-11 13:47:21 +01:00
Mike Dalessio 7f473eda66 Validate ActionText associations during import check phase
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.
2026-02-03 13:46:28 -05:00
Stanko K.R. 1c1a73c57c Check if the file exists before adding it
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.
2026-02-03 15:01:10 +01:00
Stanko K.R. a4bec6e407 Ignore missing attachments 2026-02-03 13:51:22 +01:00
Stanko K.R. 539cd638e2 Check that associations don't exist
Without this you can change a belongs to to some other account's record and get access to it
2026-02-02 16:03:50 +01:00
Stanko K.R. dca67c59ca Bust the cache after import
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
2026-02-02 15:48:49 +01:00
Stanko K.R. 1bc1b68ddf Explicitly use the default storage service 2026-02-02 15:22:16 +01:00
Stanko K.R. 60c63271b9 Fix ActiveStorage writing to disk 2026-02-02 15:16:37 +01:00
Stanko K.R. d7dcf64310 Remove model validation 2026-02-02 13:38:34 +01:00
Stanko K.R. 27c73ff42b Rename validate to check
Validate is an ActiveModel method, overriding it could have unintended side-effects so I opted to rename it
2026-02-02 12:36:48 +01:00
Stanko K.R. 184150006f Unify ZIP file interactions between all exports 2026-02-02 12:36:48 +01:00
Stanko K.R. f50d6d093d Implement cursors for imports 2026-02-02 12:36:48 +01:00
Stanko K.R. 63f6be4ef5 Resolve import conflicts 2026-02-02 12:36:48 +01:00
Stanko K.R. b97934ff48 Fix crash when exporting ActiveStorage files 2026-02-02 12:36:48 +01:00
Stanko K.R. 122a228ed4 Use convention to create boilerplate record sets 2026-02-02 12:36:48 +01:00
Stanko K.R. f457273937 Convert double negatives into a positive question 2026-02-02 12:36:48 +01:00
Stanko K.R. 00c5f7feab Break import and export objects into record sets 2026-02-02 12:36:48 +01:00