This commit is contained in:
Anaïs Saraiva
2026-08-15 10:46:10 +02:00
parent 6e76189e46
commit 4867cf57b6
7 changed files with 83 additions and 26 deletions
+11 -11
View File
@@ -93,7 +93,7 @@ Example metadata record (`storage/metadata/<file-id>.json`):
```json
{
"id": "YOWJyhDXxhe3-L8MpEFBbA",
"id": "nkj2p0ulkijp",
"originalName": "example.zip",
"size": 123456,
"mimeType": "application/zip",
@@ -137,9 +137,9 @@ curl -F "password=hunter2" -F "[email protected]" http://localhost:3000/api/uploa
}
```
`id` and `key` are the same 12-character base64url token (~72 bits of entropy);
the short length keeps share URLs tidy, and an optional password can add a
second factor of protection.
`id` and `key` are the same 12-character lowercase alphanumeric token (~62 bits
of entropy); the short length keeps share URLs tidy, and an optional password
can add a second factor of protection.
### `GET /:key` (also `HEAD`)
@@ -177,7 +177,7 @@ Permanently deletes the file and its metadata. The correct download key must be
sent in the `X-Delete-Key` header — proving you hold the secret.
```bash
curl -X DELETE -H "X-Delete-Key: YOWJyh...7OQ" http://localhost:3000/api/files/YOWJyhDXxhe3-L8MpEFBbA
curl -X DELETE -H "X-Delete-Key: nkj2p0ulkijp" http://localhost:3000/api/files/nkj2p0ulkijp
```
### `GET /api/health`
@@ -194,18 +194,18 @@ paths or stack traces.
## Security model
- **Secrets.** Each file gets a fresh 72-bit (9 random bytes) base64url key via
`crypto.randomBytes`, used as both the share id and the download key. Keys are
encoded base64url; guessing one is infeasible at scale, and an optional
password can be an added second factor. No timestamps, incrementing ids,
`Math.random()`, or filenames are ever used as secrets.
- **Secrets.** Each file gets a fresh 12-character lowercase alphanumeric key
(`crypto.randomBytes` mapped onto `a-z0-9`, ~62 bits of entropy), used as both
the share id and the download key. Guessing one is infeasible at scale, and an
optional password can be an added second factor. No timestamps, incrementing
ids, `Math.random()`, or filenames are ever used as secrets.
- **At-rest.** Only a SHA-256 hash of the key is persisted. Comparisons use
constant-time `timingSafeEqual`.
- **Passwords.** A file's password (if any) is stored only as an **scrypt** hash
(salted), never in plaintext. Successful entry mints a short-lived HMAC-signed
cookie, so access is granted by the server and cannot be forged.
- **Path safety.** Filesystem paths are built only from server-generated,
regex-validated ids (`^[A-Za-z0-9_-]{22}$`). The original filename is metadata
regex-validated ids (`^[a-z0-9]{12}$`). The original filename is metadata
only and is sanitized (basename, control/null bytes stripped) before being
stored or emitted in `Content-Disposition`. Path traversal, `../`, encoded
traversal, null bytes, and symlink injection via user input are not possible.