@kavtuai/guildgate 0.1.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.
Files changed (133) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +399 -0
  4. package/README.tr.md +397 -0
  5. package/SECURITY.md +36 -0
  6. package/TEST_REPORT.md +50 -0
  7. package/bin/guildgate-doctor.mjs +91 -0
  8. package/bin/guildgate-writing-check.mjs +73 -0
  9. package/dist/action.d.ts +65 -0
  10. package/dist/action.d.ts.map +1 -0
  11. package/dist/action.js +4 -0
  12. package/dist/action.js.map +1 -0
  13. package/dist/cache.d.ts +23 -0
  14. package/dist/cache.d.ts.map +1 -0
  15. package/dist/cache.js +53 -0
  16. package/dist/cache.js.map +1 -0
  17. package/dist/crypto.d.ts +15 -0
  18. package/dist/crypto.d.ts.map +1 -0
  19. package/dist/crypto.js +87 -0
  20. package/dist/crypto.js.map +1 -0
  21. package/dist/discord/authorizer.d.ts +41 -0
  22. package/dist/discord/authorizer.d.ts.map +1 -0
  23. package/dist/discord/authorizer.js +101 -0
  24. package/dist/discord/authorizer.js.map +1 -0
  25. package/dist/discord/oauth.d.ts +64 -0
  26. package/dist/discord/oauth.d.ts.map +1 -0
  27. package/dist/discord/oauth.js +226 -0
  28. package/dist/discord/oauth.js.map +1 -0
  29. package/dist/discord/permissions.d.ts +85 -0
  30. package/dist/discord/permissions.d.ts.map +1 -0
  31. package/dist/discord/permissions.js +99 -0
  32. package/dist/discord/permissions.js.map +1 -0
  33. package/dist/discord.d.ts +4 -0
  34. package/dist/discord.d.ts.map +1 -0
  35. package/dist/discord.js +4 -0
  36. package/dist/discord.js.map +1 -0
  37. package/dist/errors.d.ts +43 -0
  38. package/dist/errors.d.ts.map +1 -0
  39. package/dist/errors.js +57 -0
  40. package/dist/errors.js.map +1 -0
  41. package/dist/express.d.ts +3 -0
  42. package/dist/express.d.ts.map +1 -0
  43. package/dist/express.js +3 -0
  44. package/dist/express.js.map +1 -0
  45. package/dist/fastify.d.ts +3 -0
  46. package/dist/fastify.d.ts.map +1 -0
  47. package/dist/fastify.js +3 -0
  48. package/dist/fastify.js.map +1 -0
  49. package/dist/http/express.d.ts +24 -0
  50. package/dist/http/express.d.ts.map +1 -0
  51. package/dist/http/express.js +60 -0
  52. package/dist/http/express.js.map +1 -0
  53. package/dist/http/fastify.d.ts +25 -0
  54. package/dist/http/fastify.d.ts.map +1 -0
  55. package/dist/http/fastify.js +57 -0
  56. package/dist/http/fastify.js.map +1 -0
  57. package/dist/i18n.d.ts +5 -0
  58. package/dist/i18n.d.ts.map +1 -0
  59. package/dist/i18n.js +66 -0
  60. package/dist/i18n.js.map +1 -0
  61. package/dist/index.d.ts +13 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +13 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/kernel.d.ts +83 -0
  66. package/dist/kernel.d.ts.map +1 -0
  67. package/dist/kernel.js +395 -0
  68. package/dist/kernel.js.map +1 -0
  69. package/dist/memory-store.d.ts +13 -0
  70. package/dist/memory-store.d.ts.map +1 -0
  71. package/dist/memory-store.js +261 -0
  72. package/dist/memory-store.js.map +1 -0
  73. package/dist/realtime/hub.d.ts +42 -0
  74. package/dist/realtime/hub.d.ts.map +1 -0
  75. package/dist/realtime/hub.js +152 -0
  76. package/dist/realtime/hub.js.map +1 -0
  77. package/dist/realtime/outbox.d.ts +12 -0
  78. package/dist/realtime/outbox.d.ts.map +1 -0
  79. package/dist/realtime/outbox.js +18 -0
  80. package/dist/realtime/outbox.js.map +1 -0
  81. package/dist/realtime.d.ts +3 -0
  82. package/dist/realtime.d.ts.map +1 -0
  83. package/dist/realtime.js +3 -0
  84. package/dist/realtime.js.map +1 -0
  85. package/dist/redis.d.ts +49 -0
  86. package/dist/redis.d.ts.map +1 -0
  87. package/dist/redis.js +233 -0
  88. package/dist/redis.js.map +1 -0
  89. package/dist/resilience.d.ts +23 -0
  90. package/dist/resilience.d.ts.map +1 -0
  91. package/dist/resilience.js +85 -0
  92. package/dist/resilience.js.map +1 -0
  93. package/dist/security.d.ts +21 -0
  94. package/dist/security.d.ts.map +1 -0
  95. package/dist/security.js +94 -0
  96. package/dist/security.js.map +1 -0
  97. package/dist/session.d.ts +32 -0
  98. package/dist/session.d.ts.map +1 -0
  99. package/dist/session.js +97 -0
  100. package/dist/session.js.map +1 -0
  101. package/dist/stores.d.ts +73 -0
  102. package/dist/stores.d.ts.map +1 -0
  103. package/dist/stores.js +4 -0
  104. package/dist/stores.js.map +1 -0
  105. package/dist/testing.d.ts +32 -0
  106. package/dist/testing.d.ts.map +1 -0
  107. package/dist/testing.js +60 -0
  108. package/dist/testing.js.map +1 -0
  109. package/dist/types.d.ts +183 -0
  110. package/dist/types.d.ts.map +1 -0
  111. package/dist/types.js +4 -0
  112. package/dist/types.js.map +1 -0
  113. package/docs/WRITING_STYLE.md +118 -0
  114. package/docs/architecture.md +83 -0
  115. package/docs/custom-stores.md +111 -0
  116. package/docs/owner-management.md +44 -0
  117. package/docs/research-notes.md +61 -0
  118. package/docs/setup.md +104 -0
  119. package/docs/threat-model.md +79 -0
  120. package/docs/tr/arastirma-notlari.md +41 -0
  121. package/docs/tr/guvenlik-bildirimi.md +7 -0
  122. package/docs/tr/kurulum.md +63 -0
  123. package/docs/tr/mimari.md +59 -0
  124. package/docs/tr/sahip-yonetimi.md +7 -0
  125. package/docs/tr/tehdit-modeli.md +39 -0
  126. package/docs/tr/veritabani-suruculeri.md +23 -0
  127. package/docs/tr/yazim-kilavuzu.md +116 -0
  128. package/docs/uml/action-sequence.mmd +39 -0
  129. package/docs/uml/components.mmd +84 -0
  130. package/docs/uml/deployment.mmd +19 -0
  131. package/docs/uml/oauth-sequence.mmd +30 -0
  132. package/docs/uml/realtime-sequence.mmd +28 -0
  133. package/package.json +90 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ All notable changes are recorded in this file. The project follows Semantic Versioning after `1.0.0`. Before `1.0.0`, a minor release may contain an API change with migration notes.
4
+
5
+ ## 0.1.0 - 2026-07-25
6
+
7
+ ### Added
8
+
9
+ - Database-neutral storage contracts.
10
+ - Memory reference stores.
11
+ - Redis stores for sessions, OAuth state, rate limits, cache, idempotency, and locks.
12
+ - Opaque sessions with expiry, idle expiry, rotation, caps, and revocation.
13
+ - Discord OAuth authorization-code client with state and browser nonce binding.
14
+ - AES-256-GCM credential keyring.
15
+ - Discord `BigInt` permission helpers and guild access authorizer.
16
+ - Guarded actions with parsing, policies, limits, authorization, idempotency, locks, deadlines, audit, cache tags, and realtime delivery.
17
+ - Fastify and Express adapters.
18
+ - Realtime hub and outbox dispatcher.
19
+ - English and Turkish error messages and documentation.
20
+ - UML, threat model, store guide, writing guide, CI, trusted publishing workflow, doctor command, and tests.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kavtuai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,399 @@
1
+ # GuildGate
2
+
3
+ [Turkish documentation](./README.tr.md)
4
+
5
+ GuildGate is a TypeScript library for the server side of Discord bot dashboards. It handles session records, Discord OAuth state, CSRF checks, exact-origin checks, guild permission checks, request limits, idempotency, resource locks, timeouts, cache invalidation, audit records, outbox events, and authenticated realtime subscriptions.
6
+
7
+ It does not generate a user interface and it does not own your database. Each storage feature is an interface. An application can use PostgreSQL, MySQL, MongoDB, SQLite, Redis, an existing ORM, or a private storage service.
8
+
9
+ Current package: `@kavtuai/guildgate`
10
+ Current release: `0.1.0`
11
+ Required runtime: Node.js 22 or newer
12
+
13
+ ## Status
14
+
15
+ This repository is an initial public release candidate. The TypeScript build and included tests pass. Live Discord OAuth, a production Redis server, and third-party database drivers still need integration tests in the target application before deployment.
16
+
17
+ No library can promise that an application has no security defects. GuildGate reduces repeated security work, rejects unsafe production settings, and gives application owners explicit policy controls. A production service should still use code review, dependency review, monitoring, backups, and an external security review when its risk warrants one.
18
+
19
+ ## Install
20
+
21
+ After the package is published:
22
+
23
+ ```bash
24
+ npm install @kavtuai/guildgate
25
+ ```
26
+
27
+ To test this source archive before publishing:
28
+
29
+ ```bash
30
+ npm install
31
+ npm test
32
+ ```
33
+
34
+ ## What is included
35
+
36
+ - Opaque server-side sessions. Raw session tokens are not stored.
37
+ - Session expiry, idle expiry, rotation, per-user session caps, and revocation.
38
+ - Discord authorization-code login with one-time `state` records and a browser nonce cookie.
39
+ - Encrypted OAuth access and refresh token storage through a caller-supplied keyring.
40
+ - Exact origin checks and session-bound CSRF tokens for write requests.
41
+ - Discord permission parsing with `BigInt`.
42
+ - User and bot permission checks for a guild.
43
+ - A guarded `action()` API for dashboard reads and writes.
44
+ - Per-action limits, idempotency keys, resource locks, request deadlines, audit records, cache tags, and realtime events.
45
+ - Memory stores for tests and local development.
46
+ - Redis implementations for short-lived and distributed state.
47
+ - Framework adapters for Fastify and Express without runtime framework dependencies.
48
+ - An authenticated realtime hub for WebSocket adapters.
49
+ - English and Turkish error messages with per-application overrides.
50
+ - Mermaid UML files, a threat model, store contracts, release notes, and publishing workflows.
51
+
52
+ ## Five-minute local example
53
+
54
+ Memory storage is suitable for tests and a single local process. It is not a production database.
55
+
56
+ ```ts
57
+ import {
58
+ createGuildGate,
59
+ createMemoryStoreBundle,
60
+ } from "@kavtuai/guildgate";
61
+
62
+ const stores = createMemoryStoreBundle();
63
+
64
+ const gate = createGuildGate({
65
+ app: {
66
+ name: "My Discord Dashboard",
67
+ environment: "development",
68
+ baseUrl: "http://localhost:3000",
69
+ },
70
+ owners: [process.env.BOT_OWNER_ID!],
71
+ locale: {
72
+ default: "en",
73
+ messages: {
74
+ en: { MAINTENANCE_MODE: "Settings are paused during maintenance." },
75
+ },
76
+ },
77
+ security: {
78
+ allowedOrigins: ["http://localhost:3000"],
79
+ csrfSecret: process.env.GUILDGATE_CSRF_SECRET!,
80
+ auditIpSalt: process.env.GUILDGATE_AUDIT_IP_SALT!,
81
+ session: {
82
+ ttlMs: 12 * 60 * 60_000,
83
+ idleTimeoutMs: 30 * 60_000,
84
+ rotateAfterMs: 15 * 60_000,
85
+ maximumSessionsPerUser: 5,
86
+ },
87
+ },
88
+ stores,
89
+ });
90
+ ```
91
+
92
+ Secrets used by this example must contain enough random bytes. Generate them outside source control:
93
+
94
+ ```bash
95
+ node -e "console.log(require('node:crypto').randomBytes(32).toString('base64url'))"
96
+ ```
97
+
98
+ ## A guarded dashboard action
99
+
100
+ The action definition keeps checks beside the operation they protect.
101
+
102
+ ```ts
103
+ const updateSettings = gate.action({
104
+ name: "guild.settings.update",
105
+
106
+ parse(value) {
107
+ const body = value as Record<string, unknown>;
108
+ if (typeof body.guildId !== "string") throw new Error("guildId is required");
109
+ if (typeof body.revision !== "number") throw new Error("revision is required");
110
+ return {
111
+ guildId: body.guildId,
112
+ revision: body.revision,
113
+ settings: body.settings,
114
+ };
115
+ },
116
+
117
+ resource: (input) => ({ type: "guild", id: input.guildId }),
118
+
119
+ rateLimit: {
120
+ limit: 20,
121
+ windowMs: 60_000,
122
+ key: (context, input) => `${context.userId}:${input.guildId}`,
123
+ },
124
+
125
+ idempotency: {
126
+ ttlMs: 10 * 60_000,
127
+ scope: (context, input) => `${context.userId}:${input.guildId}`,
128
+ },
129
+
130
+ concurrency: {
131
+ key: (_context, input) => `guild-settings:${input.guildId}`,
132
+ ttlMs: 8_000,
133
+ waitMs: 500,
134
+ },
135
+
136
+ timeoutMs: 5_000,
137
+
138
+ authorize: async (context, input) => {
139
+ // Replace this with createDiscordGuildAuthorizer(...).require(...),
140
+ // or use the policy service already present in your application.
141
+ return context.userId
142
+ ? { allowed: true }
143
+ : { allowed: false, code: "AUTHENTICATION_REQUIRED" };
144
+ },
145
+
146
+ async execute(context, input) {
147
+ // The application owns the transaction and the revision check.
148
+ return database.transaction(async (tx) => {
149
+ return tx.guildSettings.updateWithRevision({
150
+ guildId: input.guildId,
151
+ expectedRevision: input.revision,
152
+ settings: input.settings,
153
+ actorId: context.userId!,
154
+ });
155
+ });
156
+ },
157
+
158
+ audit: {
159
+ changes: (result) => ({ revision: result.revision }),
160
+ },
161
+
162
+ cache: {
163
+ invalidateTags: (result) => [`guild:${result.guildId}`],
164
+ },
165
+
166
+ realtime: {
167
+ delivery: "outbox",
168
+ events: (result) => [{
169
+ event: "guild.settings.updated",
170
+ channel: `guild:${result.guildId}`,
171
+ data: { revision: result.revision },
172
+ }],
173
+ },
174
+ });
175
+ ```
176
+
177
+ A browser write request must include the session cookie, the CSRF token, and an idempotency key:
178
+
179
+ ```http
180
+ PATCH /api/guilds/123/settings
181
+ Origin: https://dashboard.example.com
182
+ X-CSRF-Token: <session-bound-token>
183
+ Idempotency-Key: <random-request-id>
184
+ ```
185
+
186
+ ## Discord login
187
+
188
+ ```ts
189
+ import { createTokenCipher } from "@kavtuai/guildgate";
190
+ import { createDiscordOAuth } from "@kavtuai/guildgate/discord";
191
+
192
+ const cipher = createTokenCipher({
193
+ activeKeyId: "2026-01",
194
+ keys: {
195
+ "2026-01": process.env.GUILDGATE_TOKEN_KEY_BASE64!,
196
+ },
197
+ });
198
+
199
+ const discord = createDiscordOAuth({
200
+ kernel: gate,
201
+ cipher,
202
+ config: {
203
+ clientId: process.env.DISCORD_CLIENT_ID!,
204
+ clientSecret: process.env.DISCORD_CLIENT_SECRET!,
205
+ redirectUri: "https://dashboard.example.com/auth/discord/callback",
206
+ scopes: ["identify", "guilds"],
207
+ },
208
+ });
209
+ ```
210
+
211
+ Login start:
212
+
213
+ ```ts
214
+ const login = await discord.beginLogin({ returnTo: "/dashboard", locale: "en" });
215
+ response.header("set-cookie", login.stateCookie);
216
+ response.redirect(login.authorizationUrl);
217
+ ```
218
+
219
+ Callback:
220
+
221
+ ```ts
222
+ const result = await discord.completeLogin({
223
+ code: request.query.code,
224
+ state: request.query.state,
225
+ stateCookie: request.cookies["__Host-guildgate.oauth"],
226
+ });
227
+
228
+ response.header("set-cookie", [result.setCookie, result.clearStateCookie]);
229
+ response.redirect(result.returnTo);
230
+ ```
231
+
232
+ The OAuth callback accepts only a local return path. It will not redirect to a caller-supplied external host.
233
+
234
+ ## Guild permission checks
235
+
236
+ ```ts
237
+ import { createDiscordGuildAuthorizer } from "@kavtuai/guildgate/discord";
238
+
239
+ const guildAccess = createDiscordGuildAuthorizer({
240
+ oauth: discord,
241
+ botToken: process.env.DISCORD_BOT_TOKEN!,
242
+ botUserId: process.env.DISCORD_BOT_USER_ID!,
243
+ cache: gate.cache,
244
+ });
245
+
246
+ const saveAction = gate.action({
247
+ name: "guild.settings.save",
248
+ parse: parseSettings,
249
+ resource: (input) => ({ type: "guild", id: input.guildId }),
250
+ authorize: guildAccess.require({
251
+ guildId: (input) => input.guildId,
252
+ userPermissions: ["MANAGE_GUILD"],
253
+ botPermissions: ["VIEW_CHANNEL", "MANAGE_ROLES"],
254
+ consistency: "live",
255
+ }),
256
+ execute: saveSettings,
257
+ });
258
+ ```
259
+
260
+ Use live checks for writes that change Discord or security-sensitive settings. Short cached checks are suitable for low-risk display data. Invalidate permission cache entries when member roles, guild roles, or bot membership change.
261
+
262
+ ## Database choice
263
+
264
+ `GuildGateStores` is the complete storage contract:
265
+
266
+ ```ts
267
+ interface GuildGateStores {
268
+ sessions: SessionStore;
269
+ oauthStates: OAuthStateStore;
270
+ credentials: OAuthCredentialStore;
271
+ rateLimits: RateLimitStore;
272
+ cache: CacheStore;
273
+ idempotency: IdempotencyStore;
274
+ locks: LockStore;
275
+ audit: AuditStore;
276
+ outbox: OutboxStore;
277
+ policies: PolicyStore;
278
+ }
279
+ ```
280
+
281
+ A project may put durable records in its main database and short-lived records in Redis:
282
+
283
+ ```ts
284
+ import { composeStores, createMemoryStoreBundle } from "@kavtuai/guildgate";
285
+ import {
286
+ createRedisEphemeralStores,
287
+ fromNodeRedis,
288
+ } from "@kavtuai/guildgate/redis";
289
+
290
+ const temporary = createRedisEphemeralStores(fromNodeRedis(redis), {
291
+ prefix: "mybot:guildgate",
292
+ });
293
+
294
+ const stores = composeStores(createMemoryStoreBundle(), {
295
+ ...temporary,
296
+ credentials: postgresCredentialStore,
297
+ audit: postgresAuditStore,
298
+ outbox: postgresOutboxStore,
299
+ policies: postgresPolicyStore,
300
+ });
301
+ ```
302
+
303
+ The memory fallback in that snippet is shown to explain composition. A production application should provide durable implementations for every record it must retain across restarts.
304
+
305
+ See [Custom stores](./docs/custom-stores.md) for contracts and transaction notes.
306
+
307
+ ## Fastify and Express adapters
308
+
309
+ The adapters use structural request and response types, so GuildGate does not install either framework.
310
+
311
+ ```ts
312
+ import { fastifyActionHandler } from "@kavtuai/guildgate/fastify";
313
+
314
+ fastify.patch(
315
+ "/api/guilds/:guildId/settings",
316
+ fastifyActionHandler(gate, updateSettings),
317
+ );
318
+ ```
319
+
320
+ ```ts
321
+ import { expressActionHandler } from "@kavtuai/guildgate/express";
322
+
323
+ app.patch(
324
+ "/api/guilds/:guildId/settings",
325
+ expressActionHandler(gate, updateSettings),
326
+ );
327
+ ```
328
+
329
+ ## Realtime access
330
+
331
+ `createRealtimeHub()` does not open a socket server. Connect it to `ws`, uWebSockets.js, Socket.IO, Bun, or another transport through the small `RealtimeConnection` interface.
332
+
333
+ Every connection must pass an allowed `Origin` and a valid session token. Each channel subscription has its own authorization callback. The hub also applies message size limits, message-rate limits, idle expiry, maximum connection lifetime, subscription caps, and slow-client handling.
334
+
335
+ See [Architecture](./docs/architecture.md) and the [realtime sequence](./docs/uml/realtime-sequence.mmd).
336
+
337
+ ## Owner controls
338
+
339
+ The configured owner IDs can be used by the application to expose protected management routes. The kernel includes methods for:
340
+
341
+ ```ts
342
+ await gate.owner.setMaintenance({ enabled: true, reason: "database migration" });
343
+ await gate.owner.block({ subjectType: "user", subjectId: "123", reason: "abuse" });
344
+ await gate.owner.unblock("user", "123");
345
+ await gate.revokeUserSessions("123");
346
+ ```
347
+
348
+ Owner routes must still pass session, CSRF, origin, rate-limit, and audit checks. Do not call these methods from public routes without an owner authorization check.
349
+
350
+ ## Production rules
351
+
352
+ GuildGate rejects these production settings:
353
+
354
+ - A non-HTTPS application base URL.
355
+ - An empty origin allowlist.
356
+ - An HTTP or localhost origin.
357
+ - A session cookie with `Secure` disabled.
358
+ - A Discord callback URL that does not use HTTPS.
359
+ - Short CSRF and audit hashing secrets.
360
+
361
+ Application responsibilities remain:
362
+
363
+ - Use a trusted reverse-proxy configuration and obtain the real client IP safely.
364
+ - Keep bot tokens, OAuth secrets, encryption keys, and database credentials outside source control.
365
+ - Run database changes in transactions where the driver supports them.
366
+ - Apply optimistic revision checks when multiple managers can edit one record.
367
+ - Keep audit and outbox data under retention policies.
368
+ - Back up durable records and test restore procedures.
369
+ - Pass cancellation signals to database and HTTP clients where supported.
370
+ - Review Discord permission and API changes before each major release.
371
+
372
+ ## Repository checks
373
+
374
+ ```bash
375
+ npm run typecheck
376
+ npm test
377
+ npm run pack:check
378
+ node ./bin/guildgate-doctor.mjs
379
+ ```
380
+
381
+ The doctor command reads environment variables and reports missing or unsafe deployment settings. It does not send secrets anywhere.
382
+
383
+ ## Documentation
384
+
385
+ - [Setup and first release](./docs/setup.md)
386
+ - [Architecture](./docs/architecture.md)
387
+ - [Custom storage drivers](./docs/custom-stores.md)
388
+ - [Owner management routes](./docs/owner-management.md)
389
+ - [Threat model](./docs/threat-model.md)
390
+ - [Writing rules](./docs/WRITING_STYLE.md)
391
+ - [Research notes](./docs/research-notes.md)
392
+ - [Local test report](./TEST_REPORT.md)
393
+ - [Release plan](./ROADMAP.md)
394
+ - [Security reporting](./SECURITY.md)
395
+ - [UML files](./docs/uml)
396
+
397
+ ## License
398
+
399
+ MIT. See [LICENSE](./LICENSE).