@beignet/cli 0.0.23 → 0.0.24

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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # @beignet/cli
2
2
 
3
+ ## 0.0.24
4
+
5
+ ### Patch Changes
6
+
7
+ - 9d518c0: Generate server-only boundary markers as side-effect imports so app linting can
8
+ keep a consistent unused-import rule.
9
+ - d07a420: Redact Redis credentials from provider startup errors and show a production
10
+ hardening checklist when doctor reports deployment-readiness diagnostics.
11
+ - 91bc9b5: Harden framework security defaults for generated tenancy, auth secrets, CORS,
12
+ body limits, uploads, devtools, OpenAPI, rate-limit IP sources, public storage,
13
+ and Meilisearch query fields.
14
+ - Updated dependencies [9d518c0]
15
+ - Updated dependencies [91bc9b5]
16
+ - @beignet/core@0.0.24
17
+
3
18
  ## 0.0.23
4
19
 
5
20
  ### Patch Changes
package/README.md CHANGED
@@ -562,8 +562,10 @@ increment it on success. Stale updates become the generated
562
562
 
563
563
  Use `--auth` to generate authorization metadata, policy wiring, and use-case
564
564
  `ctx.gate.authorize(...)` calls. Use `--tenant` to scope repository reads and
565
- writes by `ctx.tenant`. Use `--events` to generate created, updated, and
566
- deleted domain events and publish them through `ctx.ports.eventBus`. The
565
+ writes by `ctx.tenant`; generated request contexts resolve tenants from
566
+ verified session metadata, while service contexts may pass an explicit
567
+ `tenantId`. Use `--events` to generate created, updated, and deleted domain
568
+ events and publish them through `ctx.ports.eventBus`. The
567
569
  starter ships no event bus, so `--events` also wires one: it adds
568
570
  `eventBus: EventBusPort` to `AppPorts`, defers the key in
569
571
  `infra/app-ports.ts`, registers `createInMemoryEventBusProvider()` in
@@ -880,6 +882,12 @@ Today it detects:
880
882
  - Notification dispatchers that bypass `ctx.ports.notifications`
881
883
  - Feature artifacts in non-canonical folders
882
884
 
885
+ When production-readiness diagnostics are present, human `doctor` output also
886
+ prints a production hardening checklist covering secrets and provider
887
+ credentials, verified auth and tenant authority, exposed operational routes,
888
+ CORS and proxy trust, upload and storage limits, readiness checks, worker
889
+ shutdown, webhook secrets, and least-privilege provider credentials.
890
+
883
891
  Provider package diagnostics come from package-owned `beignet.provider`
884
892
  metadata in installed package manifests. The CLI does not import provider
885
893
  implementation modules to discover those facts, and it reports malformed
@@ -1 +1 @@
1
- {"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../src/inspect.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../src/inspect.ts"],"names":[],"mappings":"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"}
package/dist/inspect.js CHANGED
@@ -135,9 +135,46 @@ export function formatDoctor(result, options = {}) {
135
135
  ${diagnostic.message}`;
136
136
  }));
137
137
  }
138
+ const checklist = productionHardeningChecklist(result.diagnostics);
139
+ if (checklist.length > 0) {
140
+ lines.push("", paint("Production Hardening Checklist:", "bold"), "");
141
+ lines.push(...checklist.map((item) => `- ${item}`));
142
+ }
138
143
  lines.push("", formatDoctorFooter(result));
139
144
  return lines.join("\n");
140
145
  }
146
+ const productionHardeningDiagnosticCodes = new Set([
147
+ "BEIGNET_AUTH_ROUTE_MISSING",
148
+ "BEIGNET_AUTH_TRUSTED_ORIGINS_MISSING",
149
+ "BEIGNET_BETTER_AUTH_SECRET_PLACEHOLDER",
150
+ "BEIGNET_BILLING_CHECKOUT_IDEMPOTENCY_MISSING",
151
+ "BEIGNET_BILLING_ENTITLEMENTS_PROVIDER_MISSING",
152
+ "BEIGNET_BILLING_IDEMPOTENCY_PORT_MISSING",
153
+ "BEIGNET_CORS_CREDENTIALS_WILDCARD",
154
+ "BEIGNET_CRON_SECRET_MISSING",
155
+ "BEIGNET_DEVTOOLS_ROUTE_EXPOSED",
156
+ "BEIGNET_ENTITLEMENTS_PORT_MISSING",
157
+ "BEIGNET_PAYMENTS_STRIPE_MEMORY_PROVIDER",
158
+ "BEIGNET_PAYMENTS_WEBHOOK_ROUTE_MISSING",
159
+ "BEIGNET_PROVIDER_ENV_MISSING",
160
+ "BEIGNET_READINESS_ROUTE_MISSING",
161
+ "BEIGNET_TENANT_HEADER_AUTHORITY",
162
+ "BEIGNET_UPLOAD_AUTHORIZATION_MISSING",
163
+ "BEIGNET_UPLOAD_SIZE_LIMIT_MISSING",
164
+ ]);
165
+ function productionHardeningChecklist(diagnostics) {
166
+ if (!diagnostics.some((diagnostic) => productionHardeningDiagnosticCodes.has(diagnostic.code))) {
167
+ return [];
168
+ }
169
+ return [
170
+ "Secrets and provider credentials are unique per environment, validated at startup, never committed, and never logged.",
171
+ "Auth routes, tenant resolution, authorization policies, and trusted origins derive authority from verified sessions, API keys, or trusted gateway metadata.",
172
+ "Devtools, OpenAPI, cron, webhooks, and operational routes have intentional exposure and app-owned authorization.",
173
+ "CORS origins, proxy IP trust, rate-limit keys, and request body limits match the production host topology.",
174
+ "Uploads and storage define max sizes, authorization or explicit public access, object visibility, safe content headers, and short direct-upload expirations.",
175
+ "Provider-backed dependencies have bounded readiness checks, worker shutdown behavior, webhook secrets, and least-privilege credentials.",
176
+ ];
177
+ }
141
178
  function formatDoctorFooter(result) {
142
179
  const count = (severity) => result.diagnostics.filter((diagnostic) => diagnostic.severity === severity)
143
180
  .length;
@@ -1222,6 +1259,26 @@ async function inspectProductionReadiness(targetDir, files, config, convention)
1222
1259
  message: `${file} defines an upload without maxSizeBytes. Add an explicit size limit so upload behavior is predictable before deployment.`,
1223
1260
  });
1224
1261
  }
1262
+ if (isFeatureUploadDefinition(file, config) &&
1263
+ containsCallExpression(source, "defineUpload") &&
1264
+ !/\bauthorize\s*(?:\(|:)/.test(source) &&
1265
+ !/\baccess\s*:\s*["']public["']/.test(source)) {
1266
+ diagnostics.push({
1267
+ severity: "warning",
1268
+ code: "BEIGNET_UPLOAD_AUTHORIZATION_MISSING",
1269
+ file,
1270
+ message: `${file} defines a protected upload without authorize(...). Add an authorize hook, or set access: "public" only for intentionally public uploads.`,
1271
+ });
1272
+ }
1273
+ if (isRouteServerOrInfraFile(file, config) &&
1274
+ containsRequestTenantHeaderAuthority(source)) {
1275
+ diagnostics.push({
1276
+ severity: "warning",
1277
+ code: "BEIGNET_TENANT_HEADER_AUTHORITY",
1278
+ file,
1279
+ message: `${file} reads x-tenant-id and turns it into tenant authority. Resolve request tenants from verified auth/session claims or trusted gateway metadata instead of a caller-controlled header.`,
1280
+ });
1281
+ }
1225
1282
  if (isRouteServerOrInfraFile(file, config) &&
1226
1283
  containsCallExpression(source, "createCorsHooks") &&
1227
1284
  /\bcredentials\s*:\s*true\b/.test(source) &&
@@ -1234,6 +1291,17 @@ async function inspectProductionReadiness(targetDir, files, config, convention)
1234
1291
  });
1235
1292
  }
1236
1293
  }
1294
+ for (const file of configFiles) {
1295
+ const source = await readCachedSource(targetDir, file, sourceCache);
1296
+ if (containsBetterAuthLocalSecret(source)) {
1297
+ diagnostics.push({
1298
+ severity: "warning",
1299
+ code: "BEIGNET_BETTER_AUTH_SECRET_PLACEHOLDER",
1300
+ file,
1301
+ message: `${file} contains the known local Better Auth placeholder secret. Keep it out of production configuration and require a unique BETTER_AUTH_SECRET before deployment.`,
1302
+ });
1303
+ }
1304
+ }
1237
1305
  const providerEntries = await readProviderListEntries(targetDir, files, config, sourceCache);
1238
1306
  const stripeMemoryProviderMismatch = await hasStripeMemoryProviderMismatch(targetDir, sourceCache, configFiles, installedPackages, providerEntries);
1239
1307
  diagnostics.push(...(await inspectEntitlementsProductionReadiness(targetDir, files, config, sourceCache)), ...(await inspectPaymentsProductionReadiness(targetDir, files, config, sourceCache, installedPackages, stripeMemoryProviderMismatch)));
@@ -1323,6 +1391,19 @@ function containsReadinessCheck(source) {
1323
1391
  return false;
1324
1392
  return /\bchecks\s*:/.test(source) || /\.checkHealth\s*\(/.test(source);
1325
1393
  }
1394
+ function containsBetterAuthLocalSecret(source) {
1395
+ if (!source.includes("local-dev-better-auth-secret-change-me"))
1396
+ return false;
1397
+ const hasProductionRuntimeGuard = /\bisProductionRuntime\s*=\s*process\.env\.NODE_ENV\s*===\s*["']production["']\s*&&\s*process\.env\.NEXT_PHASE\s*!==\s*["']phase-production-build["']/.test(source) &&
1398
+ /BETTER_AUTH_SECRET\s*:\s*isProductionRuntime\s*\?\s*BetterAuthSecret\s*:\s*BetterAuthSecret\.default\(LOCAL_BETTER_AUTH_SECRET\)/s.test(source);
1399
+ const hasProductionGuard = /BETTER_AUTH_SECRET\s*:\s*process\.env\.NODE_ENV\s*===\s*["']production["']\s*\?\s*BetterAuthSecret\s*:\s*BetterAuthSecret\.default\(LOCAL_BETTER_AUTH_SECRET\)/s.test(source);
1400
+ return !(hasProductionRuntimeGuard || hasProductionGuard);
1401
+ }
1402
+ function containsRequestTenantHeaderAuthority(source) {
1403
+ return (/(?:(?:\breq|\brequest)\.headers|(?<!\.)\bheaders)\.get\(\s*["']x-tenant-id["']\s*\)/.test(source) &&
1404
+ (/\bcreateTenant\s*\(/.test(source) ||
1405
+ /\btenant\s*:\s*\{?\s*id\s*:/.test(source)));
1406
+ }
1326
1407
  async function inspectEntitlementsProductionReadiness(targetDir, files, config, sourceCache) {
1327
1408
  if (!(await usesEntitlementChecks(targetDir, files, sourceCache)) ||
1328
1409
  (await appPortsIncludesPort(targetDir, files, config, sourceCache, "entitlements"))) {