better
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user