@basaltkit/ai 0.9.2 → 0.10.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/dist/index.d.ts CHANGED
@@ -275,6 +275,8 @@ interface AppFileInfo {
275
275
  fastifyLoggerConfigured: boolean;
276
276
  /** In-memory (non-durable) sources still wired, e.g. `MemoryTenantSource`. */
277
277
  memorySources: string[];
278
+ /** Every `<name>Plugin(` factory call in the app file (unfiltered by capability). */
279
+ pluginCalls: string[];
278
280
  }
279
281
  interface ServerFileInfo {
280
282
  path: string;
package/dist/index.js CHANGED
@@ -367,7 +367,10 @@ function detectAppFile(reader) {
367
367
  const memorySources = ["MemoryTenantSource", "MemoryUserSource"].filter(
368
368
  (source) => found.content.includes(source)
369
369
  );
370
- return { path: found.path, plugins, fastifyLoggerConfigured, memorySources };
370
+ const pluginCalls = [
371
+ ...new Set(Array.from(found.content.matchAll(/\b([A-Za-z][A-Za-z0-9]*Plugin)\s*\(/g), (m) => m[1]))
372
+ ];
373
+ return { path: found.path, plugins, fastifyLoggerConfigured, memorySources, pluginCalls };
371
374
  }
372
375
  function detectServerFile(reader) {
373
376
  const found = firstExisting(reader, SERVER_CANDIDATES);
@@ -527,7 +530,47 @@ var DEFAULT_RULES = [
527
530
  };
528
531
  }
529
532
  }),
533
+ defineRule({
534
+ id: "missing-security-plugin",
535
+ category: "security",
536
+ check(ctx) {
537
+ if (!ctx.app) return null;
538
+ if (ctx.app.pluginCalls.includes("securityPlugin")) return null;
539
+ return {
540
+ id: "missing-security-plugin",
541
+ title: "No securityPlugin \u2014 responses ship without secure headers",
542
+ severity: "warning",
543
+ category: "security",
544
+ detected: `${ctx.app.path} registers no securityPlugin()`,
545
+ recommended: "add securityPlugin() so secure response headers are set, and enable rate limiting + a CORS allow-list for production",
546
+ reason: "Without it the API returns no security headers (HSTS, X-Frame-Options, nosniff, \u2026) and applies no rate limiting \u2014 a fresh deploy is unprotected at the edge. The security primitives exist but are off until wired.",
547
+ fix: "securityPlugin({ /* headers on by default */ rateLimit: { limit: 120, windowMs: 60_000 } })",
548
+ docs: "/guide/security"
549
+ };
550
+ }
551
+ }),
530
552
  // ── tenancy ───────────────────────────────────────────────────────────────
553
+ defineRule({
554
+ id: "missing-tenant-membership",
555
+ category: "tenancy",
556
+ check(ctx) {
557
+ if (!ctx.app || !ctx.stack.tenancy || !ctx.stack.auth) return null;
558
+ const teamsAvailable = ctx.app.pluginCalls.includes("teamsPlugin") || ctx.installed.includes("@basaltkit/teams");
559
+ if (!teamsAvailable) return null;
560
+ if (ctx.app.pluginCalls.includes("tenantMembershipPlugin")) return null;
561
+ return {
562
+ id: "missing-tenant-membership",
563
+ title: "Tenant is resolved from the request but membership is never enforced",
564
+ severity: "error",
565
+ category: "tenancy",
566
+ detected: `${ctx.app.path} wires tenancy + auth (+ teams) but no tenantMembershipPlugin`,
567
+ recommended: "register tenantMembershipPlugin() so every authenticated, tenant-scoped request verifies the user belongs to the resolved tenant",
568
+ reason: "Resolvers take the tenant from client input (x-tenant-id / Host). Without a membership check, any authenticated user can act on another tenant just by changing that header \u2014 a cross-tenant data breach. Tenant resolution is identification, never authorization.",
569
+ fix: "tenantMembershipPlugin() // central routes opt out with meta: { central: true }",
570
+ docs: "/guide/security"
571
+ };
572
+ }
573
+ }),
531
574
  defineRule({
532
575
  id: "tenant-scoping-missing",
533
576
  category: "tenancy",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basaltkit/ai",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "description": "AI-native developer experience for Basalt: a provider-agnostic engine plus `basalt ai` CLI that understands the project's stack, analyzes it, and diagnoses issues — following the framework's own conventions, not inventing new ones.",
5
5
  "license": "MIT",
6
6
  "type": "module",