4 Commits
Author SHA1 Message Date
ekklesiaandClaude Opus 5 cda841f8c7 test: gate the graceful drain proof on handler entry
The Pass 4 drain test waited for `server.getConnections() > 0` before starting
shutdown, and a comment described that as the request having "demonstrably
entered". It had not: the connection count proves the server accepted a TCP
connection, nothing about whether Hono had dispatched to a handler. Run five
times against the real integration harness it was not flaky, but the claim was
stronger than the evidence, so the evidence is now stronger instead.

A fixture module's route handler announces entry through a `globalThis` gate —
the same channel the existing lifecycle recorders use — and then blocks until the
test releases it. Shutdown therefore begins at a point where the handler is
provably inside user code. The assertions are now about recorded ordering rather
than timing: the handler finished, the module stopped, and `handler-finished`
precedes `stop` in the recorded events, with the full 200 response and a clean
transport close. No sleeps; the gate is released in `finally` so a failed
assertion cannot hang the suite.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-28 19:53:25 -05:00
ekklesiaandClaude Opus 5 07a3c61083 docs: define deployment trust boundaries
One canonical security document, linked from the README, platform architecture,
and module-development guide, stating what the boundaries actually protect
against — and, more importantly, what they do not.

Installed modules are trusted compiled backend code. A module runs arbitrary
JavaScript inside the API process and shares its PostgreSQL role and pool, so
`ModuleDatabase.execute` gives it the API's full database authority over every
table, including identity, grants, owner, bootstrap, and audit. Table prefixes,
manifests, permission declarations, route validation, frozen contexts, typed
facades, and the capability registry are correctness and coordination boundaries
that assume a cooperative module; none of them constrains one that is not. There
is no process, container, syscall, filesystem, network, database-role, schema,
tenant, or resource sandbox between a module and Core, and the document says so
in those words, along with what would have to be built before untrusted modules
could be accepted.

It also records the honest deployment state: Ekklesia is not deployed. There is
no production instance, reverse proxy, public listener, or deployment automation
anywhere in this repository, and no `.env` has ever been committed. The loopback
listen default and the production signup refusal are real, but a config default
is not a firewall, so the origin-reachability, proxy-overwrite, header-spoofing,
and trusted-peer properties a public launch needs are written as a
launch-blocking checklist rather than as guarantees. The per-process
authentication limiter is described as exactly that, valid only while signup
stays disabled and exactly one instance serves traffic.

The CI section records the workflow's verified posture and deliberately stops
there: a pull request can edit `runs-on`, so a workflow file cannot be the
boundary, and the runner topology is not described as though it had been
verified.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-28 19:49:12 -05:00
ekklesiaandClaude Opus 5 f7621a599f fix: remediate dependency advisories
Three advisory paths, closed with the narrowest scoped overrides rather than a
broad upgrade. Each raises exactly the one edge that resolved a vulnerable
version, and each pins an exact release.

PostCSS 8.5.17 (GHSA-r28c-9q8g-f849, high) was reached only through Vite, which
better-auth pulls in via Vitest, and Vite 8.1.4's own range still resolved the
vulnerable version. `vite>postcss` now resolves 8.5.24; one version in the tree.

esbuild 0.18.20 (GHSA-67mh-4wv8-2f99, moderate) was reached only through the
deprecated @esbuild-kit/core-utils, whose pinned range predates the fix. Scoped
to `@esbuild-kit/core-utils>esbuild` on purpose: drizzle-kit already resolved
esbuild 0.25.12 directly and tsx/vite resolve 0.28.1, so a global override would
have dragged those newer, unaffected versions backwards. The vulnerable version
is gone and 0.28.1 is preserved.

Lodash 4.17.21 (GHSA-r5fr-rjxr-66jc high, GHSA-f23m-r3pf-42rh and
GHSA-xxjr-mmjv-4gpg moderate) was reached only through the development-time
Better Auth `auth` CLI's Prisma parser. All three chevrotain packages pin lodash
to exactly 4.17.21, so no in-range release is patched, and the advisories
require >= 4.17.24 — a version the 4.17.x line never published. 4.18.1 is the
lowest fixed release; the override crosses chevrotain's exact pin for that
reason. Verified compatible before applying: 4.18.1 keeps the per-method
CommonJS subpath files chevrotain requires (`lodash/assign`, `lodash/isRegExp`,
and the rest), with the same `main` and no restrictive `exports` field.

Both `pnpm audit --prod` and the full `pnpm audit` now report no known
vulnerabilities, with no ignore, allowlist, or audit-level suppression.

Proven, not assumed: `pnpm install --frozen-lockfile` succeeds; the Better Auth
schema generator still matches the checked-in identity schema, which exercises
the very chevrotain/lodash path that was overridden; drizzle-kit loads the real
config and reports no schema changes against a scratch copy, leaving tracked
migration artifacts untouched; the three Core migrations keep their exact
checksums; and the whole unit and PostgreSQL integration suites stay green.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-28 19:49:12 -05:00
ekklesiaandClaude Opus 5 51f4075fd6 ci: stop persisting the job token
`actions/checkout` defaults to `persist-credentials: true`, which writes the job
token into `.git/config` and leaves it there for every later step. Nothing in
this workflow pushes, so the credential has no use after checkout — but a pull
request can edit this file, and any step it adds runs in the same workspace.

All three jobs now check out with `persist-credentials: false`. The rest of the
workflow's posture is unchanged and was re-verified: top-level
`permissions: contents: read`, both actions pinned by full commit SHA,
`pnpm install --frozen-lockfile`, no `pull_request_target`, and no secrets
referenced anywhere.

This is the one property from the Pass 5 runner-isolation requirements that is
repository-local and statically verifiable. The rest — whether pull-request jobs
run on a disposable guest, what Docker daemon they reach, and which runners this
repository can select — depends on the actual Gitea and runner hosts and is not
claimed here.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-28 19:49:12 -05:00
8 changed files with 365 additions and 276 deletions
+15
View File
@@ -20,6 +20,11 @@ jobs:
steps: steps:
- name: Check out repository - name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# `actions/checkout` defaults to leaving the job token in `.git/config`, where any later
# step — including one a pull request added to this file — could read or reuse it. Nothing
# in this workflow pushes, so the credential is never needed after checkout.
persist-credentials: false
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
@@ -44,6 +49,11 @@ jobs:
steps: steps:
- name: Check out repository - name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# `actions/checkout` defaults to leaving the job token in `.git/config`, where any later
# step — including one a pull request added to this file — could read or reuse it. Nothing
# in this workflow pushes, so the credential is never needed after checkout.
persist-credentials: false
- name: Exercise PostgreSQL and S3 infrastructure - name: Exercise PostgreSQL and S3 infrastructure
run: bash scripts/smoke-infrastructure.sh run: bash scripts/smoke-infrastructure.sh
@@ -55,6 +65,11 @@ jobs:
steps: steps:
- name: Check out repository - name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# `actions/checkout` defaults to leaving the job token in `.git/config`, where any later
# step — including one a pull request added to this file — could read or reuse it. Nothing
# in this workflow pushes, so the credential is never needed after checkout.
persist-credentials: false
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
+9
View File
@@ -189,6 +189,15 @@ exists. Modules remain trusted compiled backend code with no process, role, sche
isolation. Phase 5D is still not complete** — pass 5 (deployment/supply chain plus an independent isolation. Phase 5D is still not complete** — pass 5 (deployment/supply chain plus an independent
whole-repository re-audit) remains outstanding, and Phase 6 remains blocked. whole-repository re-audit) remains outstanding, and Phase 6 remains blocked.
**Security model.** Installed modules are trusted compiled backend code that runs arbitrary
JavaScript in the API process and shares its PostgreSQL role — table prefixes, manifests, permission
declarations, typed facades, and frozen contexts are correctness boundaries, not a sandbox. Ekklesia
is also **not currently deployed**: there is no production instance, reverse proxy, public listener,
or deployment automation, so the origin/proxy/throttling properties a public launch requires are
unproven launch-blockers rather than existing guarantees. Read
[Security model and deployment trust boundaries](docs/development/security.md) before installing a
module or exposing the API.
## Quick start ## Quick start
Prerequisites: Node.js 24 LTS, pnpm 11.12, and Docker with Compose. Prerequisites: Node.js 24 LTS, pnpm 11.12, and Docker with Compose.
+128 -33
View File
@@ -236,6 +236,97 @@ function createEventRecorder(): { events: string[]; expression: string } {
* module also registers a restricted `GET /events/:eventId` route and a restricted, state-changing * module also registers a restricted `GET /events/:eventId` route and a restricted, state-changing
* `POST /events` route against that same permission, so the full HTTP mounting/enforcement chain * `POST /events` route against that same permission, so the full HTTP mounting/enforcement chain
* can be exercised live. */ * can be exercised live. */
/** A handler gate shared with a dynamically imported fixture module through `globalThis`, the same
* technique `createEventRecorder` uses. The handler resolves `entered` when it is genuinely inside
* user code and then awaits `released`, so a test can position shutdown precisely instead of
* inferring handler entry from a connection count. `release` is idempotent and safe in `finally`. */
function createHandlerGate(): {
readonly entered: Promise<void>;
readonly expression: string;
release: () => void;
} {
const key = `ekklesia.api.integration.gate.${eventSymbols.length}.${Date.now()}.${Math.random()}`;
const symbol = Symbol.for(key);
let signalEntered: () => void = () => undefined;
let signalReleased: () => void = () => undefined;
const entered = new Promise<void>((resolve) => {
signalEntered = resolve;
});
const released = new Promise<void>((resolve) => {
signalReleased = resolve;
});
(globalThis as unknown as Record<PropertyKey, unknown>)[symbol] = {
enter: () => signalEntered(),
released,
};
eventSymbols.push(symbol);
return {
entered,
expression: `globalThis[Symbol.for(${JSON.stringify(key)})]`,
release: () => signalReleased(),
};
}
/** A fixture module with one public route whose handler blocks on the supplied gate, plus a stop
* hook that records when it ran — everything the graceful-drain ordering assertion needs. */
async function createGatedRouteModule(
modulesDirectory: string,
id: string,
recorder: { expression: string },
gate: { expression: string },
): Promise<void> {
const permissionId = `${id}.gated.read`;
const moduleDirectory = path.join(modulesDirectory, id);
await mkdir(path.join(moduleDirectory, "dist/server"), { recursive: true });
await Promise.all([
writeFile(
path.join(moduleDirectory, "package.json"),
JSON.stringify({
ekklesia: { layer: "module" },
name: `@test/module-${id}`,
type: "module",
version: "0.1.0",
}),
),
writeFile(
path.join(moduleDirectory, "ekklesia.module.json"),
JSON.stringify({
compatibility: { moduleSdk: "^0.1.0", platform: "^0.1.0" },
entrypoints: { server: "./dist/server/index.js" },
id,
manifestVersion: 3,
name: `${id} module`,
permissions: [{ baseline: "public", description: "Gated drain probe.", id: permissionId }],
version: "0.1.0",
}),
),
writeFile(
path.join(moduleDirectory, "dist/server/index.js"),
`export default {
id: ${JSON.stringify(id)},
version: "0.1.0",
register({ http }) {
http.route({
method: "GET",
path: "/gated",
permission: ${JSON.stringify(permissionId)},
maxBodyBytes: 0,
async handler() {
${recorder.expression}.push(${JSON.stringify(`${id}:handler-entered`)});
${gate.expression}.enter();
await ${gate.expression}.released;
${recorder.expression}.push(${JSON.stringify(`${id}:handler-finished`)});
return Response.json({ gated: true });
},
});
},
start() {},
stop() { ${recorder.expression}.push(${JSON.stringify(`${id}:stop`)}); },
};`,
),
]);
}
async function createFixtureModule( async function createFixtureModule(
modulesDirectory: string, modulesDirectory: string,
id: string, id: string,
@@ -2779,18 +2870,28 @@ describe.sequential("live API — bounded HTTP drain and shutdown", () => {
} }
}); });
it("lets an already-entered request finish and close cleanly rather than force-closing it", async () => { it("lets a handler that has already entered finish, and closes its transport before module stop", async () => {
// Deterministic rather than racing an unresolved `fetch`: a raw socket sends a complete request // A real handler gate, not an inference from the connection count: the fixture module's route
// with `Connection: close`, and shutdown only begins once the server provably holds that // handler announces that it has *entered*, then blocks until this test releases it. Shutdown
// connection — observed by polling the real server's own connection count, never by sleeping. // therefore begins at a point where the handler is provably inside user code, and the ordering
// claim below is an assertion about recorded events rather than about timing.
const modulesDirectory = await createFixtureModulesDirectory();
const recorder = createEventRecorder();
const gate = createHandlerGate();
await createGatedRouteModule(modulesDirectory, "drainorder", recorder, gate);
const database = await createDisposableDatabase("drain_order"); const database = await createDisposableDatabase("drain_order");
await applyCoreMigrations(database.url); await applyCoreMigrations(database.url);
const running = await startConfiguredApi(database.url, { drainTimeoutMsOverride: 20_000 }); const running = await startConfiguredApi(database.url, {
drainTimeoutMsOverride: 20_000,
modulesDirectoryOverride: modulesDirectory,
});
const address = running.server.address(); const address = running.server.address();
if (!address || typeof address !== "object") { if (!address || typeof address !== "object") {
throw harnessError("The API integration server has no bound address."); throw harnessError("The API integration server has no bound address.");
} }
const socket = net.connect({ host: "127.0.0.1", port: address.port }); const socket = net.connect({ host: "127.0.0.1", port: address.port });
let shutdown: Promise<void> | undefined;
try { try {
const responseBytes: Buffer[] = []; const responseBytes: Buffer[] = [];
const responseClosed = new Promise<void>((resolve) => { const responseClosed = new Promise<void>((resolve) => {
@@ -2802,44 +2903,38 @@ describe.sequential("live API — bounded HTTP drain and shutdown", () => {
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
socket.once("error", reject); socket.once("error", reject);
socket.write( socket.write(
"GET /api/v1/principal HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n", `GET /api/v1/modules/drainorder/gated HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n`,
() => resolve(), () => resolve(),
); );
}); });
// Bounded, condition-based: wait until the server actually holds the connection, so the // Bounded wait on the handler's own signal — no sleep, no proxy for "entered".
// request has demonstrably entered before shutdown starts. await withDeadline(gate.entered, 20_000);
const connectionCounter = running.server as unknown as { expect(recorder.events).toContain("drainorder:handler-entered");
getConnections?: (callback: (error: Error | null, count: number) => void) => void;
};
const deadline = Date.now() + 15_000;
for (;;) {
const connections = await new Promise<number>((resolve) => {
if (typeof connectionCounter.getConnections !== "function") {
resolve(1);
return;
}
connectionCounter.getConnections((_error, count) => resolve(count ?? 0));
});
if (connections > 0) {
break;
}
if (Date.now() > deadline) {
throw harnessError("The request never reached the server.");
}
await new Promise((resolve) => setTimeout(resolve, 10));
}
// The graceful path: nothing is stalled, so shutdown completes without reaching the drain // Shutdown starts while the handler is still inside user code.
// deadline and without force-closing anything. shutdown = running.shutdown();
await expect(withDeadline(running.shutdown(), 30_000)).resolves.toBeUndefined(); // Release it, then require the graceful path to complete.
gate.release();
await expect(withDeadline(shutdown, 30_000)).resolves.toBeUndefined();
// The already-entered request completed normally — full response, clean transport close. // The response really was served, in full, rather than force-closed mid-flight.
await withDeadline(responseClosed, 10_000); await withDeadline(responseClosed, 10_000);
const raw = Buffer.concat(responseBytes).toString("utf8"); const raw = Buffer.concat(responseBytes).toString("utf8");
expect(raw).toContain("HTTP/1.1 200"); expect(raw).toContain("HTTP/1.1 200");
expect(raw).toContain('{"kind":"anonymous"}'); expect(raw).toContain('"gated":true');
// Explicit ordering: the handler finished and the module stopped, in that order.
const finished = recorder.events.indexOf("drainorder:handler-finished");
const stopped = recorder.events.indexOf("drainorder:stop");
expect(finished).toBeGreaterThanOrEqual(0);
expect(stopped).toBeGreaterThanOrEqual(0);
expect(finished).toBeLessThan(stopped);
} finally { } finally {
gate.release();
if (shutdown) {
await shutdown.catch(() => undefined);
}
socket.destroy(); socket.destroy();
await running.shutdown().catch(() => undefined); await running.shutdown().catch(() => undefined);
await dropDatabase(database.name); await dropDatabase(database.name);
@@ -4,6 +4,11 @@ Ekklesia is a long-lived platform from which churches can compose many applicati
experiences. It begins as a modular monolith so contracts can stabilize before distribution is experiences. It begins as a modular monolith so contracts can stabilize before distribution is
introduced. introduced.
> **Trust boundary.** Installed modules are trusted compiled backend code, not sandboxed
> plugins: a module runs arbitrary JavaScript in the API process and shares its PostgreSQL
> role. The layering described below is a correctness and ownership boundary, not hostile-code
> isolation. See [Security model and deployment trust boundaries](../development/security.md).
## Dependency model ## Dependency model
The arrows below mean "depends on." Presentation and composition depend on stable lower-level The arrows below mean "depends on." Presentation and composition depend on stable lower-level
+154
View File
@@ -0,0 +1,154 @@
# Security model and deployment trust boundaries
The one canonical statement of what Ekklesia's boundaries actually protect against today, and what
they do not. Read it before installing a module, exposing the API, or reasoning about any other
document's guarantees.
Related: [ADR 0006](../architecture/decisions/0006-versioned-capabilities-and-compiled-module-runtime.md)
(compiled module runtime), [ADR 0016](../architecture/decisions/0016-module-database-access.md)
(module database access), [ADR 0020](../architecture/decisions/0020-module-runtime-and-migration-execution-hardening.md)
(module routing/lifecycle and migration execution), and
[Module development](../module-development/README.md).
## Installed modules are trusted compiled backend code
**Installing a module is equivalent to deploying trusted server code.** It is not equivalent to
installing a browser extension, a plugin from a marketplace, or anything else with a security
boundary around it. There is no such boundary here.
A module's `dist/server` entrypoint is imported into, and runs arbitrary JavaScript inside, the
`apps/api` process. Within the operating-system account that process runs as, a module can:
- read and write any file the account can reach, including `.env`, TLS material, and the source tree;
- read `process.env`, `process.argv`, and any other process state;
- open outbound network connections and bind listeners;
- import any installed dependency, including ones Ekklesia never intended it to use;
- monkey-patch Core's own exports, Node built-ins, `globalThis`, or another module's objects;
- keep running work after its lifecycle hooks return.
It also **shares the API's single PostgreSQL role and connection pool**. `ModuleDatabase.execute`
accepts arbitrary SQL, so a module has exactly the database authority the API has — every table in
the database, including `ekklesia_core_user`, `ekklesia_core_grant`,
`ekklesia_core_platform_owner`, `ekklesia_core_platform_owner_bootstrap`,
`ekklesia_core_authorization_audit`, and every other module's tables.
## What the module-facing mechanisms actually are
These are **correctness and coordination boundaries**. Every one of them assumes the module is
cooperative. None of them constrains a module that is not:
| Mechanism | What it actually provides |
| --- | --- |
| `ekklesia_*` table prefixes and schema discovery | Ownership clarity and collision avoidance between cooperating modules |
| TypeScript types, branded ids, contracts | Compile-time correctness; erased at runtime |
| `ekklesia.module.json` manifest and `manifestVersion` | Declared compatibility and dependency ordering |
| Permission declarations and the composed registry | Which permissions *this* module's own routes may name, checked when Core mounts them |
| Route validation, overlap rejection, body limits | Deterministic, unambiguous HTTP routing and request bounds |
| `Object.freeze` on contexts and services | Prevents accidental mutation; a module can still reach the underlying objects |
| The `ModuleDatabase` facade | A typed, ergonomic surface — not a privilege reduction, since `execute` is arbitrary SQL |
| Capability registry | Explicit inter-module wiring, not access control |
| Bounded stop/rollback deadlines (ADR 0020) | The platform stops *waiting*; module code is not cancelled or terminated |
**There is no process, container, VM, seccomp/syscall, filesystem, network, PostgreSQL role,
schema-permission, table-ACL, row-level-security, tenant, or resource sandbox between a module and
Core.** A module is not isolated from Core, from other modules, or from the host.
## What this platform must not claim
Ekklesia today has **none** of the following, and no document, README, or marketing surface may
imply otherwise:
- module signing, provenance attestation, or verified publishers;
- a marketplace, registry, or review process for third-party modules;
- sandboxed or hot-installed modules;
- per-module database roles, schemas, or credentials;
- tenant isolation;
- runtime resource limits (CPU, memory, file descriptors, connections) per module.
## Operator rule
**Only install modules whose source, provenance, and build output you have reviewed and whose
authors you trust with your production server and database.** Treat a module the way you would
treat a direct commit to `apps/api`: read the code, verify what artifact was built from it, pin it,
and re-review on upgrade. Do not install a module because a manifest looks well-formed — every
validation Ekklesia performs on a manifest is satisfied trivially by hostile code.
Installing, removing, or editing a module requires an API restart; there is no hot install path,
and none should be added while this model holds.
## What would have to change before accepting untrusted modules
Accepting third-party modules that have not been reviewed is an architectural change, not a
configuration change. At minimum it would require:
- process or container isolation per module, with the API as a supervisor;
- capability-based RPC across that boundary instead of in-process object passing;
- a separate PostgreSQL role and schema per module, with no shared superuser-equivalent pool;
- restricted filesystem and network access per module;
- CPU/memory/connection limits and enforced timeouts that can actually terminate work;
- signed, verifiable module provenance and a defined review/revocation process.
Until that exists, the trusted-code model above is the security model.
## Current deployment state
As of this branch, **Ekklesia is not deployed**. There is no production API instance, no reverse
proxy, no public listener, and no deployment automation of any kind in this repository — no deploy
script, no CI deployment step, no container publish, no orchestration manifest. No `.env` has ever
been committed; only `.env.example`.
Two production defaults are relevant but are **not** by themselves a deployment guarantee:
- `apps/api` defaults its listen address to loopback (`127.0.0.1`), so it is not reachable off-host
unless an operator explicitly changes it;
- production configuration refuses to enable public signup at all
(`PUBLIC_SIGNUP_UNSUPPORTED_IN_PRODUCTION`).
A loopback default is not a firewall, and a config default is not a verified boundary.
### Launch checklist — each item must be proven against the real deployment before public exposure
Until every one of these is verified on the actual hosts, the deployment is not launch-ready. None
of them can be satisfied by reading this repository.
1. The reverse-proxy path serves the API successfully.
2. The origin port is unreachable from the public Internet.
3. The origin port is also unreachable from any untrusted network/VLAN — only the intended private
proxy path connects. Security must not depend on an unpublished DNS name or origin IP.
4. The proxy **overwrites** (never appends to, never passes through) the client-IP header Ekklesia
is configured to trust.
5. Rotating a caller-supplied `Forwarded`, `X-Forwarded-For`, `CF-Connecting-IP`, `X-Real-IP`, or
the platform's own internal sanitized header cannot rotate the authentication rate-limit bucket.
6. The actual immediate proxy peer is in Ekklesia's explicit trusted-proxy set, and an untrusted
direct peer's forwarded header is ignored.
7. Proxy logs do not record cookies, authorization values, request bodies, or raw forwarding chains.
8. PostgreSQL is reachable only from the API host, over TLS, with the certificate and hostname
verification Ekklesia already requires.
### Rate limiting is per-process today
Better Auth's authentication rate limiter is **in-memory and per API process**. That is acceptable
only while **both** of the following hold, which is the current state:
- production public signup is disabled (and cannot be enabled — see above); and
- exactly one API instance serves traffic.
If either changes — public signup is enabled, or a second instance is added — a durable shared or
edge rate limiter becomes mandatory, and it must be proven that its counters are shared across
instances, survive process restart, and cannot be re-bucketed by a spoofed client-IP header.
Ekklesia does not ship one. Do not describe the current limiter as distributed throttling.
## Continuous integration boundary
CI runs on self-hosted Gitea Actions runners. The workflow itself is least privilege: top-level
`permissions: contents: read`, both actions pinned by full commit SHA, `pnpm install
--frozen-lockfile`, no `pull_request_target`, no secrets referenced, and checkout configured with
`persist-credentials: false` so the job token is not left in `.git/config` for a later step to read.
**The runner topology itself is not verified in this repository and is not documented here as
though it were.** A pull request can edit `runs-on` and select any runner label its repository can
reach, so a workflow file cannot be the boundary. Whether untrusted pull-request jobs execute on a
disposable guest, and whether any runner reachable from this repository holds deployment authority
or a host Docker socket, must be established against the actual Gitea instance and runner hosts.
Until that evidence exists, treat pull-request CI as executing with whatever authority the runner
host actually grants.
+6
View File
@@ -3,6 +3,12 @@
An Ekklesia module is a backend capability package. It owns business behavior and data declarations; An Ekklesia module is a backend capability package. It owns business behavior and data declarations;
it never owns components, styles, layouts, renderers, navigation, or other UI. it never owns components, styles, layouts, renderers, navigation, or other UI.
> **Before you publish or install a module:** modules are trusted compiled backend code. Your
> module runs arbitrary JavaScript inside the API process and shares its PostgreSQL role, and
> nothing in this guide — manifests, permission declarations, typed facades, route validation,
> frozen contexts — isolates it. An operator installing your module is deploying trusted server
> code. See [Security model and deployment trust boundaries](../development/security.md).
## Required package shape ## Required package shape
Each immediate child of `modules/` must be an ESM workspace package with a unique package name, a Each immediate child of `modules/` must be an ESM workspace package with a unique package name, a
+22 -243
View File
@@ -4,6 +4,13 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
overrides:
vite>postcss: 8.5.24
'@esbuild-kit/core-utils>esbuild': 0.25.12
chevrotain>lodash: 4.18.1
'@chevrotain/gast>lodash': 4.18.1
'@chevrotain/cst-dts-gen>lodash': 4.18.1
importers: importers:
.: .:
@@ -531,12 +538,6 @@ packages:
cpu: [ppc64] cpu: [ppc64]
os: [aix] os: [aix]
'@esbuild/[email protected]':
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -549,12 +550,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@esbuild/[email protected]':
resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -567,12 +562,6 @@ packages:
cpu: [arm] cpu: [arm]
os: [android] os: [android]
'@esbuild/[email protected]':
resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -585,12 +574,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [android] os: [android]
'@esbuild/[email protected]':
resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -603,12 +586,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@esbuild/[email protected]':
resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -621,12 +598,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@esbuild/[email protected]':
resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -639,12 +610,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [freebsd] os: [freebsd]
'@esbuild/[email protected]':
resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -657,12 +622,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
'@esbuild/[email protected]':
resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -675,12 +634,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@esbuild/[email protected]':
resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -693,12 +646,6 @@ packages:
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
'@esbuild/[email protected]':
resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -711,12 +658,6 @@ packages:
cpu: [ia32] cpu: [ia32]
os: [linux] os: [linux]
'@esbuild/[email protected]':
resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -729,12 +670,6 @@ packages:
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
'@esbuild/[email protected]':
resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -747,12 +682,6 @@ packages:
cpu: [mips64el] cpu: [mips64el]
os: [linux] os: [linux]
'@esbuild/[email protected]':
resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -765,12 +694,6 @@ packages:
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
'@esbuild/[email protected]':
resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -783,12 +706,6 @@ packages:
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
'@esbuild/[email protected]':
resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -801,12 +718,6 @@ packages:
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
'@esbuild/[email protected]':
resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -831,12 +742,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [netbsd] os: [netbsd]
'@esbuild/[email protected]':
resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -861,12 +766,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [openbsd] os: [openbsd]
'@esbuild/[email protected]':
resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -891,12 +790,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [openharmony] os: [openharmony]
'@esbuild/[email protected]':
resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -909,12 +802,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [sunos] os: [sunos]
'@esbuild/[email protected]':
resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -927,12 +814,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@esbuild/[email protected]':
resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -945,12 +826,6 @@ packages:
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@esbuild/[email protected]':
resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
'@esbuild/[email protected]': '@esbuild/[email protected]':
resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -1579,11 +1454,6 @@ packages:
[email protected]: [email protected]:
resolution: {integrity: sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==} resolution: {integrity: sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==}
[email protected]:
resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
engines: {node: '>=12'}
hasBin: true
[email protected]: [email protected]:
resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -1757,8 +1627,8 @@ packages:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
[email protected]7.21: [email protected]8.1:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
[email protected]: [email protected]:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -1769,8 +1639,8 @@ packages:
[email protected]: [email protected]:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
[email protected]5: [email protected]6:
resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
@@ -1843,8 +1713,8 @@ packages:
[email protected]: [email protected]:
resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==}
[email protected].17: [email protected].24:
resolution: {integrity: sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w==} resolution: {integrity: sha512-8RyVklq0owXUTa4xlpzu4l9AaVKIdQvAcOHZWaMh98HgySsUtxRVf/chRe3dsSLqb6i40BzGRzEUddRaI+9TSw==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
[email protected]: [email protected]:
@@ -2414,12 +2284,12 @@ snapshots:
dependencies: dependencies:
'@chevrotain/gast': 10.5.0 '@chevrotain/gast': 10.5.0
'@chevrotain/types': 10.5.0 '@chevrotain/types': 10.5.0
lodash: 4.17.21 lodash: 4.18.1
'@chevrotain/[email protected]': '@chevrotain/[email protected]':
dependencies: dependencies:
'@chevrotain/types': 10.5.0 '@chevrotain/types': 10.5.0
lodash: 4.17.21 lodash: 4.18.1
'@chevrotain/[email protected]': {} '@chevrotain/[email protected]': {}
@@ -2456,7 +2326,7 @@ snapshots:
'@esbuild-kit/[email protected]': '@esbuild-kit/[email protected]':
dependencies: dependencies:
esbuild: 0.18.20 esbuild: 0.25.12
source-map-support: 0.5.21 source-map-support: 0.5.21
'@esbuild-kit/[email protected]': '@esbuild-kit/[email protected]':
@@ -2470,144 +2340,96 @@ snapshots:
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
@@ -2620,9 +2442,6 @@ snapshots:
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
@@ -2635,9 +2454,6 @@ snapshots:
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
@@ -2650,36 +2466,24 @@ snapshots:
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
'@esbuild/[email protected]':
optional: true
'@esbuild/[email protected]': '@esbuild/[email protected]':
optional: true optional: true
@@ -3061,7 +2865,7 @@ snapshots:
'@chevrotain/gast': 10.5.0 '@chevrotain/gast': 10.5.0
'@chevrotain/types': 10.5.0 '@chevrotain/types': 10.5.0
'@chevrotain/utils': 10.5.0 '@chevrotain/utils': 10.5.0
lodash: 4.17.21 lodash: 4.18.1
regexp-to-ast: 0.5.0 regexp-to-ast: 0.5.0
[email protected]: [email protected]:
@@ -3112,31 +2916,6 @@ snapshots:
[email protected]: {} [email protected]: {}
[email protected]:
optionalDependencies:
'@esbuild/android-arm': 0.18.20
'@esbuild/android-arm64': 0.18.20
'@esbuild/android-x64': 0.18.20
'@esbuild/darwin-arm64': 0.18.20
'@esbuild/darwin-x64': 0.18.20
'@esbuild/freebsd-arm64': 0.18.20
'@esbuild/freebsd-x64': 0.18.20
'@esbuild/linux-arm': 0.18.20
'@esbuild/linux-arm64': 0.18.20
'@esbuild/linux-ia32': 0.18.20
'@esbuild/linux-loong64': 0.18.20
'@esbuild/linux-mips64el': 0.18.20
'@esbuild/linux-ppc64': 0.18.20
'@esbuild/linux-riscv64': 0.18.20
'@esbuild/linux-s390x': 0.18.20
'@esbuild/linux-x64': 0.18.20
'@esbuild/netbsd-x64': 0.18.20
'@esbuild/openbsd-x64': 0.18.20
'@esbuild/sunos-x64': 0.18.20
'@esbuild/win32-arm64': 0.18.20
'@esbuild/win32-ia32': 0.18.20
'@esbuild/win32-x64': 0.18.20
[email protected]: [email protected]:
optionalDependencies: optionalDependencies:
'@esbuild/aix-ppc64': 0.25.12 '@esbuild/aix-ppc64': 0.25.12
@@ -3297,7 +3076,7 @@ snapshots:
[email protected]: {} [email protected]: {}
[email protected]7.21: {} [email protected]8.1: {}
[email protected]: [email protected]:
dependencies: dependencies:
@@ -3309,7 +3088,7 @@ snapshots:
[email protected]: {} [email protected]: {}
[email protected]5: {} [email protected]6: {}
[email protected]: {} [email protected]: {}
@@ -3375,9 +3154,9 @@ snapshots:
exsolve: 1.1.0 exsolve: 1.1.0
pathe: 2.0.3 pathe: 2.0.3
[email protected].17: [email protected].24:
dependencies: dependencies:
nanoid: 3.3.15 nanoid: 3.3.16
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1
@@ -3523,7 +3302,7 @@ snapshots:
dependencies: dependencies:
lightningcss: 1.32.0 lightningcss: 1.32.0
picomatch: 4.0.5 picomatch: 4.0.5
postcss: 8.5.17 postcss: 8.5.24
rolldown: 1.1.5 rolldown: 1.1.5
tinyglobby: 0.2.17 tinyglobby: 0.2.17
optionalDependencies: optionalDependencies:
+26
View File
@@ -9,3 +9,29 @@ packages:
allowBuilds: allowBuilds:
esbuild: true esbuild: true
minimumReleaseAgeExclude:
- [email protected]
# Narrowly scoped advisory remediation. Each entry raises exactly the one edge that resolves a
# vulnerable version, never the whole graph, and pins an exact release rather than a range.
overrides:
# GHSA-r28c-9q8g-f849 (high). PostCSS is reached only through Vite, which better-auth pulls in
# via Vitest. Vite 8.1.4's own dependency range still resolves the vulnerable 8.5.17.
vite>postcss: "8.5.24"
# GHSA-67mh-4wv8-2f99 (moderate). The vulnerable esbuild 0.18.20 is reached only through the
# deprecated @esbuild-kit/core-utils (drizzle-kit -> @esbuild-kit/esm-loader), whose pinned range
# predates the fix. Scoped to that one edge deliberately: drizzle-kit already resolves esbuild
# 0.25.12 directly and tsx/vite resolve 0.28.1, so a global override would drag those newer,
# unaffected versions backwards.
"@esbuild-kit/core-utils>esbuild": "0.25.12"
# GHSA-r5fr-rjxr-66jc (high), GHSA-f23m-r3pf-42rh and GHSA-xxjr-mmjv-4gpg (moderate). Lodash is
# reached only through the development-time Better Auth `auth` CLI's Prisma parser
# (@mrleebo/prisma-ast -> chevrotain). All three chevrotain packages pin lodash to exactly
# 4.17.21, so no in-range release is patched; the advisories require >= 4.17.24, which the 4.17.x
# line never published, making 4.18.1 the lowest fixed release. Verified compatible: it keeps the
# per-method CommonJS subpath files (`lodash/assign`, `lodash/isRegExp`, ...) chevrotain requires,
# with the same `main` and no restrictive `exports` field.
chevrotain>lodash: "4.18.1"
"@chevrotain/gast>lodash": "4.18.1"
"@chevrotain/cst-dts-gen>lodash": "4.18.1"