Summary
create_share_token and create_share_token_with_mode in crates/fula-flutter/src/api/sharing.rs call guard.inner().head_object(&bucket, &storage_key) directly to retrieve the wrapped DEK from the S3 x-fula-encryption HTTP header. This bypasses the offline-fallback infrastructure (get_object_with_offline_fallback plus forest_entry.user_metadata fallback) that the download path already uses, so share-token creation fails whenever the master S3 endpoint is unreachable — even though the wrapped DEK is already available locally in the AEAD-protected forest_entry.user_metadata["x-fula-encryption"] field that the SDK populates at upload time (crates/fula-client/src/encryption.rs:5955-5968).
Reproduction
- Sign in to a v0.5.x SDK consumer (FxFiles) and upload one or more files to a bucket via the encrypted SDK.
- Change the gateway endpoint URL to an unresolvable hostname (e.g.,
s33.cloud.fx.land instead of s3.cloud.fx.land) to simulate master-unreachable. Restart the app.
- Trigger any flow that creates share tokens — for example, the automatic tag-share refresh (
SharingService._buildManifestEntries in FxFiles).
- Observe:
listObjects(bucket) succeeds via the warm-cache forest fallback (Forest loaded for bucket: images / listObjects(images, prefix=""): 34 files), but every createShareToken call fails with:
AnyhowException(Failed to fetch object metadata: HTTP error:
error sending request for url (https://<unreachable-host>/<bucket>/<storage_key>)
... dns error ... No address associated with hostname)
followed by [TagManifestUpdate] re-published share <id> with 0 files.
Root Cause (file:line)
crates/fula-flutter/src/api/sharing.rs:47 in create_share_token:
let head_result = guard.inner().head_object(&bucket, &storage_key).await
.map_err(|e| anyhow::anyhow!("Failed to fetch object metadata: {}", e))?;
crates/fula-flutter/src/api/sharing.rs:144 in create_share_token_with_mode: identical pattern.
Both call head_object directly on the inner FulaClient. The inner head_object issues a raw HTTP HEAD against the configured master endpoint — no block-cache check, no IPFS gateway race, no forest-entry fallback.
The download path is wired correctly: get_object_decrypted_inner at crates/fula-client/src/encryption.rs:1432-1463 uses get_object_with_offline_fallback_known_cid (or get_object_with_offline_fallback if no CID hint) and has a get_meta helper that falls back from HTTP headers to forest_entry.user_metadata. Share-token creation was written before that pattern landed and never updated.
Expected vs Actual
- Expected: share-token creation succeeds offline for any file whose forest entry carries the encryption JSON in
user_metadata (true for all uploads since encryption.rs:5955-5968 landed). The wrapped DEK can be unwrapped locally without any network call.
- Actual: every share-token creation issues a HEAD against the configured master, even though all the inputs needed are already in the local AEAD-protected forest.
Suggested Fix
Mirror the pattern already in get_object_decrypted_inner:
- Look up the forest entry for the given
(bucket, storage_key) via the existing forest_entry_lookup helper.
- Read
x-fula-encryption from forest_entry.user_metadata first; fall back to head_object only when the forest entry is missing OR has no x-fula-encryption key (legacy uploads pre-encryption.rs:5955).
- Parse the same JSON shape currently parsed from the HEAD response.
Approximate diff: ~30 LOC in sharing.rs, no fula-crypto change, no FFI signature change.
Severity
Medium-High. Confirmed broken: any share-token-dependent workflow when master is unreachable, including FxFiles automatic tag-share refresh, manual create-share-link, and any UI that uses share tokens to display files.
The direct file-download path (get_flat) is correctly wired through the offline-fallback infrastructure, so plain downloads work offline for files in the warm cache or reachable via IPFS gateway. Only share-token flows break.
Verification Plan
A failing unit test will be added under tests/ that:
- Sets up an in-memory blockstore client and uploads an encrypted file.
- Marks the master backend unreachable.
- Calls
create_share_token on the uploaded file.
- Asserts it currently fails — proving the bug.
After the fix lands, the same test should pass.
Reported by FxFiles team during offline-mode audit. Will follow with PR.
Summary
create_share_tokenandcreate_share_token_with_modeincrates/fula-flutter/src/api/sharing.rscallguard.inner().head_object(&bucket, &storage_key)directly to retrieve the wrapped DEK from the S3x-fula-encryptionHTTP header. This bypasses the offline-fallback infrastructure (get_object_with_offline_fallbackplusforest_entry.user_metadatafallback) that the download path already uses, so share-token creation fails whenever the master S3 endpoint is unreachable — even though the wrapped DEK is already available locally in the AEAD-protectedforest_entry.user_metadata["x-fula-encryption"]field that the SDK populates at upload time (crates/fula-client/src/encryption.rs:5955-5968).Reproduction
s33.cloud.fx.landinstead ofs3.cloud.fx.land) to simulate master-unreachable. Restart the app.SharingService._buildManifestEntriesin FxFiles).listObjects(bucket)succeeds via the warm-cache forest fallback (Forest loaded for bucket: images/listObjects(images, prefix=""): 34 files), but everycreateShareTokencall fails with:followed by
[TagManifestUpdate] re-published share <id> with 0 files.Root Cause (file:line)
crates/fula-flutter/src/api/sharing.rs:47increate_share_token:crates/fula-flutter/src/api/sharing.rs:144increate_share_token_with_mode: identical pattern.Both call
head_objectdirectly on the innerFulaClient. The innerhead_objectissues a raw HTTP HEAD against the configured master endpoint — no block-cache check, no IPFS gateway race, no forest-entry fallback.The download path is wired correctly:
get_object_decrypted_inneratcrates/fula-client/src/encryption.rs:1432-1463usesget_object_with_offline_fallback_known_cid(orget_object_with_offline_fallbackif no CID hint) and has aget_metahelper that falls back from HTTP headers toforest_entry.user_metadata. Share-token creation was written before that pattern landed and never updated.Expected vs Actual
user_metadata(true for all uploads sinceencryption.rs:5955-5968landed). The wrapped DEK can be unwrapped locally without any network call.Suggested Fix
Mirror the pattern already in
get_object_decrypted_inner:(bucket, storage_key)via the existingforest_entry_lookuphelper.x-fula-encryptionfromforest_entry.user_metadatafirst; fall back tohead_objectonly when the forest entry is missing OR has nox-fula-encryptionkey (legacy uploads pre-encryption.rs:5955).Approximate diff: ~30 LOC in
sharing.rs, no fula-crypto change, no FFI signature change.Severity
Medium-High. Confirmed broken: any share-token-dependent workflow when master is unreachable, including FxFiles automatic tag-share refresh, manual create-share-link, and any UI that uses share tokens to display files.
The direct file-download path (
get_flat) is correctly wired through the offline-fallback infrastructure, so plain downloads work offline for files in the warm cache or reachable via IPFS gateway. Only share-token flows break.Verification Plan
A failing unit test will be added under
tests/that:create_share_tokenon the uploaded file.After the fix lands, the same test should pass.
Reported by FxFiles team during offline-mode audit. Will follow with PR.