@mesh-tech/mesh-cli 0.22.4 → 0.23.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/bin/mesh.js CHANGED
@@ -10798,6 +10798,20 @@ function authzSurface(app) {
10798
10798
  walk2(app);
10799
10799
  return hits;
10800
10800
  }
10801
+ function stringList(value) {
10802
+ if (!value) return void 0;
10803
+ const text = value.trim();
10804
+ if (!text.startsWith("[") || !text.endsWith("]")) return void 0;
10805
+ const out = [];
10806
+ for (const entry of splitTopLevel(text.slice(1, -1))) {
10807
+ const item = stripLeadingComments(entry).trim();
10808
+ if (!item) continue;
10809
+ const m = /^(["'])((?:(?!\1).)*)\1$/.exec(stripTrailingComments(item));
10810
+ if (!m) return void 0;
10811
+ out.push(m[2]);
10812
+ }
10813
+ return out;
10814
+ }
10801
10815
  function balancedSpan(text, open) {
10802
10816
  const closers = { "(": ")", "{": "}", "[": "]" };
10803
10817
  const stack = [];
@@ -10986,6 +11000,7 @@ function docsSiteFindings(text) {
10986
11000
  why: `mesh.apps.${decl.ctor} declares a docs site but neither its kind nor its audience \u2014 who may read it would fall out of the name heuristic, and a vendor surface must never default to partner-readable that way`
10987
11001
  });
10988
11002
  }
11003
+ for (const f of docsAudienceUngrantable(text, decl, args, docs, httpText)) findings.push(f);
10989
11004
  }
10990
11005
  for (const decl of surfaceDeclarations(text, RE_APIDOCS_NEW)) {
10991
11006
  const options = optionsObject(decl.body);
@@ -11001,6 +11016,56 @@ function docsSiteFindings(text) {
11001
11016
  }
11002
11017
  return findings;
11003
11018
  }
11019
+ function docsAudienceUngrantable(text, decl, args, docs, httpText) {
11020
+ const findings = [];
11021
+ let roles;
11022
+ if (docs.has("audience")) {
11023
+ const audience = objectLiteralOf(docs.get("audience"), text);
11024
+ roles = audience ? stringList(topLevelProperties(audience).get("roles")) : void 0;
11025
+ } else {
11026
+ const kind = decl.ctor === "AppApiSurface" ? "app" : decl.ctor === "VendorApiSurface" ? "vendor" : /^["']([a-z]+)["']$/.exec(args.get("kind") ?? "")?.[1];
11027
+ if (kind === "app") roles = DEFAULT_PARTNER_DOCS_ROLES;
11028
+ }
11029
+ if (!roles || roles.length === 0) return findings;
11030
+ const identities = [];
11031
+ for (const d of surfaceDeclarations(text, RE_IDENTITY_NEW)) {
11032
+ const options = optionsObject(d.body);
11033
+ if (!options) continue;
11034
+ const binding = [...text.matchAll(RE_IDENTITY_BINDING)].find((m) => text.indexOf(d.body, m.index) === m.index + m[0].length - 1)?.[1];
11035
+ identities.push({ binding, options: topLevelProperties(options) });
11036
+ }
11037
+ if (identities.length === 0) return findings;
11038
+ const docsAuth = objectLiteralOf(docs.get("auth"), text);
11039
+ const clientExpr = (docsAuth ? topLevelProperties(docsAuth).get("clientId") : void 0) ?? (httpText ? topLevelProperties(objectLiteralOf(topLevelProperties(httpText).get("auth"), text) ?? "{}").get("clientId") : void 0);
11040
+ const appName = clientExpr ? RE_IDENTITY_APP_CLIENT.exec(clientExpr)?.[1] : void 0;
11041
+ const clientRoot = clientExpr ? /^([A-Za-z_$][\w$]*)\.applications\./.exec(clientExpr.trim())?.[1] : void 0;
11042
+ const identity = (clientRoot ? identities.find((id) => id.binding === clientRoot) : void 0) ?? (identities.length === 1 ? identities[0] : void 0);
11043
+ if (!identity) return findings;
11044
+ const rolesText = identity.options.get("roles");
11045
+ const coarseText = identity.options.get("coarseRoles");
11046
+ const declared = rolesText !== void 0 ? stringList(rolesText) : coarseText !== void 0 ? stringList(coarseText) : [];
11047
+ if (declared) {
11048
+ for (const role of roles) {
11049
+ if (declared.includes(role)) continue;
11050
+ findings.push({
11051
+ code: "DOCS_AUDIENCE_UNGRANTABLE",
11052
+ line: decl.line,
11053
+ why: `the docs audience gates on role "${role}", which the app's ZitadelAppIdentity does not declare in roles \u2014 the Hub cannot grant it, so no one can read the site`
11054
+ });
11055
+ }
11056
+ }
11057
+ if (!appName) return findings;
11058
+ const apps = objectLiteralOf(identity.options.get("applications"), text);
11059
+ const app = apps ? objectLiteralOf(topLevelProperties(apps).get(appName), text) : void 0;
11060
+ if (app && topLevelProperties(app).get("idTokenRoleAssertion") === "false") {
11061
+ findings.push({
11062
+ code: "DOCS_AUDIENCE_UNGRANTABLE",
11063
+ line: decl.line,
11064
+ why: `the docs sign-in client is applications.${appName} on the ZitadelAppIdentity, which sets idTokenRoleAssertion: false \u2014 the proxy's ID token then carries no roles and the site's gate admits nobody`
11065
+ });
11066
+ }
11067
+ return findings;
11068
+ }
11004
11069
  function listApps(root) {
11005
11070
  const apps = [];
11006
11071
  const appsDir = path22.join(root, "apps");
@@ -11206,7 +11271,7 @@ function registerAppCommands(program2) {
11206
11271
  }
11207
11272
  });
11208
11273
  }
11209
- var APP_CHECK_RULES, APP_CHECK_CODES, SRC_EXT, SKIP_DIRS, NON_SERVICE_DIRS, RE_HTML, RE_OIDC, RE_COOKIE, RE_LOGGER, RE_EXPOSURE, RE_POINTER, RE_METADATA, RE_IAC_GRANT, RE_POINTER_OVERRIDE, RE_PASSWORD, RE_ALLOWLIST, RE_USERS_TABLE, RE_SURFACE_NEW, RE_APIDOCS_NEW, DOCS_SITE_FIELDS, RE_DEV_BYPASS, RE_PROPERTY;
11274
+ var APP_CHECK_RULES, APP_CHECK_CODES, SRC_EXT, SKIP_DIRS, NON_SERVICE_DIRS, RE_HTML, RE_OIDC, RE_COOKIE, RE_LOGGER, RE_EXPOSURE, RE_POINTER, RE_METADATA, RE_IAC_GRANT, RE_POINTER_OVERRIDE, RE_PASSWORD, RE_ALLOWLIST, RE_USERS_TABLE, RE_SURFACE_NEW, RE_APIDOCS_NEW, RE_IDENTITY_NEW, RE_IDENTITY_BINDING, DEFAULT_PARTNER_DOCS_ROLES, RE_IDENTITY_APP_CLIENT, DOCS_SITE_FIELDS, RE_DEV_BYPASS, RE_PROPERTY;
11210
11275
  var init_app_check = __esm({
11211
11276
  "libs/mesh-cli/src/commands/app-check.ts"() {
11212
11277
  "use strict";
@@ -11319,6 +11384,13 @@ var init_app_check = __esm({
11319
11384
  severity: "block",
11320
11385
  remediation: 'declare the surface as mesh.apps.AppApiSurface or VendorApiSurface (or pass kind: "app" | "vendor"), or state docs: { audience: { roles: [\u2026] } }',
11321
11386
  scope: "app"
11387
+ },
11388
+ DOCS_AUDIENCE_UNGRANTABLE: {
11389
+ gate: "H",
11390
+ title: "Docs audience grantable from the Hub",
11391
+ severity: "block",
11392
+ remediation: `declare every audience role on the ZitadelAppIdentity (roles: ["docs"]) so the Hub can grant it, and do not set idTokenRoleAssertion: false on the application the docs sign-in proxy uses (applications.<name>) \u2014 the identity asserts roles into the ID token by default, and the site's gate reads that token`,
11393
+ scope: "app"
11322
11394
  }
11323
11395
  };
11324
11396
  APP_CHECK_CODES = Object.keys(APP_CHECK_RULES);
@@ -11352,6 +11424,10 @@ var init_app_check = __esm({
11352
11424
  RE_USERS_TABLE = /create table (if not exists )?"?(users|roles|user_roles|permissions)"?/i;
11353
11425
  RE_SURFACE_NEW = /new\s+mesh\.apps\.(ApiSurface|AppApiSurface|VendorApiSurface|Integration)\s*\(/g;
11354
11426
  RE_APIDOCS_NEW = /new\s+mesh\.apps\.ApiDocs\s*\(/g;
11427
+ RE_IDENTITY_NEW = /new\s+mesh\.auth\.(ZitadelAppIdentity)\s*\(/g;
11428
+ RE_IDENTITY_BINDING = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=]+)?=\s*new\s+mesh\.auth\.ZitadelAppIdentity\s*\(/g;
11429
+ DEFAULT_PARTNER_DOCS_ROLES = ["docs"];
11430
+ RE_IDENTITY_APP_CLIENT = /\bapplications\.([A-Za-z_$][\w$]*)\.clientId\b/;
11355
11431
  DOCS_SITE_FIELDS = ["pages", "playground", "audience", "basePath", "ingress", "auth"];
11356
11432
  RE_DEV_BYPASS = /^["']dev-bypass["']$/;
11357
11433
  RE_PROPERTY = /^(?:"([^"]+)"|'([^']+)'|([A-Za-z_$][\w$]*))\s*(?::\s*([\s\S]*))?$/;