@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/CHANGELOG.md
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@filelayer/core`.
|
|
4
|
+
|
|
5
|
+
Format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
Versioning is pre-1.0 and is explained in [`MIGRATIONS.md`](MIGRATIONS.md) §1.
|
|
7
|
+
|
|
8
|
+
## A note on honesty, before the entries
|
|
9
|
+
|
|
10
|
+
**No version of this package has been published to npm.** The versions below are
|
|
11
|
+
real, dated development milestones in this repository, not registry releases. We
|
|
12
|
+
are writing them up as a changelog rather than starting the history at the first
|
|
13
|
+
publish because a consumer deciding whether to depend on a `0.x` library is
|
|
14
|
+
entitled to know what has already moved underneath it, and because most of the
|
|
15
|
+
entries are security defects we found in our own code.
|
|
16
|
+
|
|
17
|
+
They are described the way we found them, including the ones that were
|
|
18
|
+
embarrassing. A changelog that only records features is a marketing document.
|
|
19
|
+
|
|
20
|
+
`0.3.0` is the version prepared for the first publish.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## [Unreleased] — group grant subjects (RFC-001)
|
|
25
|
+
|
|
26
|
+
Breaking schema change, additive API. Migration: `MIGRATIONS.md` Entry 2.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **A byte range could be sent as `HTTP 200`, which is silent data corruption.**
|
|
31
|
+
`readStream()` and `redeemStream()` accept a byte range and attach
|
|
32
|
+
`Content-Range` when the store serves one, but `sendNodeStream()` and
|
|
33
|
+
`toStreamResponse()` both hardcoded `200`. Anyone following the documented
|
|
34
|
+
advice to wire the `Range` request header themselves emitted a **truncated
|
|
35
|
+
body under a 200** — which every HTTP client treats as the complete
|
|
36
|
+
representation, so it is cached, hashed and handed on with no error anywhere.
|
|
37
|
+
Both writers now derive the status from the delivery: `206` with
|
|
38
|
+
`Content-Range` when a range was served, `200` otherwise. There is deliberately
|
|
39
|
+
no status parameter — there is no value a caller could supply that the writer
|
|
40
|
+
does not already know, and every value they could supply by mistake is wrong.
|
|
41
|
+
Covered by four tests in `test/delivery.test.ts`.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **`grant_subject := actor | org | role | link | anonymous`.** A grant's
|
|
46
|
+
subject is a principal set, and `org` / `role` are the missing middle between
|
|
47
|
+
"one person" and "everyone". Two new columns on `file_grant`,
|
|
48
|
+
`subject_org_id` and `subject_min_role`.
|
|
49
|
+
- **Resolution is a join, never a materialization.** Adding or removing an org
|
|
50
|
+
member changes access on the very next request, with no recomputation and no
|
|
51
|
+
write to any grant row. Asserted by fingerprinting every `file_grant` row
|
|
52
|
+
across a join, a leave and a role change.
|
|
53
|
+
- **Cross-org grants inside a project are the point** ("the company that
|
|
54
|
+
posted this job may read this CV"). Cross-**project** ones are
|
|
55
|
+
unrepresentable, by composite foreign key.
|
|
56
|
+
- No custom groups, no nested orgs, no configurable inheritance, no deny
|
|
57
|
+
rules. `subject_min_role` is only a threshold over the existing
|
|
58
|
+
`viewer | member | admin | owner` enum.
|
|
59
|
+
- **I6 — subject breadth may not be amplified by delegation.** An issuer whose
|
|
60
|
+
authority is role-derived may mint any subject type; an issuer whose authority
|
|
61
|
+
is grant-derived may mint only `actor` or `link`. Enforced in
|
|
62
|
+
`authorizeShare()` (reason `subject_breadth_amplification`, `403`, audited)
|
|
63
|
+
**and** in the `file_grant_attenuate()` trigger (`grant_subject_amplification`),
|
|
64
|
+
so it binds a caller issuing raw SQL. The trigger now also fires on the subject
|
|
65
|
+
columns, so a delegated grant cannot be widened by a later `UPDATE`.
|
|
66
|
+
- `AuthzPath` gains `grant:org` and `grant:role`. Group allows carry `viaOrgId`,
|
|
67
|
+
`viaMinRole` and `viaRole` in the audit context, so a compliance auditor can
|
|
68
|
+
see which membership conferred access.
|
|
69
|
+
- `shares.create(fileId, { as, withOrg, minRole })` on the tiered API.
|
|
70
|
+
`withOrg` does **not** auto-provision a tenant: an unknown org is `404`.
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- `grant_scope_is_live()` takes a fourth argument and gains one term: a grant is
|
|
75
|
+
live only while its **subject org** is live (I2). Deleting an org now kills the
|
|
76
|
+
grants held by its members, as it already killed the grants on its files.
|
|
77
|
+
- `file.visibility = 'org'` is **retained**, and is now describable as an
|
|
78
|
+
implicit org grant.
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
|
|
82
|
+
- **`getActorGrants` had no `ORDER BY`, and the delegation parent depended on
|
|
83
|
+
it.** `resolveStanding` attributes an allow to the first returned grant
|
|
84
|
+
carrying the requested capability, and on the share path that grant becomes
|
|
85
|
+
the child's `parent_grant_id` — the ceiling the attenuation trigger measures
|
|
86
|
+
against. With no ordering, Postgres returned heap order, which is undefined
|
|
87
|
+
and moves with page layout, `VACUUM` and row width. A principal holding
|
|
88
|
+
several grants on one file could therefore have `share()` succeed or fail
|
|
89
|
+
depending on physical storage. Fail-closed, never a disclosure, but not a
|
|
90
|
+
semantics anyone could document. Now ordered oldest-first. Found while adding
|
|
91
|
+
the columns above, which was enough to flip it.
|
|
92
|
+
|
|
93
|
+
### Known, recorded rather than hidden
|
|
94
|
+
|
|
95
|
+
- `authorizeShare` computes the issuer's held capabilities as the **union** over
|
|
96
|
+
every source, while `parent_grant_id` is a **single** grant. A principal
|
|
97
|
+
holding several grants on one file can therefore be approved for a capability
|
|
98
|
+
set that no one ancestor covers, and the attenuation trigger then refuses the
|
|
99
|
+
insert. The outcome is a `403` rather than a disclosure, so P4 is intact; what
|
|
100
|
+
is wrong is that a legitimate delegation can be refused. Closing it means
|
|
101
|
+
changing the delegation model (pick a covering parent, or mint one child per
|
|
102
|
+
contributing ancestor) and was out of scope for RFC-001.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## [0.3.0] — 2026-09-05
|
|
107
|
+
|
|
108
|
+
The release that makes the package installable, and the one that closes the
|
|
109
|
+
last cross-tenant defect.
|
|
110
|
+
|
|
111
|
+
### Added
|
|
112
|
+
|
|
113
|
+
- **Compiled output.** The package now ships `dist/` (JavaScript plus `.d.ts`)
|
|
114
|
+
and its `exports` point at it. Previously `exports` pointed at `src/index.ts`,
|
|
115
|
+
which is not importable from an install: Node refuses to strip types from
|
|
116
|
+
files under `node_modules`
|
|
117
|
+
(`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`). The package was installable
|
|
118
|
+
and unusable.
|
|
119
|
+
- **The TypeScript source and the full test suite ship in the tarball**, so
|
|
120
|
+
every claim in the README is inspectable from what you installed. They cannot
|
|
121
|
+
be *run* from `node_modules` for the reason above; clone the repository.
|
|
122
|
+
- `SEMANTICS.md`, `MIGRATIONS.md`, `CHANGELOG.md` and `LICENSE` are in the
|
|
123
|
+
tarball.
|
|
124
|
+
- **A transaction abstraction.** `withTransaction()`, `Tx`, and `Queryable`
|
|
125
|
+
gaining an optional `withTransaction` hook. `pg.Pool` is now used through
|
|
126
|
+
`connect()`, so a transaction stays on one connection instead of scattering
|
|
127
|
+
across the pool.
|
|
128
|
+
- **Streaming.** `upload()` accepts a `ReadableStream`; the storage interface
|
|
129
|
+
gained `stream()`, `head()` and an optional `list()`; delivery can stream
|
|
130
|
+
rather than buffer. `toStreamResponse()` returns a streaming WHATWG
|
|
131
|
+
`Response`.
|
|
132
|
+
- **Byte-range reads** at the storage and delivery API level. The shipped HTTP
|
|
133
|
+
route helpers still do not parse the `Range` request header and never return
|
|
134
|
+
`206`.
|
|
135
|
+
- **Redirect delivery** — an opt-in mode that hands out a short-lived presigned
|
|
136
|
+
URL instead of proxying bytes, for deployments that need a CDN. Off by
|
|
137
|
+
default; restricted to anonymous grants unless widened; requires passing a
|
|
138
|
+
verbatim acknowledgement constant, because it trades a bounded revocation
|
|
139
|
+
window for the byte path and that trade must be made deliberately.
|
|
140
|
+
- `collectStorageOrphans()` — collection for objects written by an upload whose
|
|
141
|
+
metadata never committed.
|
|
142
|
+
|
|
143
|
+
### Fixed
|
|
144
|
+
|
|
145
|
+
- **Cross-project identity collision (HIGH).** `org.external_id` and
|
|
146
|
+
`actor.external_id` were globally unique. In any deployment where more than
|
|
147
|
+
one application shares a database, application B calling `put({ org: 'acme'
|
|
148
|
+
})` did not get an error — it got application A's org id, and the identity
|
|
149
|
+
resolver then added B's user to A's tenant as a `member`. Same shape for
|
|
150
|
+
actors, so `as: 'alice'` resolved across the boundary too. A complete
|
|
151
|
+
cross-tenant compromise reachable from the most ergonomic entry point in the
|
|
152
|
+
library. Fixed by introducing a **project** scope above the tenant and making
|
|
153
|
+
every access-bearing table carry `project_id` under a composite foreign key,
|
|
154
|
+
so a cross-project row is unrepresentable rather than merely unlikely.
|
|
155
|
+
**Breaking schema change — see [`MIGRATIONS.md`](MIGRATIONS.md) entry 1.**
|
|
156
|
+
- **A soft-deleted org stopped conferring membership but did not kill
|
|
157
|
+
outstanding grants.** Deleting a tenant removed the owner's access and left a
|
|
158
|
+
contractor's share link serving bytes. Nobody chose that; it was an emergent
|
|
159
|
+
asymmetry between two store methods, found because the set query and the point
|
|
160
|
+
check disagreed. Grant liveness is now a predicate over the whole scope — the
|
|
161
|
+
file, its org, that org's project, the subject actor and the issuing actor —
|
|
162
|
+
evaluated on every request, with no cascading write, so a restore revives
|
|
163
|
+
exactly what the delete killed.
|
|
164
|
+
- **`maxDownloads` was charged only on the share-link path.** An actor grant
|
|
165
|
+
carrying `maxDownloads: 3` permitted unlimited direct authenticated reads,
|
|
166
|
+
because nothing on that path touched the counter. The field meant "link
|
|
167
|
+
redemptions" on one path and nothing at all on the other, while being named,
|
|
168
|
+
documented and billed as a download cap. A download is now charged whenever
|
|
169
|
+
bytes leave through a grant, by any principal, on any path. Authority derived
|
|
170
|
+
from an org role is not charged (a role is not a metered credential) and
|
|
171
|
+
metadata reads are not charged (no bytes leave).
|
|
172
|
+
- **The audit hash chain could fork under concurrent writers.** It was built
|
|
173
|
+
with a `SELECT` of the last hash followed by an `INSERT`, from application
|
|
174
|
+
code, in two statements — atomic only under a single serialized writer. It is
|
|
175
|
+
now one call to `audit_append()`, which takes `pg_advisory_xact_lock` on the
|
|
176
|
+
chain, reads the predecessor and inserts, inside one statement. The library
|
|
177
|
+
runs that in the same transaction as the mutation it describes, so the lock
|
|
178
|
+
covers both. Caveat stated plainly: the suite runs on PGlite, which has one
|
|
179
|
+
backend, so the test proves the lock is taken on the write path — the
|
|
180
|
+
multi-process argument rests on Postgres advisory-lock semantics.
|
|
181
|
+
- **The audit write was not in the same transaction as the thing it audited.**
|
|
182
|
+
`put()` performed five independent statements; a failure after the second left
|
|
183
|
+
a file with no audit record. For a product whose selling point is a
|
|
184
|
+
tamper-evident trail, an intact chain that simply does not mention what
|
|
185
|
+
happened is a hole in the premise.
|
|
186
|
+
- **`file.storage_provider` was written as the literal `'memory'` on every
|
|
187
|
+
insert**, regardless of the configured adapter, so a production deployment
|
|
188
|
+
recorded every object as living in an in-process map. It participates in a
|
|
189
|
+
unique index with the key and is therefore half of an object's identity, not a
|
|
190
|
+
label.
|
|
191
|
+
- **Unauthenticated audit-chain growth is now bounded.** A caller probing org
|
|
192
|
+
ids can only reach a tenant chain inside a project they are already
|
|
193
|
+
authenticated for; probes at every other id land on the system chain. Still
|
|
194
|
+
requires rate limiting at ingest — denials are audited by design and that is
|
|
195
|
+
not negotiable — but the reach went from "any internet caller, any tenant" to
|
|
196
|
+
"an authenticated customer, their own tenant".
|
|
197
|
+
|
|
198
|
+
### Changed
|
|
199
|
+
|
|
200
|
+
- **Breaking:** the schema requires a `project` row. A default project is
|
|
201
|
+
inserted by `schema.sql`, so a single-application deployment needs no project
|
|
202
|
+
vocabulary.
|
|
203
|
+
- **Breaking:** `engines.node` is `>=22.18` and enforced.
|
|
204
|
+
- **Licensed under Apache-2.0.** `license` was `UNLICENSED` and `LICENSE` was an
|
|
205
|
+
explicit placeholder granting no rights; both are resolved before this version
|
|
206
|
+
is published. `LICENSE` is now the verbatim Apache License 2.0 and ships in
|
|
207
|
+
the tarball alongside a `NOTICE` file, so the grant travels with the package
|
|
208
|
+
rather than only with the repository. See
|
|
209
|
+
[LICENSE](https://github.com/filelayer/filelayer/blob/main/LICENSE) and
|
|
210
|
+
[NOTICE](https://github.com/filelayer/filelayer/blob/main/NOTICE).
|
|
211
|
+
- Scratch and diagnostic scripts moved out of the package root into `dev/` and
|
|
212
|
+
are excluded from the tarball. The `package.json` scripts that referenced them
|
|
213
|
+
moved to the repository root.
|
|
214
|
+
- Documentation was swept of internal review vocabulary, and a CI check now
|
|
215
|
+
fails the build if it comes back.
|
|
216
|
+
|
|
217
|
+
### Known limitations at this version
|
|
218
|
+
|
|
219
|
+
Listed in the README, and repeated here because a changelog that only records
|
|
220
|
+
wins is not one: no `Range` responses from the shipped routes; no direct
|
|
221
|
+
browser-to-storage upload; org admins can read `private` files; identifiers are
|
|
222
|
+
per-project, not per-org; the S3/R2 adapter has never run against live
|
|
223
|
+
credentials; orphan collection and ingest rate limiting are jobs you schedule.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## [0.2.0] — 2026-09-05
|
|
228
|
+
|
|
229
|
+
The security rebuild. Twelve defects found by an independent review of the
|
|
230
|
+
authorization core plus five found while fixing them. This is the release where
|
|
231
|
+
the delivery layer stopped being the developer's problem.
|
|
232
|
+
|
|
233
|
+
### Fixed — authorization
|
|
234
|
+
|
|
235
|
+
- **A delegated grant outlived the grant that created it (HIGH).** Revoking a
|
|
236
|
+
grant left everything delegated from it alive, which meant the central claim —
|
|
237
|
+
a URL never outlives its permission — was true only for directly-issued
|
|
238
|
+
grants. Liveness is now evaluated over the whole ancestor chain, in the
|
|
239
|
+
schema, so revocation is transitive at any depth with no cascading write.
|
|
240
|
+
- **Capability amplification through `share` (HIGH).** A holder of `read` could
|
|
241
|
+
mint a grant carrying `delete`. Attenuation is now enforced by the
|
|
242
|
+
authorization engine *and* again by a `BEFORE INSERT` trigger, so it binds a
|
|
243
|
+
migration or a `psql` session as well as an API call.
|
|
244
|
+
- **Membership management had no authorization and no audit (HIGH).** Anyone who
|
|
245
|
+
could reach the method could change anyone's role. Now: you may not grant a
|
|
246
|
+
role above your own, may not modify anyone who outranks you, may not remove
|
|
247
|
+
the last owner, and every attempt — allowed or denied — is audited.
|
|
248
|
+
- **Holding `share` conferred revoke over every grant on the file.** A
|
|
249
|
+
contractor given the ability to share could revoke the owner's grants.
|
|
250
|
+
Narrowed to the grants in your own delegation subtree.
|
|
251
|
+
- **A `link` grant could carry `delete`.** A leaked share link could destroy the
|
|
252
|
+
file it pointed at. Share links are read-only by database constraint now.
|
|
253
|
+
|
|
254
|
+
### Fixed — the audit trail
|
|
255
|
+
|
|
256
|
+
- **Enumeration left no trace (HIGH).** Decisions that could not be attributed
|
|
257
|
+
to a tenant — a probe against a file id that does not exist, a sweep against
|
|
258
|
+
link secrets — were dropped rather than recorded, which made exactly the
|
|
259
|
+
reconnaissance the log exists to catch invisible. They now go to a system
|
|
260
|
+
chain (`org_id IS NULL`) that no tenant can read.
|
|
261
|
+
- **An exhausted download cap was not audited.** "Why did my link stop working"
|
|
262
|
+
was unanswerable from the log.
|
|
263
|
+
- **The hash chain did not cover the forensic fields.** The digest covered seven
|
|
264
|
+
columns; `reason`, `grant_id`, `ip`, `user_agent` and `context` — the fields an
|
|
265
|
+
incident responder relies on and an attacker would rewrite — were outside the
|
|
266
|
+
commitment, which made the chain decorative for the questions that matter. The
|
|
267
|
+
digest now covers every forensically relevant column, encoded as canonical
|
|
268
|
+
JSON rather than concatenated, because concatenation lets a forger shift field
|
|
269
|
+
boundaries.
|
|
270
|
+
- **A well-formed but unregistered actor id crashed the audit write (HIGH).**
|
|
271
|
+
`audit_event.actor_id` carried a foreign key to `actor`, so a caller
|
|
272
|
+
presenting an unknown-but-valid id could not be audited at all: the insert
|
|
273
|
+
raised, the exception propagated out of `authorize()`, the denial was never
|
|
274
|
+
recorded, and the caller got a 500 where a real stranger gets a 404 — an
|
|
275
|
+
actor-existence oracle and a silent hole in the denial log, at once. The
|
|
276
|
+
foreign key is gone; recording identifiers that do not exist is the audit
|
|
277
|
+
log's job.
|
|
278
|
+
|
|
279
|
+
### Fixed — the error surface and delivery
|
|
280
|
+
|
|
281
|
+
- **The error surface was an existence oracle.** Denials are now uniformly 404
|
|
282
|
+
and the evaluation order that makes that true is itself a security property.
|
|
283
|
+
- **`Content-Disposition` filenames were interpolated, not encoded** — response
|
|
284
|
+
splitting through an uploaded filename. On one comparison implementation the
|
|
285
|
+
same bug turns every share download of an affected document into a permanent
|
|
286
|
+
500.
|
|
287
|
+
- **`upload()` took a bare actor id (HIGH)**, so ownership was forgeable from a
|
|
288
|
+
request body. It takes an authorized principal now.
|
|
289
|
+
- **Byte delivery was the developer's problem (HIGH).** `read()` returned a
|
|
290
|
+
`Uint8Array` and the application chose `Content-Type`,
|
|
291
|
+
`Content-Disposition`, `X-Content-Type-Options` and `Cache-Control` — three
|
|
292
|
+
security-sensitive decisions handed back to the developer by a library
|
|
293
|
+
claiming to have removed them, and the example got them wrong. `get()` now
|
|
294
|
+
returns `headers`, computed from the file record, with no option to disable
|
|
295
|
+
them.
|
|
296
|
+
- **A credential could be passed in a share-link query string.** Refused with
|
|
297
|
+
`400 credential_in_query`, before any work, without consuming a download.
|
|
298
|
+
Query strings end up in access logs, proxy logs and browser history.
|
|
299
|
+
|
|
300
|
+
### Added
|
|
301
|
+
|
|
302
|
+
- **An authorized listing primitive (HIGH — its absence was the defect).** There
|
|
303
|
+
was no way to ask "which files may this caller see?", so building a listing
|
|
304
|
+
screen meant hand-rolling the org filter, the visibility rule, the owner
|
|
305
|
+
check, the role check and a union over the grant table in application SQL.
|
|
306
|
+
`listFiles()` answers it in one query and one audit event, with no filter
|
|
307
|
+
parameter to forget. `test/listing.test.ts` asserts set equality against
|
|
308
|
+
`authorize()` over a randomized corpus on every capability, every run — a set
|
|
309
|
+
query that is too wide *or* too narrow fails the build.
|
|
310
|
+
- **File-level `visibility`.** Every member of an org could previously read
|
|
311
|
+
every file in it. Files are now `private` by default — owner and org admins
|
|
312
|
+
only — and org-wide visibility is something the creator asks for.
|
|
313
|
+
|
|
314
|
+
### Known and reported, not fixed at this version
|
|
315
|
+
|
|
316
|
+
- `authorize()` is file-scoped, so creation authorization sits outside it.
|
|
317
|
+
Narrowed to a single `authorizeOrg('create_file')` call, not eliminated.
|
|
318
|
+
- Unauthenticated callers can append denial events to a known tenant's audit
|
|
319
|
+
chain. Bounded in 0.3.0.
|
|
320
|
+
- `maxDownloads` charged only on redemption. Fixed in 0.3.0.
|
|
321
|
+
- The audit chain could fork under concurrent writers. Fixed in 0.3.0.
|
|
322
|
+
- A soft-deleted org left outstanding grants alive. Fixed in 0.3.0.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## [0.1.0] — 2026-09-04
|
|
327
|
+
|
|
328
|
+
Initial implementation.
|
|
329
|
+
|
|
330
|
+
- The data model: projects had not been invented yet, so orgs, actors, files,
|
|
331
|
+
memberships, grants and a hash-chained audit log, with tenant isolation
|
|
332
|
+
enforced by composite foreign key rather than by `WHERE` clause.
|
|
333
|
+
- The authorization engine: one decision core, reached from a point check.
|
|
334
|
+
- The tiered facade — `files`, `orgs`, `shares` — over it.
|
|
335
|
+
- `MemoryStorage`, and an S3/R2 adapter that had not been run against anything.
|
|
336
|
+
- Share links with expiry, password and download cap.
|
|
337
|
+
|
|
338
|
+
Every defect in the 0.2.0 list above was present in this version.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|