Add safe archive preview and extraction workflows
This commit is contained in:
+48
-27
@@ -94,21 +94,29 @@ An ordinary upload does not append a new version to an existing asset. The
|
||||
`overwrite` strategy retires the old asset at that path. Version-preserving
|
||||
updates currently belong to connector sync.
|
||||
|
||||
### Upload and unpack ZIP files
|
||||
### Preview and unpack archives
|
||||
|
||||
The UI can unpack a ZIP upload. The request body is spooled to a bounded
|
||||
temporary file rather than buffered wholly in memory. The defaults are:
|
||||
The UI previews ZIP, TAR, TAR.GZ, TAR.BZ2, and TAR.XZ before writing managed
|
||||
files. Users may select individual files or complete folders. The request is
|
||||
spooled to a bounded temporary file rather than buffered wholly in memory. The
|
||||
defaults are:
|
||||
|
||||
- 250 MiB compressed request and total extracted data;
|
||||
- 250 MiB compressed request;
|
||||
- 50 MiB per extracted member;
|
||||
- 1,000 non-directory members;
|
||||
- encrypted archives are rejected;
|
||||
- member paths are normalized and `..` traversal is rejected;
|
||||
- the actual bytes read are counted, not only the ZIP header declarations.
|
||||
- 2 GiB total expanded data;
|
||||
- 10,000 declared entries;
|
||||
- 100:1 maximum expansion ratio;
|
||||
- a 30-minute preview token bound to the tenant, user, archive digest, and
|
||||
destination;
|
||||
- password-protected ZIP support with request-only password handling;
|
||||
- traversal, duplicate paths, links, devices, and other special entries are
|
||||
rejected;
|
||||
- actual bytes read are counted, not only archive header declarations.
|
||||
|
||||
The byte limits use `FILE_UPLOAD_ZIP_MAX_BYTES` and
|
||||
`FILE_UPLOAD_MAX_BYTES`. The 1,000-member limit is currently fixed in the
|
||||
extraction service.
|
||||
The browser retains the selected archive and password until confirmation.
|
||||
Confirmation reuploads the archive, verifies the token and digest, repeats all
|
||||
safety checks, and commits only the selected files. No preview archive or
|
||||
password is retained server-side.
|
||||
|
||||
### Organize files and folders
|
||||
|
||||
@@ -399,8 +407,13 @@ the table above says live access is disabled.
|
||||
| `FILE_STORAGE_LOCAL_ROOT` | `runtime/files` | Primary local write/read root |
|
||||
| `FILE_STORAGE_LOCAL_FALLBACK_ROOTS` | empty | Comma-separated older read-only roots checked after the primary root |
|
||||
| `FILE_STORAGE_S3_ENDPOINT_URL` and related `FILE_STORAGE_S3_*` values | deployment-specific | S3-compatible endpoint, region, credentials, and bucket |
|
||||
| `FILE_UPLOAD_MAX_BYTES` | 50 MiB | Direct-upload and ZIP-member maximum |
|
||||
| `FILE_UPLOAD_ZIP_MAX_BYTES` | 250 MiB | ZIP request and extracted-total maximum |
|
||||
| `FILE_STORAGE_S3_DEPLOYMENT_MANAGED` | `false` | Installer-only trust marker for the exact `http://garage:3900` service; never use it for another endpoint |
|
||||
| `FILE_UPLOAD_MAX_BYTES` | 50 MiB | Direct-upload and extracted archive-member maximum |
|
||||
| `FILE_UPLOAD_ZIP_MAX_BYTES` | 250 MiB | Compressed archive request maximum (legacy name retained for compatibility) |
|
||||
| `FILE_ARCHIVE_MAX_ENTRIES` | 10,000 | Maximum declared archive entries |
|
||||
| `FILE_ARCHIVE_MAX_EXPANDED_BYTES` | 2 GiB | Maximum expanded archive bytes |
|
||||
| `FILE_ARCHIVE_MAX_EXPANSION_RATIO` | 100 | Maximum expanded-to-compressed ratio |
|
||||
| `FILE_ARCHIVE_PREVIEW_TTL_SECONDS` | 1,800 | Lifetime of the sealed archive preview token |
|
||||
| `MASTER_KEY_B64` | development fallback only | Encrypts database-managed connector secrets |
|
||||
|
||||
The local backend is the operational baseline. It resolves every storage key
|
||||
@@ -408,10 +421,14 @@ under the configured root and rejects escape attempts. Fallback roots support a
|
||||
controlled storage-root migration: new writes go to the primary root while
|
||||
reads can still find older objects.
|
||||
|
||||
The S3 managed-storage adapter currently fails closed before creating a boto3
|
||||
client because the SDK cannot yet guarantee connection-time DNS/IP pinning and
|
||||
redirect revalidation. Do not select `FILE_STORAGE_BACKEND=s3` for a live Files
|
||||
deployment until that boundary is implemented and the status above changes.
|
||||
The S3 managed-storage adapter fails closed before creating a boto3 client for
|
||||
arbitrary external endpoints because the SDK cannot yet guarantee
|
||||
connection-time DNS/IP pinning and redirect revalidation. The supported
|
||||
installer may provision a deployment-owned Garage service at the exact
|
||||
`http://garage:3900` endpoint and set
|
||||
`FILE_STORAGE_S3_DEPLOYMENT_MANAGED=true`. Files accepts only that exact
|
||||
service-discovery endpoint and forces path-style S3 addressing. The marker is
|
||||
deployment authority, not a general private-network bypass.
|
||||
|
||||
Multiple API replicas require the same durable blob namespace. Separate local
|
||||
container filesystems will produce incomplete reads. Until a pinned shared
|
||||
@@ -558,7 +575,7 @@ All routes below are under `/api/v1/files`.
|
||||
| Area | Routes |
|
||||
| --- | --- |
|
||||
| Spaces and content | `GET /spaces`, `GET /`, `GET /folders`, `GET /delta` |
|
||||
| Upload and folders | `POST /upload`, `POST /upload-zip`, `POST /folders`, `POST /folders/delete` |
|
||||
| Upload and folders | `POST /upload`, `POST /upload-zip` (compatibility), `POST /archive-preview`, `POST /archive-confirm`, `POST /folders`, `POST /folders/delete` |
|
||||
| File access | `GET /{file_id}`, `GET /{file_id}/download`, `DELETE /{file_id}`, `POST /bulk-delete` |
|
||||
| Organization | `POST /bulk-rename`, `POST /transfer`, `POST /archive.zip`, `POST /resolve-patterns` |
|
||||
| Sharing | `POST /{file_id}/shares`, `POST /bulk-shares` |
|
||||
@@ -603,8 +620,9 @@ Files baseline indiscriminately.
|
||||
- Owner and share checks protect individual resources after operation-scope
|
||||
checks.
|
||||
- Logical paths reject traversal and storage keys cannot escape the local root.
|
||||
- Upload, ZIP extraction, connector response, and S3 stream code use bounded
|
||||
reads; encrypted ZIP archives are rejected.
|
||||
- Upload, archive extraction, connector response, and S3 stream code use bounded
|
||||
reads. Archive previews are sealed and short-lived; ZIP passwords are
|
||||
request-only.
|
||||
- Connector HTTP sockets use connection-time DNS/IP validation and pinning,
|
||||
redirects are refused, and unsafe SDK transports fail before client creation.
|
||||
- Database-managed connector passwords/tokens are encrypted; responses redact
|
||||
@@ -699,12 +717,14 @@ her personal or Finance space, then the asset has the selected owner and tenant.
|
||||
Given Bob is outside Finance and has no share or Files admin permission, the
|
||||
same asset ID must not make the asset readable to Bob.
|
||||
|
||||
### Safe ZIP import
|
||||
### Safe archive import
|
||||
|
||||
Given a ZIP member contains `../../secret.txt`, is encrypted, exceeds 50 MiB,
|
||||
or pushes actual extracted bytes above 250 MiB, unpacking must fail without a
|
||||
committed managed asset. A valid archive must preserve normalized relative paths
|
||||
below the chosen logical folder.
|
||||
Given an archive member contains `../../secret.txt`, is a link or special
|
||||
filesystem object, exceeds 50 MiB, pushes actual expanded bytes above 2 GiB, or
|
||||
exceeds the 100:1 expansion ratio, preview or confirmation must fail without a
|
||||
committed managed asset. A password-protected ZIP requires the correct
|
||||
request-only password. A valid confirmation must match its unexpired preview
|
||||
token and preserve normalized relative paths below the chosen logical folder.
|
||||
|
||||
### Explicit conflicts
|
||||
|
||||
@@ -760,7 +780,7 @@ returning different content or credentials.
|
||||
| Area | Implemented now | Planned or explicitly outside the current boundary |
|
||||
| --- | --- | --- |
|
||||
| Managed storage | Local durable-root backend, fallback read roots, tenant blob deduplication, checksums, bounded resumable integrity scans, quarantine, and dry-run-first orphan cleanup | Operational S3 after pinned SDK transport ([#34](https://git.add-ideas.de/GovOPlaN/govoplan-files/issues/34)); scheduled scan execution |
|
||||
| Upload | Bounded direct upload, drag-and-drop UI, ZIP spool/extract limits, explicit conflicts | Malware scanning, quotas, type policy, resumable/chunked upload |
|
||||
| Upload | Bounded direct upload, drag-and-drop UI, archive preview/selective extraction, password-protected ZIP support, explicit conflicts | Malware scanning, quotas, type policy, resumable/chunked upload |
|
||||
| Organization | Folders, bulk rename preview/apply, move/copy, drag-and-drop, ZIP download, pattern resolution | General file-history UI and user-driven append-version/restore |
|
||||
| Sharing | User/group/tenant/campaign grants, expiry, idempotent revocation, searchable share-management UI, and campaign linkage display | Richer policy-driven share lifecycles |
|
||||
| Deletion/retention | Soft-delete assets/folders/spaces; immediate audited connector-secret scrubbing | File restore API, hard purge, retention policy, legal hold, and blob GC ([#38](https://git.add-ideas.de/GovOPlaN/govoplan-files/issues/38)) |
|
||||
@@ -788,7 +808,8 @@ Before releasing Files:
|
||||
5. Exercise an allowed and denied owner/share path.
|
||||
6. Exercise upload, ZIP bounds, conflict handling, download, and soft deletion.
|
||||
7. Exercise connector policy explanation and one pinned HTTP provider where
|
||||
configured; verify S3/SMB still fail closed.
|
||||
configured; verify managed Garage if selected, and verify arbitrary external
|
||||
S3 and SMB still fail closed.
|
||||
8. Verify credential deletion scrubs dependents and produces audit evidence.
|
||||
9. Verify a campaign attachment snapshot still identifies its exact version and
|
||||
checksum after the current file changes.
|
||||
|
||||
Reference in New Issue
Block a user