@filelayer/core 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +338 -0
- package/LICENSE +202 -0
- package/MIGRATIONS.md +328 -0
- package/NOTICE +37 -0
- package/README.md +343 -0
- package/SEMANTICS.md +729 -0
- package/dist/authz.d.ts +524 -0
- package/dist/authz.d.ts.map +1 -0
- package/dist/authz.js +889 -0
- package/dist/authz.js.map +1 -0
- package/dist/db.d.ts +145 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +217 -0
- package/dist/db.js.map +1 -0
- package/dist/delivery.d.ts +293 -0
- package/dist/delivery.d.ts.map +1 -0
- package/dist/delivery.js +519 -0
- package/dist/delivery.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/filelayer.d.ts +542 -0
- package/dist/filelayer.d.ts.map +1 -0
- package/dist/filelayer.js +1360 -0
- package/dist/filelayer.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/simple.d.ts +297 -0
- package/dist/simple.d.ts.map +1 -0
- package/dist/simple.js +492 -0
- package/dist/simple.js.map +1 -0
- package/dist/storage.d.ts +269 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +700 -0
- package/dist/storage.js.map +1 -0
- package/dist/store.d.ts +432 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +862 -0
- package/dist/store.js.map +1 -0
- package/package.json +77 -0
- package/schema.sql +1190 -0
- package/src/authz.ts +1398 -0
- package/src/db.ts +271 -0
- package/src/delivery.ts +737 -0
- package/src/errors.ts +24 -0
- package/src/filelayer.ts +1836 -0
- package/src/index.ts +7 -0
- package/src/simple.ts +666 -0
- package/src/storage.ts +917 -0
- package/src/store.ts +1072 -0
- package/test/delivery.test.ts +0 -0
- package/test/group-subjects.test.ts +1072 -0
- package/test/helpers.ts +65 -0
- package/test/listing.test.ts +689 -0
- package/test/local-s3.d.mts +33 -0
- package/test/local-s3.mjs +400 -0
- package/test/persistence.test.ts +953 -0
- package/test/regression.test.ts +619 -0
- package/test/s3-live.test.ts +322 -0
- package/test/security.test.ts +1652 -0
- package/test/semantics.test.ts +888 -0
- package/test/storage.test.ts +437 -0
- package/test/tiers.test.ts +432 -0
- package/test/vault-example.test.ts +302 -0
- package/tsconfig.build.json +29 -0
- package/tsconfig.json +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# Filelayer
|
|
2
|
+
|
|
3
|
+
[](https://github.com/filelayer/filelayer/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@filelayer/core)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
[](https://github.com/filelayer/filelayer/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
<!-- The first three badges render as "unknown" until the repository is pushed
|
|
9
|
+
and the package is published. Every URL on this page is rooted at
|
|
10
|
+
github.com/filelayer/filelayer and npmjs.com/package/@filelayer/core; both
|
|
11
|
+
namespaces are unclaimed and reserved for this project, so the badges
|
|
12
|
+
resolve on their own with no edit here. The licence badge is live now. -->
|
|
13
|
+
|
|
14
|
+
> ## ⚠️ Alpha — developer preview. Not production software.
|
|
15
|
+
>
|
|
16
|
+
> We would rather you trust us later for good reasons than trust us now for bad
|
|
17
|
+
> ones, so here is the honest state of this project.
|
|
18
|
+
>
|
|
19
|
+
> **What is tested.** The authorization engine is the part we stand behind. It
|
|
20
|
+
> carries a property suite covering cross-tenant isolation, revocation,
|
|
21
|
+
> delegation attenuation, download caps, audit tamper-evidence and the full role
|
|
22
|
+
> matrix, plus a differential test asserting that the set query and the point
|
|
23
|
+
> check agree exactly, plus an adversarial suite of attacks it must survive. All
|
|
24
|
+
> of it runs in CI on every commit, against real PostgreSQL. A release gate packs
|
|
25
|
+
> the tarball, installs it into an empty directory and drives the whole lifecycle
|
|
26
|
+
> as a stranger would.
|
|
27
|
+
>
|
|
28
|
+
> **What has never run against live infrastructure.** The S3/R2 storage adapter
|
|
29
|
+
> has **never been executed against real AWS or Cloudflare credentials.** It is
|
|
30
|
+
> exercised against a local implementation that verifies SigV4 signatures, which
|
|
31
|
+
> is not the same thing and we will not pretend it is. A live-credential suite
|
|
32
|
+
> exists and runs automatically once `FILELAYER_TEST_S3_*` is in the
|
|
33
|
+
> environment; nobody has supplied it yet.
|
|
34
|
+
>
|
|
35
|
+
> **What may break.** Nobody has deployed this. There is no production usage, no
|
|
36
|
+
> hosted service, no CLI, and no operational track record — so the failure modes
|
|
37
|
+
> that only appear under real traffic, real object stores and real connection
|
|
38
|
+
> pools are unmeasured. Expect to be the person who finds them.
|
|
39
|
+
>
|
|
40
|
+
> **The schema may change before 1.0.** It has already had one breaking change.
|
|
41
|
+
> Any `0.x` → `0.(x+1)` may break the API, the schema, or both. Every break is
|
|
42
|
+
> in the [changelog](https://github.com/filelayer/filelayer/blob/main/packages/core/CHANGELOG.md)
|
|
43
|
+
> and every schema break ships with SQL in
|
|
44
|
+
> [MIGRATIONS.md](https://github.com/filelayer/filelayer/blob/main/packages/core/MIGRATIONS.md),
|
|
45
|
+
> but there is no long-term support branch and no backporting.
|
|
46
|
+
>
|
|
47
|
+
> **Use it** to evaluate the model, to build something that is not yet carrying
|
|
48
|
+
> customer data, or to tell us where it breaks. **Do not use it** as the file
|
|
49
|
+
> layer under a production system you would be embarrassed to lose. The full
|
|
50
|
+
> list of known gaps is in [Limitations](#limitations) below; nothing there is
|
|
51
|
+
> hidden in an appendix.
|
|
52
|
+
|
|
53
|
+
**The file layer for SaaS applications.** Public files and private files, with
|
|
54
|
+
one authorization model behind both.
|
|
55
|
+
|
|
56
|
+
You tell Filelayer who the caller is. Filelayer decides what they may do with a
|
|
57
|
+
file, serves the bytes with the right headers, and writes the audit event. You
|
|
58
|
+
do not write authorization rules, RLS policies, bucket ACLs, ownership checks in
|
|
59
|
+
route handlers, or presigned-URL expiry logic — because there is exactly one
|
|
60
|
+
place a decision is made, and it is not in your application.
|
|
61
|
+
|
|
62
|
+
<!-- doccheck-setup
|
|
63
|
+
import { Filelayer } from '@filelayer/core';
|
|
64
|
+
const fl = await Filelayer.quickstart({ baseUrl: 'http://localhost:3000' });
|
|
65
|
+
const bytes = new TextEncoder().encode('hello');
|
|
66
|
+
-->
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// Public avatar
|
|
70
|
+
const { url } = await fl.files.put(bytes, { public: true });
|
|
71
|
+
|
|
72
|
+
// Private, user-owned
|
|
73
|
+
const { id } = await fl.files.put(bytes, { owner: 'user_123' });
|
|
74
|
+
const file = await fl.files.get(id, { as: 'user_123' });
|
|
75
|
+
|
|
76
|
+
// Multi-tenant, role-controlled
|
|
77
|
+
await fl.files.put(bytes, { org: 'acme', owner: 'user_123' });
|
|
78
|
+
|
|
79
|
+
// Shared with an expiry, a password and a download cap — and revocable
|
|
80
|
+
const share = await fl.shares.create(id, {
|
|
81
|
+
as: 'user_123', expiresIn: 3600, maxDownloads: 3, password: 'hunter2',
|
|
82
|
+
});
|
|
83
|
+
await fl.shares.revoke(share.grantId, { as: 'user_123' }); // takes effect now
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Start at whichever line matches your problem.** Complexity is incremental:
|
|
87
|
+
each tier adds one concept, and no tier makes you pay for a concept you are not
|
|
88
|
+
using. → [`docs/QUICKSTART.md`](https://github.com/filelayer/filelayer/blob/main/docs/QUICKSTART.md)
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Status: pre-release. Read this part.
|
|
93
|
+
|
|
94
|
+
Filelayer is pre-1.0 and has not been deployed by anyone. This README is
|
|
95
|
+
accurate rather than promotional, because you are more likely to be an AI agent
|
|
96
|
+
reading it to write an integration than a human reading it to be persuaded, and
|
|
97
|
+
an inaccurate README wastes your time and ours.
|
|
98
|
+
|
|
99
|
+
- **Licensed under [Apache-2.0](https://github.com/filelayer/filelayer/blob/main/LICENSE).**
|
|
100
|
+
You may use it, modify it, distribute it and ship it inside commercial
|
|
101
|
+
software, with a patent grant. This was the single largest blocker to adoption
|
|
102
|
+
and it is resolved: `packages/core/package.json` declares
|
|
103
|
+
`"license": "Apache-2.0"`, and both `LICENSE` and
|
|
104
|
+
[`NOTICE`](https://github.com/filelayer/filelayer/blob/main/NOTICE) ship
|
|
105
|
+
inside the npm tarball. See [License](#license) below.
|
|
106
|
+
- The authorization core is tested: a property suite covering cross-tenant
|
|
107
|
+
isolation, revocation, delegation attenuation, download caps, audit
|
|
108
|
+
tamper-evidence and the full role matrix, plus a differential test that
|
|
109
|
+
asserts the set query and the point check agree exactly.
|
|
110
|
+
- The S3/R2 storage adapter is exercised against a **signature-verifying local
|
|
111
|
+
S3 implementation** (`packages/core/test/storage.test.ts`). It has **never
|
|
112
|
+
been run against live AWS or Cloudflare credentials.** A live-credential suite
|
|
113
|
+
exists (`packages/core/test/s3-live.test.ts`) and runs automatically when
|
|
114
|
+
`FILELAYER_TEST_S3_*` is present in the environment; nobody has supplied it.
|
|
115
|
+
- There is no hosted service and no CLI. You run it against your own Postgres.
|
|
116
|
+
- Versioning is pre-1.0: see [Versioning](#versioning) below and
|
|
117
|
+
[`packages/core/MIGRATIONS.md`](https://github.com/filelayer/filelayer/blob/main/packages/core/MIGRATIONS.md).
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## What it is for, and what it is not for
|
|
122
|
+
|
|
123
|
+
**Worth the overhead when:** files are private, belong to specific people or
|
|
124
|
+
tenants, and their permissions change over time. Documents, contracts,
|
|
125
|
+
attachments, exports, anything with a share link you might later want back.
|
|
126
|
+
|
|
127
|
+
**Not worth the overhead when:**
|
|
128
|
+
|
|
129
|
+
| You need | Use instead | Why |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| Public images at CDN volume | a CDN-backed bucket | Default delivery proxies every byte. Redirect delivery (below) removes the proxy but is opt-in and narrow. |
|
|
132
|
+
| Video or audio seeking in a browser | a CDN / media service | The shipped HTTP route helpers do not answer `Range` requests. |
|
|
133
|
+
| Direct browser → storage upload | Supabase / presigned S3 | Uploads go through your server. |
|
|
134
|
+
| Thumbnails, transforms, format negotiation | Cloudinary / imgix | We have none. |
|
|
135
|
+
|
|
136
|
+
We publish the full comparison, including the cases we lose, in
|
|
137
|
+
[`ARCHITECTURE-PROGRESSIVE.md`](https://github.com/filelayer/filelayer/blob/main/ARCHITECTURE-PROGRESSIVE.md)
|
|
138
|
+
§5. Short version: **for a public avatar, Supabase is 10 lines and Filelayer is
|
|
139
|
+
16.** If avatars are your whole problem, use Supabase.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## The five security properties
|
|
144
|
+
|
|
145
|
+
These are the product. Each is enforced in the schema or the authorization
|
|
146
|
+
engine, not by convention, and each has tests named after it.
|
|
147
|
+
|
|
148
|
+
| | Property | What it means in practice |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| **P1** | Deny by default | There is no `public` boolean anywhere in the schema. Public delivery is an explicit, revocable, auditable grant row. "The bucket was public" is not expressible. |
|
|
151
|
+
| **P2** | No ambient authority | Knowing an object key, a URL or a file id grants nothing. Storage location is never an input to a decision. |
|
|
152
|
+
| **P3** | Structural tenant isolation | A grant's `org_id` must equal its file's `org_id`, enforced by a composite foreign key. Cross-tenant access is unrepresentable, not merely prevented by a `WHERE` clause. |
|
|
153
|
+
| **P4** | A URL never outlives its permission | Every signed URL embeds a grant id and is re-validated on **every** request, transitively through the whole delegation chain. Revocation beats a live URL. |
|
|
154
|
+
| **P5** | Every decision is audited, including denials | Hash-chained per tenant. Probes that cannot be attributed to a tenant go to a system chain rather than being dropped. |
|
|
155
|
+
|
|
156
|
+
Three more properties are enforced in the schema and documented in
|
|
157
|
+
[`packages/core/SEMANTICS.md`](https://github.com/filelayer/filelayer/blob/main/packages/core/SEMANTICS.md): atomic download
|
|
158
|
+
counters (**P6**), deletion as a liveness predicate rather than a cascade
|
|
159
|
+
(**P7**), and per-project identifier namespaces (**P8**).
|
|
160
|
+
|
|
161
|
+
Two consequences worth knowing before you adopt:
|
|
162
|
+
|
|
163
|
+
- **Revocation actually works.** `fl.files.unpublish(id)` makes a URL that has
|
|
164
|
+
been printed, indexed and pasted into a support ticket stop working on the
|
|
165
|
+
next request — no deletion, no key rotation, no cache purge. Supabase's
|
|
166
|
+
`getPublicUrl()` is offline string concatenation, so it has no request at
|
|
167
|
+
which to make that decision; deleting the object is the only withdrawal.
|
|
168
|
+
- **This costs you a byte path.** P4 is why the default is to serve bytes rather
|
|
169
|
+
than hand out a presigned URL, and it is why the default path has no CDN in
|
|
170
|
+
front of it. The two facts are the same fact. Redirect delivery trades a
|
|
171
|
+
bounded revocation window for that CDN and is opt-in; see
|
|
172
|
+
[`packages/core/SEMANTICS.md`](https://github.com/filelayer/filelayer/blob/main/packages/core/SEMANTICS.md).
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Install
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm install @filelayer/core
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Requires **Node ≥ 22.18**. The package ships compiled JavaScript and type
|
|
183
|
+
declarations in `dist/`; the TypeScript source and the full test suite are in
|
|
184
|
+
the tarball as well, so every claim on this page is inspectable from what you
|
|
185
|
+
installed.
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
import { Filelayer } from '@filelayer/core';
|
|
189
|
+
|
|
190
|
+
const fl2 = await Filelayer.quickstart(); // PGlite + in-memory bytes
|
|
191
|
+
const { url: avatarUrl } = await fl2.files.put(bytes, { public: true });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`quickstart()` is **ephemeral** — everything is lost when the process exits.
|
|
195
|
+
Production is three configuration steps and is not hidden:
|
|
196
|
+
[`docs/QUICKSTART.md`](https://github.com/filelayer/filelayer/blob/main/docs/QUICKSTART.md)
|
|
197
|
+
§6.
|
|
198
|
+
|
|
199
|
+
### Running the suite
|
|
200
|
+
|
|
201
|
+
The tests are in the tarball but they cannot be executed from inside
|
|
202
|
+
`node_modules`: Node refuses to strip types from files under `node_modules`, and
|
|
203
|
+
the tests import the TypeScript source directly. To run them, clone the
|
|
204
|
+
repository — tests run against PGlite, PostgreSQL 17 compiled to WebAssembly and
|
|
205
|
+
running in-process, so there is no daemon and no Docker:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
git clone https://github.com/filelayer/filelayer && cd filelayer
|
|
209
|
+
npm run bootstrap # npm ci in packages/core
|
|
210
|
+
npm test # the security property suite, 313 tests
|
|
211
|
+
npm run typecheck
|
|
212
|
+
npm run verify # typecheck + tests + build + doc and language checks
|
|
213
|
+
npm run example:tier1 # a public avatar, on :3000
|
|
214
|
+
npm run example:vault # the full Vault app, on :8787
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Versioning
|
|
220
|
+
|
|
221
|
+
Pre-1.0. The version is `0.MINOR.PATCH` and the promise is deliberately narrow:
|
|
222
|
+
|
|
223
|
+
- **`0.x` → `0.(x+1)`** may break the API, the schema, or both. Every break is
|
|
224
|
+
in [`packages/core/CHANGELOG.md`](https://github.com/filelayer/filelayer/blob/main/packages/core/CHANGELOG.md), and every
|
|
225
|
+
schema break has a migration in
|
|
226
|
+
[`packages/core/MIGRATIONS.md`](https://github.com/filelayer/filelayer/blob/main/packages/core/MIGRATIONS.md).
|
|
227
|
+
- **`0.x.y` → `0.x.(y+1)`** is additive or a fix. No schema change that requires
|
|
228
|
+
action, no signature change.
|
|
229
|
+
- There is no long-term support branch and no backporting before 1.0.
|
|
230
|
+
|
|
231
|
+
The schema has already had one breaking change (per-project identifier
|
|
232
|
+
namespaces). It is written up, with the SQL, as the first entry in
|
|
233
|
+
[`packages/core/MIGRATIONS.md`](https://github.com/filelayer/filelayer/blob/main/packages/core/MIGRATIONS.md).
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Layout
|
|
238
|
+
|
|
239
|
+
| Path | What it is |
|
|
240
|
+
|---|---|
|
|
241
|
+
| `packages/core/schema.sql` | The data model. Every security property is commented at the constraint that enforces it. Read this first if you are reviewing us. |
|
|
242
|
+
| `packages/core/src/authz.ts` | The authorization engine. Two functions, one decision core. |
|
|
243
|
+
| `packages/core/src/simple.ts` | The tiered API (`files`, `orgs`, `shares`). No authorization logic — a facade. |
|
|
244
|
+
| `packages/core/src/delivery.ts` | Byte delivery. Owns the response headers so you cannot get them wrong. |
|
|
245
|
+
| `packages/core/test/` | The property suite. Ships in the tarball. |
|
|
246
|
+
| `packages/core/dev/` | Diagnostic scripts. Not published. |
|
|
247
|
+
| `examples/tier1-avatar` … `tier3-org-roles` | One runnable example per tier |
|
|
248
|
+
| `examples/vault` | A full B2B document workspace over HTTP |
|
|
249
|
+
|
|
250
|
+
## Documents
|
|
251
|
+
|
|
252
|
+
- [`docs/QUICKSTART.md`](https://github.com/filelayer/filelayer/blob/main/docs/QUICKSTART.md) — install → first file → the advanced capabilities
|
|
253
|
+
- [`packages/core/SEMANTICS.md`](https://github.com/filelayer/filelayer/blob/main/packages/core/SEMANTICS.md) — the exact behaviour of every edge: liveness, delegation, deletion, the audit chain
|
|
254
|
+
- [`packages/core/MIGRATIONS.md`](https://github.com/filelayer/filelayer/blob/main/packages/core/MIGRATIONS.md) — how schema changes are delivered and what the pre-1.0 compatibility promise is
|
|
255
|
+
- [`packages/core/CHANGELOG.md`](https://github.com/filelayer/filelayer/blob/main/packages/core/CHANGELOG.md) — the real history, including the security defects we found in ourselves
|
|
256
|
+
- [`ARCHITECTURE-PROGRESSIVE.md`](https://github.com/filelayer/filelayer/blob/main/ARCHITECTURE-PROGRESSIVE.md) — the tier model, lines of code versus the alternatives, and where we lose
|
|
257
|
+
- [`architecture/TIER5-DESIGN-NOTE.md`](https://github.com/filelayer/filelayer/blob/main/architecture/TIER5-DESIGN-NOTE.md) — large files, streaming, range requests, CDN
|
|
258
|
+
- [`llms.txt`](https://github.com/filelayer/filelayer/blob/main/llms.txt) — the same map, written for an agent implementing an integration
|
|
259
|
+
- [`openapi.yaml`](https://github.com/filelayer/filelayer/blob/main/openapi.yaml) — the two HTTP routes the library serves, as OpenAPI 3.1
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
|
|
263
|
+
**[Apache-2.0](https://github.com/filelayer/filelayer/blob/main/LICENSE).**
|
|
264
|
+
Commercial use, modification, distribution and private use are permitted, with
|
|
265
|
+
an express patent grant and a patent-retaliation termination clause. You must
|
|
266
|
+
preserve the copyright and licence notices and state significant changes; there
|
|
267
|
+
is no copyleft obligation on your own code.
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
SPDX-License-Identifier: Apache-2.0
|
|
271
|
+
Copyright 2026 Technology Pro Bono S.L.
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`LICENSE` is the unmodified licence text from apache.org.
|
|
275
|
+
[`NOTICE`](https://github.com/filelayer/filelayer/blob/main/NOTICE) carries the
|
|
276
|
+
attribution notice required by section 4(d); both are inside the published npm
|
|
277
|
+
tarball, so the grant travels with the artifact rather than only with the
|
|
278
|
+
repository. There are no per-file licence headers — the grant is carried by
|
|
279
|
+
`LICENSE`, `NOTICE` and the `license` field of every `package.json`.
|
|
280
|
+
|
|
281
|
+
**Dependencies.** Filelayer has exactly one runtime dependency,
|
|
282
|
+
[`@electric-sql/pglite`](https://github.com/electric-sql/pglite), itself
|
|
283
|
+
Apache-2.0. It is installed from the registry rather than vendored, ships no
|
|
284
|
+
`NOTICE` file of its own, and is recorded in ours for convenience. No
|
|
285
|
+
third-party code is copied or embedded anywhere in this repository.
|
|
286
|
+
|
|
287
|
+
## Contributing, security and conduct
|
|
288
|
+
|
|
289
|
+
- [`CONTRIBUTING.md`](https://github.com/filelayer/filelayer/blob/main/CONTRIBUTING.md)
|
|
290
|
+
— how to set up, what `npm run verify` covers, and what a pull request needs.
|
|
291
|
+
- [`SECURITY.md`](https://github.com/filelayer/filelayer/blob/main/SECURITY.md)
|
|
292
|
+
— **report vulnerabilities privately**, never as a public issue. Includes our
|
|
293
|
+
disclosure timetable and what is in and out of scope.
|
|
294
|
+
- [`CODE_OF_CONDUCT.md`](https://github.com/filelayer/filelayer/blob/main/CODE_OF_CONDUCT.md)
|
|
295
|
+
— Contributor Covenant 2.1.
|
|
296
|
+
|
|
297
|
+
Bugs, questions and "this document is wrong" reports go to
|
|
298
|
+
[GitHub Issues](https://github.com/filelayer/filelayer/issues). Given the alpha
|
|
299
|
+
status at the top of this page, a report that the product does not do what this
|
|
300
|
+
README says is the most valuable thing you can send us.
|
|
301
|
+
|
|
302
|
+
## Limitations
|
|
303
|
+
|
|
304
|
+
Restated here so they are not only in an appendix. Each one is current as of
|
|
305
|
+
`0.3.0`; where a limitation has been lifted since an earlier release, the
|
|
306
|
+
[changelog](https://github.com/filelayer/filelayer/blob/main/packages/core/CHANGELOG.md) says so.
|
|
307
|
+
|
|
308
|
+
1. **No `Range` responses from the shipped HTTP routes.** `fileDownloadRoute()`
|
|
309
|
+
and `shareDownloadRoute()` do not parse the `Range` request header, so they
|
|
310
|
+
never return `206` and a browser cannot seek. Do not use the shipped routes
|
|
311
|
+
for video or audio. The layer underneath is complete: `readStream()` and
|
|
312
|
+
`redeemStream()` take a byte range, the S3 adapter honours it, and
|
|
313
|
+
`sendNodeStream()` / `toStreamResponse()` emit `206` with `Content-Range`
|
|
314
|
+
whenever a range was served. Parsing the request header is the part you
|
|
315
|
+
write — see
|
|
316
|
+
[QUICKSTART §6](https://github.com/filelayer/filelayer/blob/main/docs/QUICKSTART.md)
|
|
317
|
+
for the whole thing.
|
|
318
|
+
2. **The tiered facade `fl.files.put()` takes a `Uint8Array`**, so a file put
|
|
319
|
+
through it is fully resident in memory. The core `fl.upload()` accepts a
|
|
320
|
+
`ReadableStream`; use that above a few tens of megabytes.
|
|
321
|
+
3. **No direct browser → storage upload.** Upload bytes go through your server.
|
|
322
|
+
4. **Org admins and owners can read `private` files.** Deliberate — retention
|
|
323
|
+
and legal hold are their responsibility — but if you need to exclude the
|
|
324
|
+
operator, you need envelope encryption and we do not have it.
|
|
325
|
+
5. **Identifiers are unique per *project*, not per org.** `actor.external_id`
|
|
326
|
+
and `org.external_id` are scoped to a project (one customer application). Two
|
|
327
|
+
orgs inside one project cannot both have a user called `alice` meaning
|
|
328
|
+
different people.
|
|
329
|
+
6. **The S3/R2 adapter has never run against live credentials.** It is tested
|
|
330
|
+
against a local implementation that verifies SigV4 signatures, which is not
|
|
331
|
+
the same thing.
|
|
332
|
+
7. **Unauthenticated callers can still append denial events to the audit chain
|
|
333
|
+
of a tenant inside a project they can reach.** That is P5 working as designed
|
|
334
|
+
— denials are the events worth recording — but it is a load-bearing reason to
|
|
335
|
+
rate-limit at ingest. `orgExists` is project-scoped, so the reach is bounded
|
|
336
|
+
to a project the caller is already authenticated for.
|
|
337
|
+
8. **Orphan collection is a job you have to run.** Bytes are written before the
|
|
338
|
+
metadata commits, so a crash in between leaves an unreferenced object.
|
|
339
|
+
`collectStorageOrphans()` cleans them up and nothing calls it for you.
|
|
340
|
+
9. **Redirect delivery has a revocation window.** If you enable it, a presigned
|
|
341
|
+
URL stays valid for up to its TTL after the grant is revoked. It is off by
|
|
342
|
+
default, defaults to anonymous grants only, and requires passing a verbatim
|
|
343
|
+
acknowledgement string. That string is the point.
|