getaura 0.4.0 → 0.5.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.js CHANGED
@@ -9,8 +9,8 @@ var __export = (target, all) => {
9
9
  import { Command, Option } from "commander";
10
10
 
11
11
  // src/commands/apply.ts
12
- import { existsSync as existsSync25, rmSync as rmSync5 } from "fs";
13
- import path42 from "path";
12
+ import { existsSync as existsSync27, rmSync as rmSync5 } from "fs";
13
+ import path45 from "path";
14
14
 
15
15
  // ../shared/src/types.ts
16
16
  var CATEGORIES = ["security", "agent-setup", "foundations", "structure"];
@@ -128,8 +128,42 @@ function findSecrets(text) {
128
128
  out.push({ patternId: p2.id, label: p2.label, service: p2.service, confidence: p2.confidence, value, index: m.index ?? 0 });
129
129
  }
130
130
  }
131
- out.sort((a, b) => a.index - b.index || b.value.length - a.value.length);
132
- return out.filter((m, i) => !out.slice(0, i).some((o) => m.index >= o.index && m.index + m.value.length <= o.index + o.value.length));
131
+ const inside = (m, o) => m !== o && m.index >= o.index && m.index + m.value.length <= o.index + o.value.length;
132
+ const specific = out.filter((m) => !(m.patternId === "generic-assignment" && out.some((o) => o.patternId !== "generic-assignment" && inside(o, m))));
133
+ specific.sort((a, b) => a.index - b.index || b.value.length - a.value.length);
134
+ return specific.filter((m, i) => !specific.slice(0, i).some((o) => inside(m, o)));
135
+ }
136
+ var PLACEHOLDER = /example|placeholder|your[_-]?|xxx|changeme|change[_-]me|dummy|fake|mock|sample|redacted|replace|insert|<[^>]*>|\{\{|\.\.\.|\*\*\*|(?:^|[_-])test|test[_-]|invalid|synthetic/i;
137
+ var KEY_PLACEHOLDER = /x{8,}|X{8,}|EXAMPLE|\.\.\./;
138
+ var COMMENT_LINE = /^\s*(?:\/\/|#|\*|\/\*|<!--|--\s)/;
139
+ var WORDS_ONLY = /^[A-Za-z]+(?:[-_.:][A-Za-z]+)+$/;
140
+ var LOCAL_HOST = /^(?:localhost|127\.0\.0\.1|0\.0\.0\.0|host\.docker\.internal|\[::1\])$/i;
141
+ function lineAt(text, index) {
142
+ const start = text.lastIndexOf("\n", index - 1) + 1;
143
+ const end = text.indexOf("\n", index);
144
+ return text.slice(start, end === -1 ? void 0 : end);
145
+ }
146
+ var assignedValue = (match) => match.match(/[:=]\s*["'`]([^"'`]*)["'`]\s*$/)?.[1] ?? match;
147
+ function isPlaceholderSecret(m, text) {
148
+ if (m.patternId === "database-url") {
149
+ const [, password = "", host = ""] = m.value.match(/^[a-z+]+:\/\/[^:]+:([^@]*)@([^:/?]+)/i) ?? [];
150
+ if (/^[[<{$]|passw|pwd|secret|changeme|your|example|xxx|\*{3,}/i.test(password)) return true;
151
+ return LOCAL_HOST.test(host) || !host.includes(".");
152
+ }
153
+ if (m.patternId === "private-key") {
154
+ const body = m.value.replace(/-----[^-]+-----/g, "").replace(/\\n|\s|"/g, "");
155
+ return body.length < 64 || body.includes("...");
156
+ }
157
+ if (m.patternId === "generic-assignment") {
158
+ const value = assignedValue(m.value);
159
+ if (PLACEHOLDER.test(value) || WORDS_ONLY.test(value) || /^env\(/i.test(value)) return true;
160
+ if (COMMENT_LINE.test(lineAt(text, m.index))) return true;
161
+ return /^[0-9a-f]{32}$/i.test(value) && /algolia|docsearch/i.test(text);
162
+ }
163
+ return KEY_PLACEHOLDER.test(m.value);
164
+ }
165
+ function findLeakedSecrets(text) {
166
+ return findSecrets(text).filter((m) => !isPlaceholderSecret(m, text));
133
167
  }
134
168
  function maskSecret(value) {
135
169
  if (value.includes("PRIVATE KEY")) return "-----BEGIN PRIVATE KEY----- [masked]";
@@ -978,10 +1012,10 @@ function mergeDefs(...defs) {
978
1012
  function cloneDef(schema) {
979
1013
  return mergeDefs(schema._zod.def);
980
1014
  }
981
- function getElementAtPath(obj, path52) {
982
- if (!path52)
1015
+ function getElementAtPath(obj, path55) {
1016
+ if (!path55)
983
1017
  return obj;
984
- return path52.reduce((acc, key) => acc?.[key], obj);
1018
+ return path55.reduce((acc, key) => acc?.[key], obj);
985
1019
  }
986
1020
  function promiseAllObject(promisesObj) {
987
1021
  const keys = Object.keys(promisesObj);
@@ -1321,11 +1355,11 @@ function explicitlyAborted(x, startIndex = 0) {
1321
1355
  }
1322
1356
  return false;
1323
1357
  }
1324
- function prefixIssues(path52, issues) {
1358
+ function prefixIssues(path55, issues) {
1325
1359
  return issues.map((iss) => {
1326
1360
  var _a3;
1327
1361
  (_a3 = iss).path ?? (_a3.path = []);
1328
- iss.path.unshift(path52);
1362
+ iss.path.unshift(path55);
1329
1363
  return iss;
1330
1364
  });
1331
1365
  }
@@ -1775,16 +1809,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
1775
1809
  }
1776
1810
  function formatError(error62, mapper = (issue2) => issue2.message) {
1777
1811
  const fieldErrors = { _errors: [] };
1778
- const processError = (error63, path52 = []) => {
1812
+ const processError = (error63, path55 = []) => {
1779
1813
  for (const issue2 of error63.issues) {
1780
1814
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1781
- issue2.errors.map((issues) => processError({ issues }, [...path52, ...issue2.path]));
1815
+ issue2.errors.map((issues) => processError({ issues }, [...path55, ...issue2.path]));
1782
1816
  } else if (issue2.code === "invalid_key") {
1783
- processError({ issues: issue2.issues }, [...path52, ...issue2.path]);
1817
+ processError({ issues: issue2.issues }, [...path55, ...issue2.path]);
1784
1818
  } else if (issue2.code === "invalid_element") {
1785
- processError({ issues: issue2.issues }, [...path52, ...issue2.path]);
1819
+ processError({ issues: issue2.issues }, [...path55, ...issue2.path]);
1786
1820
  } else {
1787
- const fullpath = [...path52, ...issue2.path];
1821
+ const fullpath = [...path55, ...issue2.path];
1788
1822
  if (fullpath.length === 0) {
1789
1823
  fieldErrors._errors.push(mapper(issue2));
1790
1824
  } else {
@@ -1823,17 +1857,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
1823
1857
  }
1824
1858
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
1825
1859
  const result = { errors: [] };
1826
- const processError = (error63, path52 = []) => {
1860
+ const processError = (error63, path55 = []) => {
1827
1861
  var _a3;
1828
1862
  for (const issue2 of error63.issues) {
1829
1863
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1830
- issue2.errors.map((issues) => processError({ issues }, [...path52, ...issue2.path]));
1864
+ issue2.errors.map((issues) => processError({ issues }, [...path55, ...issue2.path]));
1831
1865
  } else if (issue2.code === "invalid_key") {
1832
- processError({ issues: issue2.issues }, [...path52, ...issue2.path]);
1866
+ processError({ issues: issue2.issues }, [...path55, ...issue2.path]);
1833
1867
  } else if (issue2.code === "invalid_element") {
1834
- processError({ issues: issue2.issues }, [...path52, ...issue2.path]);
1868
+ processError({ issues: issue2.issues }, [...path55, ...issue2.path]);
1835
1869
  } else {
1836
- const fullpath = [...path52, ...issue2.path];
1870
+ const fullpath = [...path55, ...issue2.path];
1837
1871
  if (fullpath.length === 0) {
1838
1872
  result.errors.push(mapper(issue2));
1839
1873
  continue;
@@ -1872,8 +1906,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
1872
1906
  }
1873
1907
  function toDotPath(_path) {
1874
1908
  const segs = [];
1875
- const path52 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1876
- for (const seg of path52) {
1909
+ const path55 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1910
+ for (const seg of path55) {
1877
1911
  if (typeof seg === "number")
1878
1912
  segs.push(`[${seg}]`);
1879
1913
  else if (typeof seg === "symbol")
@@ -3250,12 +3284,12 @@ function isIso7064Mod97(iban3) {
3250
3284
  let remainder = 0;
3251
3285
  const len = iban3.length;
3252
3286
  for (let i = 4; i < len; i++) {
3253
- const code = iban3.charCodeAt(i);
3254
- remainder = (code >= 65 ? remainder * 100 + (code - 55) : remainder * 10 + (code - 48)) % 97;
3287
+ const code2 = iban3.charCodeAt(i);
3288
+ remainder = (code2 >= 65 ? remainder * 100 + (code2 - 55) : remainder * 10 + (code2 - 48)) % 97;
3255
3289
  }
3256
3290
  for (let i = 0; i < 4; i++) {
3257
- const code = iban3.charCodeAt(i);
3258
- remainder = (code >= 65 ? remainder * 100 + (code - 55) : remainder * 10 + (code - 48)) % 97;
3291
+ const code2 = iban3.charCodeAt(i);
3292
+ remainder = (code2 >= 65 ? remainder * 100 + (code2 - 55) : remainder * 10 + (code2 - 48)) % 97;
3259
3293
  }
3260
3294
  return remainder === 1;
3261
3295
  }
@@ -13173,12 +13207,12 @@ function compileFn(schema, options) {
13173
13207
  doc.write(outputAccessor === null ? `return true;` : `return ${outputAccessor};`);
13174
13208
  const constantNames = ["INVALID", ...ctx.constants.keys()];
13175
13209
  const constantValues = [INVALID, ...ctx.constants.values()];
13176
- const code = doc.content.join("\n");
13210
+ const code2 = doc.content.join("\n");
13177
13211
  const fullCode = options?.debug ? constantNames.length > 0 ? `// Constants: ${constantNames.join(", ")}
13178
- ${code}` : code : "";
13212
+ ${code2}` : code2 : "";
13179
13213
  const F = Function;
13180
13214
  const factoryCode = `return (input) => {
13181
- ${code}
13215
+ ${code2}
13182
13216
  }`;
13183
13217
  let fn;
13184
13218
  try {
@@ -16318,8 +16352,8 @@ var contributors = {
16318
16352
  function aggregateChecks(schema) {
16319
16353
  const agg = {};
16320
16354
  const def = schema._zod.def;
16321
- const list9 = schema._zod.traits.has("$ZodCheck") ? [schema, ...def.checks ?? []] : def.checks ?? [];
16322
- for (const ch of list9)
16355
+ const list11 = schema._zod.traits.has("$ZodCheck") ? [schema, ...def.checks ?? []] : def.checks ?? [];
16356
+ for (const ch of list11)
16323
16357
  contributors[ch._zod.def.check]?.(agg, ch._zod.def);
16324
16358
  const bag = schema._zod.bag;
16325
16359
  if (bag.minimum !== void 0)
@@ -18975,13 +19009,13 @@ function resolveRef(ref, ctx) {
18975
19009
  if (!ref.startsWith("#")) {
18976
19010
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
18977
19011
  }
18978
- const path52 = ref.slice(1).split("/").filter(Boolean);
18979
- if (path52.length === 0) {
19012
+ const path55 = ref.slice(1).split("/").filter(Boolean);
19013
+ if (path55.length === 0) {
18980
19014
  return ctx.rootSchema;
18981
19015
  }
18982
19016
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
18983
- if (path52[0] === defsKey) {
18984
- const key = path52[1] === void 0 ? void 0 : decodeJSONPointerSegment(path52[1]);
19017
+ if (path55[0] === defsKey) {
19018
+ const key = path55[1] === void 0 ? void 0 : decodeJSONPointerSegment(path55[1]);
18985
19019
  if (!key || !ctx.defs[key]) {
18986
19020
  throw new Error(`Reference not found: ${ref}`);
18987
19021
  }
@@ -20189,6 +20223,29 @@ var SECURITY_3 = {
20189
20223
  facts: external_exports.strictObject({}),
20190
20224
  items: { "client-imported": modules, unguarded: modules }
20191
20225
  },
20226
+ /**
20227
+ * Supabase edge functions that use admin access or paid keys without checking the caller.
20228
+ * `jwtOff`: `verify_jwt = false` in supabase/config.toml. Part facts: functions seen, ones with a check, ones marked aura-ignore.
20229
+ */
20230
+ "security.edge-functions": {
20231
+ v: 1,
20232
+ facts: external_exports.strictObject({ functions: count, guarded: count, ignored: count }),
20233
+ items: {
20234
+ "open-admin": external_exports.strictObject({ jwtOff: flag }),
20235
+ "open-keys": external_exports.strictObject({ jwtOff: flag, keys: count })
20236
+ }
20237
+ },
20238
+ /** Single-page apps (Vite): server keys and server-only packages in code that ships to the browser. */
20239
+ "security.browser-secrets": {
20240
+ v: 1,
20241
+ /** `files`: browser code files looked at. */
20242
+ facts: external_exports.strictObject({ files: count }),
20243
+ items: {
20244
+ "public-env": external_exports.strictObject({ names: count }),
20245
+ "server-package": external_exports.strictObject({ packages: count }),
20246
+ "env-example": external_exports.strictObject({ names: count })
20247
+ }
20248
+ },
20192
20249
  "security.risky-code": {
20193
20250
  v: 1,
20194
20251
  facts: external_exports.strictObject({ files: count, flaggedFiles: count }),
@@ -20412,6 +20469,12 @@ var FOUNDATIONS_1 = {
20412
20469
  "no-loading": external_exports.strictObject({ app: count, pages: count, routes: count })
20413
20470
  }
20414
20471
  },
20472
+ /** Single-page apps (Vite): an error boundary, and a "page not found" route when the app has a router. */
20473
+ "foundations.spa-errors": {
20474
+ v: 1,
20475
+ facts: external_exports.strictObject({ router: flag }),
20476
+ items: { "no-error-boundary": external_exports.strictObject({}), "no-catch-all": external_exports.strictObject({}) }
20477
+ },
20415
20478
  "foundations.leftovers": {
20416
20479
  v: 1,
20417
20480
  /** App files read. */
@@ -20810,9 +20873,9 @@ function run(cmd, args, opts = {}) {
20810
20873
  if (timer) clearTimeout(timer);
20811
20874
  resolve({ code: 127, stdout, stderr: stderr + String(err) });
20812
20875
  });
20813
- child.on("close", (code) => {
20876
+ child.on("close", (code2) => {
20814
20877
  if (timer) clearTimeout(timer);
20815
- resolve({ code: code ?? 1, stdout, stderr, timedOut });
20878
+ resolve({ code: code2 ?? 1, stdout, stderr, timedOut });
20816
20879
  });
20817
20880
  if (opts.input !== void 0) child.stdin.end(opts.input);
20818
20881
  else child.stdin.end();
@@ -21076,11 +21139,11 @@ var github = {
21076
21139
  isPrivate: Boolean(info.isPrivate),
21077
21140
  defaultBranch: info.defaultBranchRef?.name || "main",
21078
21141
  gh,
21079
- get: (path52) => {
21080
- let result = cache3.get(path52);
21142
+ get: (path55) => {
21143
+ let result = cache3.get(path55);
21081
21144
  if (!result) {
21082
- result = gh(["api", path52]);
21083
- cache3.set(path52, result);
21145
+ result = gh(["api", path55]);
21146
+ cache3.set(path55, result);
21084
21147
  }
21085
21148
  return result;
21086
21149
  }
@@ -21331,11 +21394,11 @@ var supabase = {
21331
21394
  return result;
21332
21395
  }
21333
21396
  };
21334
- const read2 = await call("GET", `/v1/projects/${ref}`);
21335
- if (read2.status === 401)
21397
+ const read3 = await call("GET", `/v1/projects/${ref}`);
21398
+ if (read3.status === 401)
21336
21399
  return { ok: false, problem: "Your Supabase login has expired. Run `npx supabase login`, then try again.", errorType: "CliNotSignedIn" };
21337
- const project = read2.json;
21338
- if (read2.status !== 200 || !project?.name)
21400
+ const project = read3.json;
21401
+ if (read3.status !== 200 || !project?.name)
21339
21402
  return {
21340
21403
  ok: false,
21341
21404
  // Projects created through Vercel's Supabase integration live in a Supabase account linked to Vercel.
@@ -21433,14 +21496,14 @@ var parse4 = (r) => {
21433
21496
  };
21434
21497
  var projectInfo = async (ctx) => await ctx.get(`/v9/projects/${ctx.projectId}${ctx.team}`);
21435
21498
  async function exposedSecrets(ctx) {
21436
- const list9 = await ctx.get(`/v10/projects/${ctx.projectId}/env?decrypt=true${ctx.team.replace("?", "&")}`);
21437
- const envs = list9?.envs ?? [];
21499
+ const list11 = await ctx.get(`/v10/projects/${ctx.projectId}/env?decrypt=true${ctx.team.replace("?", "&")}`);
21500
+ const envs = list11?.envs ?? [];
21438
21501
  return [...new Set(envs.filter((e) => PUBLIC_PREFIX.test(e.key) && (SECRET_NAME.test(e.key) || e.value && containsSecret(e.value))).map((e) => e.key))];
21439
21502
  }
21440
21503
  async function envReading(ctx) {
21441
- const list9 = await ctx.get(`/v10/projects/${ctx.projectId}/env${ctx.team}`);
21442
- if (!Array.isArray(list9?.envs)) throw new Error("Vercel didn\u2019t return the environment variables");
21443
- const names = (keep) => [...new Set(list9.envs?.filter(keep).map((e) => e.key))].sort();
21504
+ const list11 = await ctx.get(`/v10/projects/${ctx.projectId}/env${ctx.team}`);
21505
+ if (!Array.isArray(list11?.envs)) throw new Error("Vercel didn\u2019t return the environment variables");
21506
+ const names = (keep) => [...new Set(list11.envs?.filter(keep).map((e) => e.key))].sort();
21444
21507
  const targets2 = (e) => Array.isArray(e.target) ? e.target : e.target ? [e.target] : [];
21445
21508
  return {
21446
21509
  production: names((e) => targets2(e).includes("production")),
@@ -21842,6 +21905,15 @@ var ENV_SERVICES = [
21842
21905
  [/^GOOGLE_/, "Google Cloud"],
21843
21906
  [/^SLACK_/, "Slack"]
21844
21907
  ];
21908
+ var EDGE_FUNCTION = /(?:^|\/)supabase\/functions\//;
21909
+ var SUPABASE_FUNCTION_ENV = /* @__PURE__ */ new Set([
21910
+ "SUPABASE_URL",
21911
+ "SUPABASE_ANON_KEY",
21912
+ "SUPABASE_SERVICE_ROLE_KEY",
21913
+ "SUPABASE_DB_URL",
21914
+ "SUPABASE_PUBLISHABLE_KEY",
21915
+ "SUPABASE_SECRET_KEY"
21916
+ ]);
21845
21917
  var BUILTIN_ENV = /* @__PURE__ */ new Set([
21846
21918
  "USERPROFILE",
21847
21919
  "APPDATA",
@@ -21894,15 +21966,16 @@ function inStringLiteral(text, index) {
21894
21966
  const top = stack.at(-1);
21895
21967
  return top === "'" || top === '"' || top === "`";
21896
21968
  }
21897
- function findEnvRefs(files, read2) {
21969
+ function findEnvRefs(files, read3) {
21898
21970
  const map2 = /* @__PURE__ */ new Map();
21899
21971
  for (const f of files) {
21900
21972
  if (!isSourceFile(f) || isTestFile(f) || /(?:^|\/)(?:node_modules|\.next)\//.test(f)) continue;
21901
- const text = read2(f);
21973
+ const text = read3(f);
21902
21974
  if (!text) continue;
21903
21975
  for (const m of text.matchAll(ENV_REF)) {
21904
21976
  const name = m[1] ?? m[2] ?? m[3] ?? m[4];
21905
21977
  if (!name || /[a-z]/.test(name) || BUILTIN_ENV.has(name) || inStringLiteral(text, m.index)) continue;
21978
+ if (EDGE_FUNCTION.test(f) && SUPABASE_FUNCTION_ENV.has(name)) continue;
21906
21979
  const fileSet = map2.get(name);
21907
21980
  if (fileSet) fileSet.add(f);
21908
21981
  else map2.set(name, /* @__PURE__ */ new Set([f]));
@@ -21915,7 +21988,7 @@ function findEnvRefs(files, read2) {
21915
21988
  public: /^(?:NEXT_PUBLIC_|VITE_|EXPO_PUBLIC_|PUBLIC_)/.test(name)
21916
21989
  }));
21917
21990
  }
21918
- function detectStack(root, files, pkg2, read2) {
21991
+ function detectStack(root, files, pkg2, read3) {
21919
21992
  const deps2 = allDeps(pkg2);
21920
21993
  const has2 = (f) => files.includes(f) || existsSync5(path6.join(root, f));
21921
21994
  const hasDir = (d) => files.some((f) => f.startsWith(`${d}/`));
@@ -21944,6 +22017,11 @@ function detectStack(root, files, pkg2, read2) {
21944
22017
  const pages = hasDir(`${base2}pages`);
21945
22018
  nextjs = { version: deps2.next.replace(/^[^\d]*/, ""), router: app2 && pages ? "both" : app2 ? "app" : pages ? "pages" : null, srcDir };
21946
22019
  }
22020
+ const serverRendered = ["@remix-run/dev", "@react-router/dev", "@tanstack/react-start", "vike", "vite-plugin-ssr"].some((d) => deps2[d]);
22021
+ let vite = null;
22022
+ if (!nextjs && !serverRendered && deps2.vite && deps2.react && files.some((f) => /^vite\.config\.[cm]?[jt]s$/.test(f))) {
22023
+ vite = { router: deps2["react-router-dom"] || deps2["react-router"] ? "react-router" : null, lovable: Boolean(deps2["lovable-tagger"]) };
22024
+ }
21947
22025
  let supabase2 = null;
21948
22026
  if (services.has("Supabase")) {
21949
22027
  supabase2 = { migrationsDir: hasDir("supabase/migrations") ? "supabase/migrations" : null, hasConfig: has2("supabase/config.toml") };
@@ -21954,18 +22032,19 @@ function detectStack(root, files, pkg2, read2) {
21954
22032
  else if (deps2["@playwright/test"]) testRunner = "playwright";
21955
22033
  else if (deps2.mocha) testRunner = "mocha";
21956
22034
  else if (pkg2?.scripts?.test?.includes("node --test")) testRunner = "node:test";
21957
- const envVars = findEnvRefs(files, read2);
22035
+ const envVars = findEnvRefs(files, read3);
21958
22036
  for (const v of envVars) if (v.service) services.add(v.service);
21959
22037
  return {
21960
22038
  packageManager,
21961
22039
  frameworks,
21962
22040
  services: [...services].sort(),
21963
22041
  nextjs,
22042
+ vite,
21964
22043
  supabase: supabase2,
21965
22044
  typescript: has2("tsconfig.json"),
21966
22045
  testRunner,
21967
22046
  envVars,
21968
- recognized: Boolean(nextjs)
22047
+ recognized: Boolean(nextjs || vite)
21969
22048
  };
21970
22049
  }
21971
22050
  function pmCommands(pm) {
@@ -22069,7 +22148,7 @@ function detectKind(input2) {
22069
22148
  confidence,
22070
22149
  evidence: [...why, ...evidence]
22071
22150
  });
22072
- const fw = (list9) => frameworks.find((f) => list9.includes(f));
22151
+ const fw = (list11) => frameworks.find((f) => list11.includes(f));
22073
22152
  if (deps2.expo || deps2["react-native"] || deps2["@capacitor/core"] || files.includes("pubspec.yaml"))
22074
22153
  return done2("mobile-app", "high", fw(["Expo", "React Native", "Capacitor"]) ?? input2.label("pubspec.yaml"));
22075
22154
  if (deps2.electron || deps2["@tauri-apps/api"]) return done2("desktop-app", "high", deps2.electron ? "Electron" : "Tauri");
@@ -22179,9 +22258,9 @@ var LANGUAGE_FILES = [
22179
22258
  function detectLanguage(files) {
22180
22259
  return LANGUAGE_FILES.find(([re]) => files.some((f) => re.test(f)))?.[1] ?? null;
22181
22260
  }
22182
- function scopeFor(root, dir, files, read2, rootFiles) {
22261
+ function scopeFor(root, dir, files, read3, rootFiles) {
22183
22262
  const abs = packageFolder(root, dir);
22184
- const readHere = (f) => read2(inDir(dir, f));
22263
+ const readHere = (f) => read3(inDir(dir, f));
22185
22264
  const pkg2 = files.includes("package.json") ? parseJson(readHere("package.json")) : null;
22186
22265
  const stack = detectStack(abs, files, pkg2, readHere);
22187
22266
  const seen = existsSync6(path8.join(abs, ".vercel", "project.json")) ? [...files, ".vercel/project.json"] : files;
@@ -22209,13 +22288,13 @@ function scopeFor(root, dir, files, read2, rootFiles) {
22209
22288
  read: readHere
22210
22289
  };
22211
22290
  }
22212
- function detectProjects(root, files, read2, overrides = []) {
22213
- const dirs = findProjectDirs({ files, read: read2 });
22291
+ function detectProjects(root, files, read3, overrides = []) {
22292
+ const dirs = findProjectDirs({ files, read: read3 });
22214
22293
  let projects;
22215
- if (dirs.length === 1 && dirs[0] === ".") projects = [scopeFor(root, ".", files, read2, files)];
22294
+ if (dirs.length === 1 && dirs[0] === ".") projects = [scopeFor(root, ".", files, read3, files)];
22216
22295
  else {
22217
- projects = dirs.map((d) => scopeFor(root, d, filesIn(files, d), read2, files));
22218
- const rootScope = scopeFor(root, ".", filesIn(files, ".", dirs), read2, files);
22296
+ projects = dirs.map((d) => scopeFor(root, d, filesIn(files, d), read3, files));
22297
+ const rootScope = scopeFor(root, ".", filesIn(files, ".", dirs), read3, files);
22219
22298
  if (rootScope.profile.kind !== "unknown" || overrides.some((o) => o.path === ".")) projects.unshift(rootScope);
22220
22299
  }
22221
22300
  for (const p2 of projects)
@@ -22244,8 +22323,8 @@ function applyOverride(profile, o) {
22244
22323
  profile.evidence = ["set in .aura/config.json", ...profile.evidence];
22245
22324
  }
22246
22325
  }
22247
- function setKindOverride(overrides = [], path52, kind) {
22248
- const at = normPath(path52);
22326
+ function setKindOverride(overrides = [], path55, kind) {
22327
+ const at = normPath(path55);
22249
22328
  const rest = overrides.filter((o) => normPath(o.path) !== at);
22250
22329
  const existing = overrides.find((o) => normPath(o.path) === at);
22251
22330
  const next = { ...existing, path: at, kind };
@@ -22284,7 +22363,7 @@ function describeProject(p2) {
22284
22363
  // package.json
22285
22364
  var package_default = {
22286
22365
  name: "getaura",
22287
- version: "0.4.0",
22366
+ version: "0.5.0",
22288
22367
  description: "Aura helps you and your coding agent follow best practices while you vibe code.",
22289
22368
  type: "module",
22290
22369
  bin: {
@@ -22404,6 +22483,7 @@ function templateVars(root, scan, pkg2, extra = {}) {
22404
22483
  " ",
22405
22484
  " "
22406
22485
  ),
22486
+ scan.stack.vite && `- Vite + React single-page app${scan.stack.vite.router ? " with React Router" : ""}${scan.stack.vite.lovable ? " (built with Lovable)" : ""}`,
22407
22487
  scan.stack.typescript && "- TypeScript",
22408
22488
  ...scan.stack.services.filter((s) => s !== "GitHub").map((s) => `- ${s}`),
22409
22489
  scan.stack.testRunner && `- Tests: ${scan.stack.testRunner}`,
@@ -22435,7 +22515,8 @@ function templateVars(root, scan, pkg2, extra = {}) {
22435
22515
  aura_version: AURA_VERSION,
22436
22516
  setup_node_cache: pm === "bun" ? "" : pm,
22437
22517
  dependabot_ecosystem: pm === "bun" ? "bun" : "npm",
22438
- src_alias_path: scan.stack.nextjs?.srcDir ? "src" : ".",
22518
+ // Vite apps (Lovable, Bolt, v0) point "@/" at src/.
22519
+ src_alias_path: scan.stack.nextjs?.srcDir || scan.stack.vite ? "src" : ".",
22439
22520
  details: "",
22440
22521
  ...extra
22441
22522
  };
@@ -22532,6 +22613,22 @@ var SECURITY_ACTIONS = [
22532
22613
  resolves: ["security.supabase-code"],
22533
22614
  instructions: "Fix the Supabase problems listed in the findings, one concern per pull request (browser code, public variables, storage buckets, database functions). 1) Admin access in browser code: move every use of the service role or secret key, `createAdminClient()`, `supabaseAdmin`, `auth.admin` or `createClient(url, serviceKey)` out of `'use client'` files, `components/`, `hooks/` and Vite or Create React App `src/` code into server-only modules (a route handler, a server action or a `lib/` module that starts with `import 'server-only'`). Do the sign-in and permission check on the server first, then make the browser call that route or action. The browser keeps using the anon or publishable key, with row-level security protecting the data. 2) Secrets under public names: rename every `NEXT_PUBLIC_`, `VITE_` or `EXPO_PUBLIC_` variable whose name contains SERVICE_ROLE or SECRET to a server-only name (for example `SUPABASE_SERVICE_ROLE_KEY`), use it only in server code, and update `.env.example` (name and comment, no value). Say clearly in the pull request that the key must be replaced in the Supabase dashboard and in the hosting settings because it may already be public (`aura guide rotate-supabase-key`), since Aura can\u2019t do that for the user. 3) Public storage buckets: for buckets that hold private files, make them private in a NEW migration (`update storage.buckets set public = false where id = \u2026`), add storage policies on `storage.objects` scoped to the owner, and serve files through signed URLs (`createSignedUrl`) from server code. Leave buckets that are meant to be public (avatars, logos, marketing images) as they are and list them in the pull request. 4) Database functions: in a NEW migration (never edit an applied one), add `set search_path = ''` to each `security definer` function listed (`alter function public.name(args) set search_path = '';` or a `create or replace` with fully qualified names such as `public.profiles`), and revoke `anon` or `public` execute where signed-out visitors don't need it (`revoke execute on function public.name(args) from anon, public;`). Never touch `.env` files, never deploy, never change database data and never write a key value anywhere. Keep the app working the same way for signed-in users. Run the tests, lint, type check and build before finishing, and explain each change in plain language."
22534
22615
  },
22616
+ {
22617
+ id: "protect-edge-functions",
22618
+ title: "Check who is calling each edge function",
22619
+ category: "security",
22620
+ delivery: "agent-fix",
22621
+ resolves: ["security.edge-functions"],
22622
+ instructions: "For each Supabase edge function in the findings (`supabase/functions/<name>/index.ts`, Deno), add a check on who is calling before it does anything else. If the app calls it for a signed-in user: read the `Authorization` header, create a client with the anon key and that header, call `supabase.auth.getUser()`, and answer 401 with a plain JSON error when there is no user; then check that user is allowed to touch the records involved. Keep the service role client only for the steps that truly need it, after the check. If it is a webhook: verify the sender\u2019s signature with the provider\u2019s official helper (for Stripe `stripe.webhooks.constructEventAsync` with the raw body). If it is a scheduled or internal job: compare the `Authorization` header with a secret read by `Deno.env.get(...)` and answer 401 otherwise. Put a check used by several functions in `supabase/functions/_shared/` once. Don\u2019t change `verify_jwt` in `supabase/config.toml` without saying so, never put a key value in code, and never deploy. Run the type check and any tests. In the pull request, explain in plain words who can call each function now, and tell the user to deploy the functions (`supabase functions deploy`) and to add any new secret with `supabase secrets set NAME=...`."
22623
+ },
22624
+ {
22625
+ id: "move-secrets-to-server",
22626
+ title: "Move server keys out of the browser",
22627
+ category: "security",
22628
+ delivery: "agent-fix",
22629
+ resolves: ["security.browser-secrets"],
22630
+ instructions: "This is a single-page app: everything in `src/` is downloaded by every visitor. For each file in the findings, move the code that needs a server key or a server-only package out of the browser. If the app uses Supabase, put it in a Supabase edge function (`supabase/functions/<name>/index.ts`, Deno) that reads the key with `Deno.env.get(...)`, checks the signed-in user from the `Authorization` header with `supabase.auth.getUser()` before doing anything, validates its input, and answers with plain JSON; call it from the app with `supabase.functions.invoke('<name>', { body })`. Without Supabase, use the hosting provider's server functions (for example a Vercel function in `api/`). Rename each variable without the `VITE_` prefix, remove it from browser code and from `.env.example` (list the new server-side name there with a comment, no value), and use the browser package instead where one exists (for example `@stripe/stripe-js` for Stripe Checkout). Never put a key value in code and never touch `.env` files. Run the type check, lint and build before finishing. In the pull request, tell the user in plain words that each key was public in any deployed version, so they must rotate it in that service's dashboard and add the new key as an edge function secret (`supabase secrets set NAME=...`) or in their hosting settings."
22631
+ },
22535
22632
  {
22536
22633
  id: "add-server-only-guards",
22537
22634
  title: "Guard server code with server-only",
@@ -22698,11 +22795,21 @@ var ACTIONS = [
22698
22795
  delivery: "guide",
22699
22796
  resolves: ["foundations.migrations-in-sync"]
22700
22797
  },
22798
+ {
22799
+ id: "add-error-boundary",
22800
+ title: "Add an error screen and a \u201Cpage not found\u201D page",
22801
+ category: "foundations",
22802
+ delivery: "pr",
22803
+ stacks: ["vite"],
22804
+ resolves: ["foundations.spa-errors"],
22805
+ instructions: 'This is a single-page app (Vite + React). Wrap the whole app in an error boundary (a class component with `getDerivedStateFromError`, or `react-error-boundary` if it is already installed) that shows a friendly message and a \u201CReload the page\u201D button instead of a blank screen, and logs the error to the console only. If the app uses React Router and has no catch-all route, add `<Route path="*" element={<NotFound />} />` as the last route, with a simple \u201Cpage not found\u201D page that links home. Match the app\u2019s existing styling (Tailwind classes and theme colours if it uses them). Run the type check, lint and build before finishing.'
22806
+ },
22701
22807
  {
22702
22808
  id: "add-error-pages",
22703
22809
  title: "Add friendly error and \u201Cpage not found\u201D pages",
22704
22810
  category: "foundations",
22705
22811
  delivery: "pr",
22812
+ stacks: ["nextjs"],
22706
22813
  resolves: ["foundations.error-pages"],
22707
22814
  // Used when the PR falls back to an agent fix (no Next.js app at the repo root) or for an existing error page that isn't a client component.
22708
22815
  instructions: 'Add the missing Next.js App Router error pages listed in the findings, in each app\u2019s `app` (or `src/app`) folder: `error.tsx` (a client component starting with `\'use client\'`, receiving `{ error, reset }`, showing a plain-language "Something went wrong" message and a "Try again" button that calls `reset()`), `global-error.tsx` (the same, but rendering its own `<html>` and `<body>` with inline styles, because the root layout and its stylesheet are not loaded there), and `not-found.tsx` (a "Page not found" message with a link to the home page). Use `.jsx` in apps written in JavaScript. Style them with the classes and theme colours the app already uses (Tailwind classes if the app uses Tailwind), and never hardcode new colours. Never show `error.message`, stack traces or other technical details to visitors; showing `error.digest` as a reference code is fine. If an existing `error` or `global-error` file doesn\u2019t start with `\'use client\'`, add that line at the top and change nothing else in it. Never replace or rewrite other existing files. Don\u2019t add loading states here (that is the `add-loading-states` fix). Run the type check, lint, tests and build, and explain in plain language what visitors will now see.'
@@ -22735,8 +22842,8 @@ var ACTIONS = [
22735
22842
  resolves: ["structure.local-files"]
22736
22843
  },
22737
22844
  { id: "save-aura-setup", buildSafe: true, title: "Save Aura\u2019s setup in git", category: "agent-setup", delivery: "pr", resolves: ["agent.setup-saved"] },
22738
- { id: "remove-dead-files", title: "Remove dead files", category: "structure", delivery: "pr", resolves: ["structure.dead-files"] },
22739
- { id: "move-misplaced-files", title: "Move misplaced files", category: "structure", delivery: "pr", resolves: ["structure.layout"] },
22845
+ { id: "remove-dead-files", title: "Remove dead files", category: "structure", delivery: "pr", stacks: ["nextjs"], resolves: ["structure.dead-files"] },
22846
+ { id: "move-misplaced-files", title: "Move misplaced files", category: "structure", delivery: "pr", stacks: ["nextjs"], resolves: ["structure.layout"] },
22740
22847
  {
22741
22848
  id: "split-oversized-files",
22742
22849
  title: "Split oversized files",
@@ -22840,6 +22947,10 @@ var EFFORT = { pr: "one-click", "agent-fix": "review-agent-fix", guide: "follow-
22840
22947
  function effortLabel(e) {
22841
22948
  return { "one-click": "One click", "review-agent-fix": "Agent fix to review", "follow-guide": "Guide to follow", "answer-questions": "A few questions" }[e];
22842
22949
  }
22950
+ function templateStack(stack) {
22951
+ if (!stack.recognized) return null;
22952
+ return stack.nextjs ? "nextjs" : stack.vite ? "vite" : null;
22953
+ }
22843
22954
  function effectiveDelivery(action2, scan) {
22844
22955
  if (action2.delivery !== "pr") return { delivery: action2.delivery, fellBack: false };
22845
22956
  if (action2.buildSafe) return { delivery: "pr", fellBack: false };
@@ -22847,7 +22958,9 @@ function effectiveDelivery(action2, scan) {
22847
22958
  return { delivery: "pr", fellBack: false };
22848
22959
  }
22849
22960
  const inProject = (scan.checks ?? []).some((c) => c.findings.some((f) => f.actionId === action2.id && f.meta?.project));
22850
- const recognized = scan.stack.recognized && !inProject && (!action2.needsSupabase || scan.stack.supabase !== null);
22961
+ const stack = templateStack(scan.stack);
22962
+ const fits2 = stack !== null && (!action2.stacks || action2.stacks.includes(stack));
22963
+ const recognized = fits2 && !inProject && (!action2.needsSupabase || scan.stack.supabase !== null);
22851
22964
  return recognized ? { delivery: "pr", fellBack: false } : { delivery: "agent-fix", fellBack: true };
22852
22965
  }
22853
22966
  function commandFor(actionId, delivery) {
@@ -22979,8 +23092,8 @@ function nextSteps(scan, opts = {}) {
22979
23092
  }
22980
23093
 
22981
23094
  // src/actions/generators.ts
22982
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
22983
- import path22 from "path";
23095
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
23096
+ import path24 from "path";
22984
23097
 
22985
23098
  // src/checks/agent-setup.ts
22986
23099
  import { existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
@@ -23011,10 +23124,10 @@ function parseJson2(text) {
23011
23124
  return null;
23012
23125
  }
23013
23126
  }
23014
- function permissionList(settings, list9) {
23127
+ function permissionList(settings, list11) {
23015
23128
  const perms = settings?.permissions;
23016
23129
  if (!perms || typeof perms !== "object") return [];
23017
- const value = perms[list9];
23130
+ const value = perms[list11];
23018
23131
  return Array.isArray(value) ? value.filter((v) => typeof v === "string").map((v) => v.trim()) : [];
23019
23132
  }
23020
23133
  function globRegex(glob) {
@@ -23112,25 +23225,25 @@ function dangerousSettings(root, tracked) {
23112
23225
  }
23113
23226
  function describeDanger(d) {
23114
23227
  const agent = AGENT_LABELS[d.agent];
23115
- const list9 = d.settings.map((s) => `\`${s}\``).join(", ");
23228
+ const list11 = d.settings.map((s) => `\`${s}\``).join(", ");
23116
23229
  if (d.kind === "bypass" && d.agent === "codex")
23117
23230
  return {
23118
23231
  title: "Codex runs every command with full access and never asks",
23119
- detail: `${d.file} sets ${list9}, so Codex can change any file on the computer and use the internet without asking first.`
23232
+ detail: `${d.file} sets ${list11}, so Codex can change any file on the computer and use the internet without asking first.`
23120
23233
  };
23121
23234
  if (d.kind === "bypass")
23122
23235
  return {
23123
23236
  title: `${agent} is set to skip every permission prompt`,
23124
- detail: `${d.file} sets ${list9}. Recent versions of ${agent} ignore this in shared project settings, but older ones run every command and edit without asking.`
23237
+ detail: `${d.file} sets ${list11}. Recent versions of ${agent} ignore this in shared project settings, but older ones run every command and edit without asking.`
23125
23238
  };
23126
23239
  if (d.kind === "all-shell")
23127
23240
  return {
23128
23241
  title: `${agent} can run any terminal command without asking`,
23129
- detail: `${d.file} allows ${list9}, which approves every terminal command in advance.`
23242
+ detail: `${d.file} allows ${list11}, which approves every terminal command in advance.`
23130
23243
  };
23131
23244
  return {
23132
23245
  title: `${agent} can run risky commands without asking`,
23133
- detail: `${d.file} approves ${list9} in advance: deleting files, pushing code, downloading from the internet or running as administrator, with no prompt.`
23246
+ detail: `${d.file} approves ${list11} in advance: deleting files, pushing code, downloading from the internet or running as administrator, with no prompt.`
23134
23247
  };
23135
23248
  }
23136
23249
  var KNOWN_AGENTS = new Set(AGENT_IDS);
@@ -23294,7 +23407,7 @@ var localFiles = {
23294
23407
  if (!problems) return { facts: { gitAnswered: false }, items: [] };
23295
23408
  if (problems.length === 0) return { facts: { gitAnswered: true }, items: [] };
23296
23409
  const committed = problems.flatMap((p2) => p2.committed);
23297
- const list9 = problems.map(
23410
+ const list11 = problems.map(
23298
23411
  (p2) => `${p2.what} (${[...p2.committed, ...p2.notIgnored].slice(0, 3).join(", ")}): add \`${p2.rule}\` to .gitignore${p2.committed.length ? " and stop tracking them" : ""}`
23299
23412
  ).join("; ");
23300
23413
  const item = {
@@ -23302,7 +23415,7 @@ var localFiles = {
23302
23415
  facts: { committed: committed.length, kinds: problems.length },
23303
23416
  key: "local-files",
23304
23417
  locations: [{ file: ".gitignore" }],
23305
- vars: { list: list9 }
23418
+ vars: { list: list11 }
23306
23419
  };
23307
23420
  return { facts: { gitAnswered: true }, items: [item] };
23308
23421
  }
@@ -23495,10 +23608,10 @@ function parseImports(text) {
23495
23608
  function stripJsonc(text) {
23496
23609
  return text.replace(/("(?:[^"\\]|\\.)*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (_m, str) => str ?? "").replace(/,(\s*[}\]])/g, "$1");
23497
23610
  }
23498
- function readAliases(read2) {
23611
+ function readAliases(read3) {
23499
23612
  const entries = [];
23500
23613
  for (const file2 of ["tsconfig.json", "jsconfig.json"]) {
23501
- const text = read2(file2);
23614
+ const text = read3(file2);
23502
23615
  if (!text) continue;
23503
23616
  try {
23504
23617
  const json2 = JSON.parse(stripJsonc(text));
@@ -23537,13 +23650,13 @@ function entryPoints(files, pkgScripts = {}) {
23537
23650
  (f) => isSourceFile(f) && (isNextRouteFile(f) || /^(?:src\/)?pages\//.test(f) || /^(?:src\/)?(?:middleware|proxy|instrumentation(?:-client)?)\.[cm]?[jt]s$/.test(f) || /^[^/]+\.config\.[cm]?[jt]s$/.test(f) || /^(?:scripts|supabase\/functions|e2e|\.github)\//.test(f) || isTestFile(f) || /(?:^|\/)(?:setup|vitest\.setup|jest\.setup)\.[cm]?[jt]sx?$/.test(f) || scripts.includes(f))
23538
23651
  );
23539
23652
  }
23540
- function buildGraph(files, read2, aliases) {
23653
+ function buildGraph(files, read3, aliases) {
23541
23654
  const source = files.filter(isSourceFile);
23542
23655
  const fileSet = new Set(files);
23543
23656
  const edges = /* @__PURE__ */ new Map();
23544
23657
  for (const f of source) {
23545
23658
  const deps2 = /* @__PURE__ */ new Set();
23546
- for (const ref of parseImports(read2(f) ?? "")) {
23659
+ for (const ref of parseImports(read3(f) ?? "")) {
23547
23660
  const target = resolveImport(f, ref.specifier, fileSet, aliases);
23548
23661
  if (target) deps2.add(target);
23549
23662
  }
@@ -23551,9 +23664,9 @@ function buildGraph(files, read2, aliases) {
23551
23664
  }
23552
23665
  return { source, edges };
23553
23666
  }
23554
- function unreachableFiles(files, read2, pkgScripts) {
23555
- const aliases = readAliases(read2);
23556
- const { source, edges } = buildGraph(files, read2, aliases);
23667
+ function unreachableFiles(files, read3, pkgScripts) {
23668
+ const aliases = readAliases(read3);
23669
+ const { source, edges } = buildGraph(files, read3, aliases);
23557
23670
  const seen = /* @__PURE__ */ new Set();
23558
23671
  const stack = entryPoints(files, pkgScripts);
23559
23672
  while (stack.length) {
@@ -24109,15 +24222,526 @@ var addErrorPages = ({ root, ctx }) => {
24109
24222
  return { changes, details: [...details, ...notes.length ? ["", "**Not in this PR**", ...notes] : []] };
24110
24223
  };
24111
24224
 
24112
- // src/actions/git-hygiene.ts
24113
- import { readFileSync as readFileSync10 } from "fs";
24225
+ // src/actions/spa-fixes.ts
24226
+ import { existsSync as existsSync14 } from "fs";
24114
24227
  import path20 from "path";
24228
+
24229
+ // src/core/code-text.ts
24230
+ var REGEX_BEFORE = /[(,=:[!&|?{};+\-*%~^]/;
24231
+ var REGEX_KEYWORD = /\b(?:return|typeof|case|in|of|void|yield|await|delete|throw|new)$/;
24232
+ function maskCode(src) {
24233
+ const out = src.split("");
24234
+ const blank = (from, to) => {
24235
+ for (let k = from; k < to && k < out.length; k++) if (out[k] !== "\n") out[k] = " ";
24236
+ };
24237
+ const n = src.length;
24238
+ const stack = [];
24239
+ let depth = 0;
24240
+ let inTemplate = false;
24241
+ let i = 0;
24242
+ while (i < n) {
24243
+ const c = src[i];
24244
+ if (inTemplate) {
24245
+ if (c === "\\") {
24246
+ blank(i, i + 2);
24247
+ i += 2;
24248
+ } else if (c === "`") {
24249
+ inTemplate = false;
24250
+ i++;
24251
+ } else if (c === "$" && src[i + 1] === "{") {
24252
+ stack.push(depth++);
24253
+ inTemplate = false;
24254
+ i += 2;
24255
+ } else {
24256
+ blank(i, i + 1);
24257
+ i++;
24258
+ }
24259
+ continue;
24260
+ }
24261
+ const next = src[i + 1];
24262
+ if (c === "/" && next === "/") {
24263
+ const end = src.indexOf("\n", i);
24264
+ const e = end < 0 ? n : end;
24265
+ blank(i, e);
24266
+ i = e;
24267
+ } else if (c === "/" && next === "*") {
24268
+ const end = src.indexOf("*/", i + 2);
24269
+ const e = end < 0 ? n : end + 2;
24270
+ blank(i, e);
24271
+ i = e;
24272
+ } else if (c === '"' || c === "'") {
24273
+ let j = i + 1;
24274
+ while (j < n && src[j] !== c && src[j] !== "\n") j += src[j] === "\\" ? 2 : 1;
24275
+ blank(i + 1, j);
24276
+ i = j + 1;
24277
+ } else if (c === "`") {
24278
+ inTemplate = true;
24279
+ i++;
24280
+ } else if (c === "/" && regexCanStart(out, i)) {
24281
+ let j = i + 1;
24282
+ let inClass = false;
24283
+ while (j < n && src[j] !== "\n" && (src[j] !== "/" || inClass)) {
24284
+ if (src[j] === "\\") j++;
24285
+ else if (src[j] === "[") inClass = true;
24286
+ else if (src[j] === "]") inClass = false;
24287
+ j++;
24288
+ }
24289
+ if (src[j] === "/") {
24290
+ blank(i + 1, j);
24291
+ i = j + 1;
24292
+ } else i++;
24293
+ } else {
24294
+ if (c === "{") depth++;
24295
+ else if (c === "}") {
24296
+ depth--;
24297
+ if (stack.length && stack[stack.length - 1] === depth) {
24298
+ stack.pop();
24299
+ inTemplate = true;
24300
+ }
24301
+ }
24302
+ i++;
24303
+ }
24304
+ }
24305
+ return out.join("");
24306
+ }
24307
+ function regexCanStart(out, i) {
24308
+ let k = i - 1;
24309
+ while (k >= 0 && /\s/.test(out[k] ?? "")) k--;
24310
+ if (k < 0) return true;
24311
+ const prev = out[k] ?? "";
24312
+ if (REGEX_BEFORE.test(prev)) return true;
24313
+ return REGEX_KEYWORD.test(out.slice(Math.max(0, k - 7), k + 1).join(""));
24314
+ }
24315
+ function closingIndex(masked, open2, limit = 4e3) {
24316
+ let depth = 0;
24317
+ const end = Math.min(masked.length, open2 + limit);
24318
+ for (let i = open2; i < end; i++) {
24319
+ const c = masked[i];
24320
+ if (c === "(" || c === "[" || c === "{") depth++;
24321
+ else if (c === ")" || c === "]" || c === "}") {
24322
+ depth--;
24323
+ if (depth === 0) return i + 1;
24324
+ }
24325
+ }
24326
+ return -1;
24327
+ }
24328
+ function lineFinder(text) {
24329
+ const starts = [0];
24330
+ for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) starts.push(i + 1);
24331
+ return (index) => {
24332
+ let lo = 0;
24333
+ let hi = starts.length - 1;
24334
+ while (lo < hi) {
24335
+ const mid = lo + hi + 1 >> 1;
24336
+ if ((starts[mid] ?? 0) <= index) lo = mid;
24337
+ else hi = mid - 1;
24338
+ }
24339
+ return lo + 1;
24340
+ };
24341
+ }
24342
+
24343
+ // src/checks/security/logs.ts
24344
+ var NOT_APP_CODE = /(?:^|\/)(?:node_modules|\.next|\.nuxt|\.svelte-kit|\.vercel|\.turbo|\.output|dist|build|out|coverage|vendor|public|generated|__generated__|__mocks__|\.aura|\.claude|\.agents|\.cursor|\.codex|\.pi)\/|\.min\.[cm]?js$|\.stories\.[cm]?[jt]sx?$|(?:^|\/)[^/]+\.config\.[cm]?[jt]s$|(?:^|\/)\.[\w-]+rc\.[cm]?js$|(?:^|\/)(?:database|supabase)\.types\.ts$/;
24345
+ function appCodeFiles(ctx) {
24346
+ return ctx.files.filter((f) => isSourceFile(f) && !isTestFile(f) && !NOT_APP_CODE.test(f));
24347
+ }
24348
+ var LOG_CALL = /(?<![\w$])(?:console|logger|log)\s*\??\.\s*(?:log|info|debug|warn|error|trace|fatal)\s*\(/g;
24349
+ var REDACTED_CALL = /(?<![\w$.])[\w$]*(?:redact|mask|sanitiz|scrub|obfuscat)[\w$]*\s*\(/gi;
24350
+ var CHAIN = /(?<![\w$.])[A-Za-z_$][\w$]*(?:\s*\??\.\s*[A-Za-z_$][\w$]*)*/g;
24351
+ var SECRET_ENV = /KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|DATABASE_URL|POSTGRES(?:QL)?_URL|REDIS_URL|MONGO(?:DB)?_URI|CONNECTION_STRING|SALT/;
24352
+ var PUBLIC_ENV = /^(?:NEXT_PUBLIC_|VITE_|PUBLIC_|EXPO_PUBLIC_|REACT_APP_|GATSBY_)/;
24353
+ var REQUEST = /^(?:req|request|ctx|context|event)$/;
24354
+ var PRESENCE_BEFORE = /(?:typeof\s+|!\s*|Boolean\(\s*|\bas\s+|\bkeyof\s+|Object\.keys\(\s*)$/;
24355
+ var CONTENT_METHODS = /* @__PURE__ */ new Set([
24356
+ "slice",
24357
+ "substring",
24358
+ "substr",
24359
+ "toString",
24360
+ "trim",
24361
+ "split",
24362
+ "padStart",
24363
+ "padEnd",
24364
+ "toUpperCase",
24365
+ "toLowerCase",
24366
+ "concat",
24367
+ "entries",
24368
+ "values",
24369
+ "toJSON"
24370
+ ]);
24371
+ var SECRET_NAME2 = /(token|secret|password|passwd|passphrase|(?:api|private|secret|access|service|servicerole|signing|encryption|master|client)key|authorization|authheader|cookies?|jwt|credentials?|bearer)$/;
24372
+ var NOT_A_SECRET = /hash|masked|redacted|sanitiz|prefix|hint|last4|preview/i;
24373
+ var YES_NO_OR_VERB = /^(?:is|has|show|hide|should|needs?|can|toggle|validate|verify|check|create|generate|get|set|use|missing|invalid|no|with|requires?)[A-Z_]/;
24374
+ var SECRET_STRING_KEY = /^(?:authorization|cookie|setcookie|session|sessionid|jwt|credentials?)$|(?:token|secret|password|passwd|apikey)$/;
24375
+ var PERSON = /^(?:(?:[a-z][a-zA-Z0-9]*)?(User|Session|Profile|Customer)|(user|session|profile|customer))s?$/;
24376
+ function secretKind(word) {
24377
+ const w = word.toLowerCase();
24378
+ if (/pass/.test(w)) return "a password";
24379
+ if (/cookie/.test(w)) return "a cookie";
24380
+ if (/token$/.test(w)) return "a token";
24381
+ if (/key$/.test(w)) return "an API key";
24382
+ if (/credential/.test(w)) return "login credentials";
24383
+ if (/secret/.test(w)) return "a secret";
24384
+ if (/auth/.test(w)) return "an authorization header";
24385
+ if (/session/.test(w)) return "a session id";
24386
+ return "a token";
24387
+ }
24388
+ function isSecretName(name) {
24389
+ return !NOT_A_SECRET.test(name) && !YES_NO_OR_VERB.test(name) && SECRET_NAME2.test(name.toLowerCase().replace(/[_$]/g, ""));
24390
+ }
24391
+ function splitTopLevel(s) {
24392
+ const parts = [];
24393
+ let depth = 0;
24394
+ let start = 0;
24395
+ for (let i = 0; i < s.length; i++) {
24396
+ const c = s[i];
24397
+ if (c === "(" || c === "[" || c === "{") depth++;
24398
+ else if (c === ")" || c === "]" || c === "}") depth--;
24399
+ else if (c === "," && depth === 0) {
24400
+ parts.push(s.slice(start, i));
24401
+ start = i + 1;
24402
+ }
24403
+ }
24404
+ parts.push(s.slice(start));
24405
+ return parts.map((p2) => p2.trim()).filter(Boolean);
24406
+ }
24407
+ function wholeValues(expr, depth = 0) {
24408
+ const e = expr.trim().replace(/^(?:await\s+|\.\.\.)/, "").trim();
24409
+ if (depth > 2) return [e];
24410
+ const json2 = e.match(/^JSON\s*\.\s*stringify\s*\(/);
24411
+ if (json2 && closingIndex(e, json2[0].length - 1) === e.length) return wholeValues(splitTopLevel(e.slice(json2[0].length, -1))[0] ?? "", depth + 1);
24412
+ if (e.startsWith("{") && closingIndex(e, 0) === e.length) {
24413
+ return splitTopLevel(e.slice(1, -1)).flatMap((entry) => {
24414
+ const colon = entry.match(/^[\w$\s]+:(?!:)/);
24415
+ return wholeValues(colon ? entry.slice(colon[0].length) : entry, depth + 1);
24416
+ });
24417
+ }
24418
+ return [e];
24419
+ }
24420
+ function analyzeLogArgs(masked, raw) {
24421
+ const hits = [];
24422
+ let m = masked;
24423
+ let r = raw;
24424
+ for (const call of masked.matchAll(REDACTED_CALL)) {
24425
+ const end = closingIndex(masked, call.index + call[0].length - 1);
24426
+ const to = end < 0 ? masked.length : end;
24427
+ const pad = " ".repeat(to - call.index);
24428
+ m = m.slice(0, call.index) + pad + m.slice(to);
24429
+ r = r.slice(0, call.index) + pad + r.slice(to);
24430
+ }
24431
+ for (const chain of m.matchAll(CHAIN)) {
24432
+ const start = chain.index;
24433
+ const end = start + chain[0].length;
24434
+ const before = m.slice(Math.max(0, start - 16), start);
24435
+ const after = m.slice(end).trimStart();
24436
+ const segments2 = chain[0].split(/\s*\??\.\s*/);
24437
+ const last = segments2[segments2.length - 1] ?? "";
24438
+ if (segments2.length === 1 && /[{,]\s*$/.test(before) && /^:(?!:)/.test(after)) continue;
24439
+ if (PRESENCE_BEFORE.test(before) || last === "length" || last === "size") continue;
24440
+ let value = segments2;
24441
+ if (after.startsWith("(")) {
24442
+ if (segments2.length === 1) {
24443
+ if ((last === "headers" || last === "cookies") && !/^\(\s*\)\s*\??\.\s*(?:get|has)\s*\(/.test(after))
24444
+ hits.push({ severity: "high", kind: last === "headers" ? "the request headers" : "the cookies" });
24445
+ continue;
24446
+ }
24447
+ const receiver = segments2[segments2.length - 2] ?? "";
24448
+ if (REQUEST.test(receiver) && /^(?:json|formData|text)$/.test(last)) {
24449
+ hits.push({ severity: "medium", kind: last === "formData" ? "the whole form submission" : "the whole request body" });
24450
+ continue;
24451
+ }
24452
+ if (!CONTENT_METHODS.has(last)) continue;
24453
+ value = segments2.slice(0, -1);
24454
+ }
24455
+ const name = value[value.length - 1] ?? "";
24456
+ const owner = value[value.length - 2] ?? "";
24457
+ if (value[0] === "process" && value[1] === "env") {
24458
+ if (value.length === 2 && !after.startsWith("[")) hits.push({ severity: "high", kind: "every environment variable (process.env)" });
24459
+ else if (value.length === 3 && SECRET_ENV.test(name) && !PUBLIC_ENV.test(name) && !/^\?(?![.?])/.test(after))
24460
+ hits.push({ severity: "high", kind: `the ${name} environment variable` });
24461
+ continue;
24462
+ }
24463
+ if (after.startsWith("[")) continue;
24464
+ if (name === "headers" && REQUEST.test(owner)) hits.push({ severity: "high", kind: "the request headers" });
24465
+ else if (name === "body" && REQUEST.test(owner)) hits.push({ severity: "medium", kind: "the whole request body" });
24466
+ else if (name === "formData" && value.length === 1) hits.push({ severity: "medium", kind: "the whole form submission" });
24467
+ else if (isSecretName(name)) hits.push({ severity: "high", kind: secretKind(name) });
24468
+ }
24469
+ for (const key of r.matchAll(/(?:\.\s*get\s*\(|\[)\s*(['"`])([\w-]{2,40})\1\s*[)\]]/g)) {
24470
+ const word = (key[2] ?? "").toLowerCase().replace(/[-_]/g, "");
24471
+ if (m[key.index] !== r[key.index] || !SECRET_STRING_KEY.test(word)) continue;
24472
+ hits.push({ severity: "high", kind: word === "authorization" ? "the authorization header" : secretKind(word) });
24473
+ }
24474
+ for (const arg of splitTopLevel(m)) {
24475
+ for (const v of wholeValues(arg)) {
24476
+ const person = v.match(/^(?:[\w$]+\s*\??\.\s*)*([\w$]+)$/)?.[1]?.match(PERSON);
24477
+ if (person) hits.push({ severity: "medium", kind: `a whole ${(person[1] ?? person[2] ?? "user").toLowerCase()} object` });
24478
+ }
24479
+ }
24480
+ return hits;
24481
+ }
24482
+ function findLogCalls(text, pattern = LOG_CALL) {
24483
+ const src = text.replace(/\r\n/g, "\n");
24484
+ const masked = maskCode(src);
24485
+ const line = lineFinder(src);
24486
+ const calls = [];
24487
+ for (const m of masked.matchAll(new RegExp(pattern.source, "g"))) {
24488
+ const open2 = m.index + m[0].length - 1;
24489
+ const close = closingIndex(masked, open2);
24490
+ calls.push({
24491
+ index: m.index,
24492
+ line: line(m.index),
24493
+ masked: close < 0 ? null : masked.slice(open2 + 1, close - 1),
24494
+ raw: close < 0 ? null : src.slice(open2 + 1, close - 1)
24495
+ });
24496
+ }
24497
+ return calls;
24498
+ }
24499
+ var KINDS = { high: "secret-logs", medium: "personal-logs" };
24500
+ var logLeaks = {
24501
+ id: "security.log-leaks",
24502
+ category: "security",
24503
+ title: "Logs don\u2019t leak secrets or personal data",
24504
+ obsV: 1,
24505
+ applies: (ctx) => appCodeFiles(ctx).length > 0,
24506
+ observe(ctx) {
24507
+ let logging = 0;
24508
+ const flagged = /* @__PURE__ */ new Set();
24509
+ const items = [];
24510
+ for (const file2 of appCodeFiles(ctx)) {
24511
+ const text = ctx.read(file2);
24512
+ if (!text) continue;
24513
+ const calls = findLogCalls(text);
24514
+ if (calls.length === 0) continue;
24515
+ logging++;
24516
+ const bySeverity = /* @__PURE__ */ new Map();
24517
+ for (const call of calls) {
24518
+ if (call.masked === null || call.raw === null) continue;
24519
+ for (const hit of analyzeLogArgs(call.masked, call.raw)) {
24520
+ const lines = bySeverity.get(hit.severity) ?? /* @__PURE__ */ new Map();
24521
+ bySeverity.set(hit.severity, lines);
24522
+ lines.set(call.line, (lines.get(call.line) ?? /* @__PURE__ */ new Set()).add(hit.kind));
24523
+ }
24524
+ }
24525
+ for (const severity2 of ["high", "medium"]) {
24526
+ const lines = bySeverity.get(severity2);
24527
+ if (!lines) continue;
24528
+ flagged.add(file2);
24529
+ const entries = [...lines.entries()].sort((a, b) => a[0] - b[0]);
24530
+ const shown = entries.slice(0, 20);
24531
+ const kinds = [...new Set(entries.flatMap(([, k]) => [...k]))];
24532
+ items.push({
24533
+ kind: KINDS[severity2],
24534
+ facts: { more: entries.length - shown.length },
24535
+ key: `${file2}|${severity2}`,
24536
+ locations: shown.map(([line]) => ({ file: file2, line })),
24537
+ // What each line logs can name the app's own variables, so it stays on this computer.
24538
+ vars: { file: file2, lines: shown.map(([line, k]) => `Line ${line} logs ${[...k].join(" and ")}`).join("; ") },
24539
+ meta: { kinds, lines: entries.length }
24540
+ });
24541
+ }
24542
+ }
24543
+ return { facts: { logging, flagged: flagged.size }, items };
24544
+ }
24545
+ };
24546
+
24547
+ // src/checks/spa-errors.ts
24548
+ var ERROR_BOUNDARY = /\bcomponentDidCatch\s*\(|\bgetDerivedStateFromError\s*\(|from\s+['"]react-error-boundary['"]|<(?:Sentry\.)?ErrorBoundary\b|\berrorElement\s*[:=]|\bErrorBoundary\s*:|export\s+(?:async\s+)?(?:function|const)\s+ErrorBoundary\b/;
24549
+ var CATCH_ALL = /\bpath\s*[:=]\s*\{?\s*['"`]\*['"`]|\bnotFoundComponent\b|\bdefaultNotFoundComponent\b/;
24550
+ function hasErrorBoundary(ctx) {
24551
+ return appCodeFiles(ctx).some((f) => ERROR_BOUNDARY.test(ctx.read(f) ?? ""));
24552
+ }
24553
+ function hasCatchAll(ctx) {
24554
+ return appCodeFiles(ctx).some((f) => CATCH_ALL.test(ctx.read(f) ?? ""));
24555
+ }
24556
+ function usesRouter(pkg2) {
24557
+ const deps2 = { ...pkg2?.dependencies, ...pkg2?.devDependencies };
24558
+ return Boolean(deps2["react-router-dom"] || deps2["react-router"] || deps2["@tanstack/react-router"]);
24559
+ }
24560
+ var spaErrors = {
24561
+ id: "foundations.spa-errors",
24562
+ scope: "project",
24563
+ category: "foundations",
24564
+ title: "Users see a friendly page when something breaks",
24565
+ obsV: 1,
24566
+ applies: (ctx) => Boolean(ctx.stack.vite) && ctx.project.traits.servesPages,
24567
+ observe(ctx) {
24568
+ const router = usesRouter(ctx.pkg);
24569
+ const items = [];
24570
+ const main = ctx.files.find((f) => /^src\/main\.[jt]sx?$/.test(f));
24571
+ const app2 = ctx.files.find((f) => /^src\/App\.[jt]sx?$/.test(f));
24572
+ if (!hasErrorBoundary(ctx)) items.push({ kind: "no-error-boundary", facts: {}, key: "error-boundary", locations: main ? [{ file: main }] : [] });
24573
+ if (router && !hasCatchAll(ctx)) items.push({ kind: "no-catch-all", facts: {}, key: "catch-all", locations: app2 ? [{ file: app2 }] : [] });
24574
+ return { facts: { router }, items };
24575
+ }
24576
+ };
24577
+
24578
+ // src/actions/spa-fixes.ts
24579
+ function addImport(text, line) {
24580
+ const imports3 = [...text.matchAll(/^import[^\n]*\n/gm)];
24581
+ const last = imports3.at(-1);
24582
+ if (!last || last.index === void 0) return `${line}
24583
+ ${text}`;
24584
+ const at = last.index + last[0].length;
24585
+ return `${text.slice(0, at)}${line}
24586
+ ${text.slice(at)}`;
24587
+ }
24588
+ function addErrorBoundary({ root, ctx }) {
24589
+ if (!ctx.stack.vite || !ctx.stack.typescript)
24590
+ return { skip: "This template is for Vite apps written in TypeScript. Run `aura fix add-error-boundary` instead." };
24591
+ const read3 = (f) => ctx.read(f);
24592
+ const style = pageStyle(ctx, { dir: ".", tailwind: Boolean(allDeps(ctx.pkg).tailwindcss) });
24593
+ const changes = [];
24594
+ const details = [];
24595
+ const notes = [];
24596
+ if (!hasErrorBoundary(ctx)) {
24597
+ const main = ctx.files.find((f) => /^src\/main\.tsx$/.test(f));
24598
+ const text = main ? read3(main) : null;
24599
+ const file2 = "src/components/ErrorBoundary.tsx";
24600
+ if (!main || !text || (text.match(/<App\s*\/>/g) ?? []).length !== 1 || existsSync14(path20.join(root, file2))) {
24601
+ notes.push("- An error boundary: `src/main.tsx` isn\u2019t in the shape Aura edits safely. Run `aura fix add-error-boundary`.");
24602
+ } else {
24603
+ changes.push({ path: file2, content: styled(readTemplate("vite/ErrorBoundary.tsx"), style) });
24604
+ const wrapped = addImport(text, 'import ErrorBoundary from "./components/ErrorBoundary";').replace(
24605
+ /<App\s*\/>/,
24606
+ "<ErrorBoundary>\n <App />\n </ErrorBoundary>"
24607
+ );
24608
+ changes.push({ path: main, content: wrapped });
24609
+ details.push(
24610
+ `- \`${file2}\`: a friendly message and a \u201CReload the page\u201D button when something breaks, instead of a blank page`,
24611
+ `- \`${main}\`: wraps the app in it`
24612
+ );
24613
+ }
24614
+ }
24615
+ if (usesRouter(ctx.pkg) && !hasCatchAll(ctx)) {
24616
+ const app2 = ctx.files.find((f) => /^src\/App\.tsx$/.test(f));
24617
+ const text = app2 ? read3(app2) : null;
24618
+ const routes2 = text?.match(/\n([ \t]*)<\/Routes>/g) ?? [];
24619
+ const page = "src/pages/NotFound.tsx";
24620
+ const pkg2 = allDeps(ctx.pkg)["react-router-dom"] ? "react-router-dom" : "react-router";
24621
+ if (!app2 || !text || routes2.length !== 1 || !/<Route\b/.test(text)) {
24622
+ notes.push("- A \u201Cpage not found\u201D route: `src/App.tsx` isn\u2019t in the shape Aura edits safely. Run `aura fix add-error-boundary`.");
24623
+ } else {
24624
+ const indent = /\n([ \t]*)<Route\b[^\n]*\n(?![\s\S]*<Route\b)/.exec(text)?.[1] ?? " ";
24625
+ let updated = text.replace(/\n([ \t]*)<\/Routes>/, `
24626
+ ${indent}<Route path="*" element={<NotFound />} />
24627
+ $1</Routes>`);
24628
+ if (!/\bNotFound\b/.test(text)) updated = addImport(updated, 'import NotFound from "./pages/NotFound";');
24629
+ if (!existsSync14(path20.join(root, page)) && !/\bNotFound\b/.test(text)) {
24630
+ changes.push({ path: page, content: styled(readTemplate("vite/NotFound.tsx").replace('"react-router-dom"', `"${pkg2}"`), style) });
24631
+ details.push(`- \`${page}\`: a \u201Cpage not found\u201D page with a link home`);
24632
+ }
24633
+ changes.push({ path: app2, content: updated });
24634
+ details.push(`- \`${app2}\`: sends every unknown address to it`);
24635
+ }
24636
+ }
24637
+ if (changes.length === 0)
24638
+ return { skip: notes.length ? notes.join("\n").replace(/^- /gm, "") : "The app already has an error screen and a \u201Cpage not found\u201D page." };
24639
+ return { changes, details: [...details, ...notes.length ? ["", "**Not in this PR**", ...notes] : []], verifyTypes: true };
24640
+ }
24641
+
24642
+ // src/actions/security-headers.ts
24643
+ import { existsSync as existsSync15, readFileSync as readFileSync10 } from "fs";
24644
+ import path21 from "path";
24645
+ function read(root, file2) {
24646
+ try {
24647
+ return readFileSync10(path21.join(root, file2), "utf8");
24648
+ } catch {
24649
+ return null;
24650
+ }
24651
+ }
24652
+ var SECURITY_HEADERS2 = [
24653
+ ["Strict-Transport-Security", "max-age=63072000"],
24654
+ ["X-Content-Type-Options", "nosniff"],
24655
+ ["X-Frame-Options", "DENY"],
24656
+ ["Referrer-Policy", "strict-origin-when-cross-origin"],
24657
+ ["Permissions-Policy", "camera=(), microphone=(), geolocation=()"]
24658
+ ];
24659
+ var HEADER_DETAILS = [
24660
+ "- HSTS, nosniff, frame protection, referrer policy and permissions policy on every page",
24661
+ "- No Content Security Policy yet (it often breaks apps; add it separately)"
24662
+ ];
24663
+ var NEXT_HEADERS = `const securityHeaders = [
24664
+ { key: "Strict-Transport-Security", value: "max-age=63072000" },
24665
+ { key: "X-Content-Type-Options", value: "nosniff" },
24666
+ { key: "X-Frame-Options", value: "DENY" },
24667
+ { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
24668
+ { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
24669
+ ];
24670
+ `;
24671
+ var addSecurityHeaders = (g) => {
24672
+ if (g.ctx.stack.vite) return addSpaSecurityHeaders(g);
24673
+ const { root, ctx } = g;
24674
+ const file2 = ctx.files.find((f) => /^next\.config\.[cm]?[jt]s$/.test(f));
24675
+ if (!file2) return { skip: "No next.config file found." };
24676
+ const text = read(root, file2);
24677
+ if (text === null) return { skip: `Aura couldn\u2019t read ${file2}.` };
24678
+ if (/\bheaders\s*\(/.test(text))
24679
+ return { skip: `${file2} already has a headers() function. Run \`aura fix add-security-headers\` to have Aura\u2019s agent merge them in.` };
24680
+ const m = text.match(/(const\s+nextConfig(?:\s*:\s*NextConfig)?\s*=\s*\{)/);
24681
+ if (!m || m.index === void 0) return { skip: `${file2} isn\u2019t in a shape Aura can edit safely. Run \`aura fix add-security-headers\` instead.` };
24682
+ const insertAt = m.index + m[0].length;
24683
+ const headersFn = `
24684
+ async headers() {
24685
+ return [{ source: "/:path*", headers: securityHeaders }];
24686
+ },`;
24687
+ const withFn = text.slice(0, insertAt) + headersFn + text.slice(insertAt);
24688
+ const content2 = withFn.replace(m[0], `${NEXT_HEADERS}
24689
+ ${m[0]}`);
24690
+ return {
24691
+ verifyTypes: true,
24692
+ changes: [{ path: file2, content: content2 }],
24693
+ details: [
24694
+ "- HSTS, nosniff, frame protection, referrer policy and permissions policy on every page",
24695
+ "- No Content Security Policy yet (it often breaks apps; add it separately)"
24696
+ ]
24697
+ };
24698
+ };
24699
+ function addSpaSecurityHeaders({ root, ctx }) {
24700
+ const deps2 = allDeps(ctx.pkg);
24701
+ const vercel2 = ctx.files.includes("vercel.json") || ctx.stack.services.includes("Vercel");
24702
+ const netlify = ctx.files.includes("netlify.toml") || Boolean(deps2["netlify-cli"]);
24703
+ const cloudflare = ctx.files.some((f) => /^wrangler\.(?:toml|jsonc?)$/.test(f)) || Boolean(deps2.wrangler);
24704
+ if (vercel2) {
24705
+ const existing = existsSync15(path21.join(root, "vercel.json")) ? ctx.read("vercel.json") : null;
24706
+ let config2 = {};
24707
+ try {
24708
+ config2 = existing ? JSON.parse(existing) : {};
24709
+ } catch {
24710
+ return { skip: "vercel.json isn\u2019t valid JSON, so Aura won\u2019t edit it. Run `aura fix add-security-headers`." };
24711
+ }
24712
+ if ("headers" in config2) return { skip: "vercel.json already has headers. Run `aura fix add-security-headers` to have Aura\u2019s agent merge them in." };
24713
+ const headers2 = [{ source: "/(.*)", headers: SECURITY_HEADERS2.map(([key, value]) => ({ key, value })) }];
24714
+ return {
24715
+ changes: [{ path: "vercel.json", content: `${JSON.stringify({ ...config2, headers: headers2 }, null, 2)}
24716
+ ` }],
24717
+ details: ["- `vercel.json`:", ...HEADER_DETAILS]
24718
+ };
24719
+ }
24720
+ if (netlify || cloudflare) {
24721
+ if (existsSync15(path21.join(root, "public", "_headers")))
24722
+ return { skip: "`public/_headers` already exists. Run `aura fix add-security-headers` to have Aura\u2019s agent merge them in." };
24723
+ const content2 = `/*
24724
+ ${SECURITY_HEADERS2.map(([k, v]) => ` ${k}: ${v}`).join("\n")}
24725
+ `;
24726
+ return {
24727
+ changes: [{ path: "public/_headers", content: content2 }],
24728
+ details: [`- \`public/_headers\` (${netlify ? "Netlify" : "Cloudflare Pages"}):`, ...HEADER_DETAILS]
24729
+ };
24730
+ }
24731
+ return {
24732
+ skip: "Aura can\u2019t tell where this app is hosted, and single-page apps get their headers from the host. On Vercel or Netlify, connect the repo there and run this again. Lovable\u2019s own hosting doesn\u2019t let apps set headers yet. See `aura guide security-headers`."
24733
+ };
24734
+ }
24735
+
24736
+ // src/actions/git-hygiene.ts
24737
+ import { readFileSync as readFileSync11 } from "fs";
24738
+ import path22 from "path";
24115
24739
  var ignoreLocalFiles = async ({ root, ctx }) => {
24116
24740
  const problems = await localFileProblems(ctx) ?? [];
24117
24741
  if (problems.length === 0) return { skip: "Tool and machine files are already ignored." };
24118
24742
  let current2 = "";
24119
24743
  try {
24120
- current2 = readFileSync10(path20.join(root, ".gitignore"), "utf8");
24744
+ current2 = readFileSync11(path22.join(root, ".gitignore"), "utf8");
24121
24745
  } catch {
24122
24746
  }
24123
24747
  const lines = new Set(current2.split(/\r?\n/).map((l) => l.trim()));
@@ -24146,17 +24770,17 @@ var saveAuraSetup = async ({ root }) => {
24146
24770
  const files = await unsavedSetup({ root }) ?? [];
24147
24771
  if (files.length === 0) return { skip: "Everything Aura set up is already committed." };
24148
24772
  return {
24149
- changes: files.map((file2) => ({ path: file2, content: readFileSync10(path20.join(root, file2), "utf8") })),
24773
+ changes: files.map((file2) => ({ path: file2, content: readFileSync11(path22.join(root, file2), "utf8") })),
24150
24774
  details: files.map((f) => `- \`${f}\``)
24151
24775
  };
24152
24776
  };
24153
24777
 
24154
24778
  // src/actions/agent-files.ts
24155
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
24156
- import path21 from "path";
24779
+ import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
24780
+ import path23 from "path";
24157
24781
  function current(root, file2) {
24158
- const full = path21.join(root, file2);
24159
- return existsSync14(full) ? readFileSync11(full, "utf8") : null;
24782
+ const full = path23.join(root, file2);
24783
+ return existsSync16(full) ? readFileSync12(full, "utf8") : null;
24160
24784
  }
24161
24785
  function upsertBlock(existing, body) {
24162
24786
  const block = `${AURA_BLOCK_START}
@@ -24199,12 +24823,12 @@ function skillChanges(agents2, names) {
24199
24823
  }
24200
24824
 
24201
24825
  // src/actions/generators.ts
24202
- function read(root, file2) {
24203
- const full = path22.join(root, file2);
24204
- return existsSync15(full) ? readFileSync12(full, "utf8") : null;
24826
+ function read2(root, file2) {
24827
+ const full = path24.join(root, file2);
24828
+ return existsSync17(full) ? readFileSync13(full, "utf8") : null;
24205
24829
  }
24206
24830
  function withScripts(root, scripts, overwrite = false) {
24207
- const text = read(root, "package.json");
24831
+ const text = read2(root, "package.json");
24208
24832
  if (!text) return null;
24209
24833
  const indent = text.match(/^\{\n(\s+)"/)?.[1] ?? " ";
24210
24834
  const pkg2 = JSON.parse(text);
@@ -24223,14 +24847,14 @@ function findingMeta(scan, checkId, key) {
24223
24847
  return scan.checks.find((c) => c.checkId === checkId)?.findings[0]?.meta?.[key];
24224
24848
  }
24225
24849
  var addBrief = ({ root }) => {
24226
- const brief2 = BRIEF_PATHS.find((f) => existsSync15(path22.join(root, f)));
24227
- const briefText = brief2 ? read(root, brief2) : null;
24850
+ const brief2 = BRIEF_PATHS.find((f) => existsSync17(path24.join(root, f)));
24851
+ const briefText = brief2 ? read2(root, brief2) : null;
24228
24852
  if (!brief2 || briefText === null) return { skip: "There is no product brief yet. Run `aura init` to draft and confirm it first." };
24229
24853
  const changes = [
24230
24854
  { path: brief2, content: briefText },
24231
24855
  { path: ".aura/.gitignore", content: AURA_GITIGNORE }
24232
24856
  ];
24233
- const config2 = read(root, ".aura/config.json");
24857
+ const config2 = read2(root, ".aura/config.json");
24234
24858
  if (config2 !== null) changes.push({ path: ".aura/config.json", content: config2 });
24235
24859
  return { changes, details: [`- Adds \`${brief2}\`, \`.aura/config.json\` and \`.aura/.gitignore\` (keeps Aura's local reports out of git)`] };
24236
24860
  };
@@ -24250,14 +24874,14 @@ var installSkills = ({ root, agents: agents2, skills: skills2 }) => {
24250
24874
  };
24251
24875
  var tokenEfficiency = (g) => {
24252
24876
  const changes = [];
24253
- if (!existsSync15(path22.join(g.root, "docs/index.md"))) changes.push({ path: "docs/index.md", content: render("docs/index.md", g.vars) });
24877
+ if (!existsSync17(path24.join(g.root, "docs/index.md"))) changes.push({ path: "docs/index.md", content: render("docs/index.md", g.vars) });
24254
24878
  changes.push(...rulesChanges(g.root, g.agents, g.vars));
24255
24879
  return { changes, details: ["- `docs/index.md`: a map of the repo your agent reads first", "- Lean-context rules in the agent rules file"] };
24256
24880
  };
24257
24881
  var addCi = ({ root, ctx, vars }) => {
24258
24882
  const existing = ctx.files.filter((f) => /^\.github\/workflows\//.test(f));
24259
24883
  const file2 = existing.includes(".github/workflows/ci.yml") ? ".github/workflows/checks.yml" : ".github/workflows/ci.yml";
24260
- if (existsSync15(path22.join(root, file2))) return { skip: `${file2} already exists.` };
24884
+ if (existsSync17(path24.join(root, file2))) return { skip: `${file2} already exists.` };
24261
24885
  return {
24262
24886
  changes: [{ path: file2, content: render("github/workflows/ci.yml", vars) }],
24263
24887
  details: [`- \`${file2}\`: lint, type check, tests and build on every pull request`]
@@ -24281,8 +24905,8 @@ var setupTesting = ({ root, ctx, vars }) => {
24281
24905
  };
24282
24906
  };
24283
24907
  var addEnvExample = ({ root, scan }) => {
24284
- const file2 = [".env.example", ".env.sample", ".env.template"].find((f) => existsSync15(path22.join(root, f))) ?? ".env.example";
24285
- const existing = read(root, file2);
24908
+ const file2 = [".env.example", ".env.sample", ".env.template"].find((f) => existsSync17(path24.join(root, f))) ?? ".env.example";
24909
+ const existing = read2(root, file2);
24286
24910
  const declared = new Set(
24287
24911
  (existing ?? "").split(/\r?\n/).map((l) => l.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1]).filter(Boolean)
24288
24912
  );
@@ -24317,11 +24941,15 @@ var addLinting = ({ root, ctx }) => {
24317
24941
  const changes = [];
24318
24942
  const devDeps = [];
24319
24943
  const details = [];
24320
- if (!hasLint) {
24944
+ const addEslint = !hasLint && ctx.stack.nextjs !== null;
24945
+ if (!hasLint && !addEslint && hasFormat)
24946
+ return { skip: "Aura\u2019s ESLint template is for Next.js apps. Run `aura fix add-linting` to have Aura\u2019s agent set ESLint up for this app." };
24947
+ if (addEslint) {
24321
24948
  changes.push({ path: "eslint.config.mjs", content: readTemplate("configs/eslint.config.mjs") });
24322
24949
  devDeps.push("eslint", "eslint-config-next");
24323
24950
  details.push("- ESLint with the standard Next.js rules (`lint` script)");
24324
- }
24951
+ } else if (!hasLint)
24952
+ details.push("- ESLint isn\u2019t added here: Aura\u2019s template is for Next.js. Run `aura fix add-linting` afterwards to set it up for this app.");
24325
24953
  if (!hasFormat) {
24326
24954
  changes.push(
24327
24955
  { path: ".prettierrc.json", content: readTemplate("configs/prettierrc.json") },
@@ -24331,14 +24959,14 @@ var addLinting = ({ root, ctx }) => {
24331
24959
  details.push("- Prettier (`format` and `format:check` scripts). It doesn\u2019t reformat existing files in this PR.");
24332
24960
  }
24333
24961
  const scripts = withScripts(root, {
24334
- ...!hasLint ? { lint: "eslint ." } : {},
24962
+ ...addEslint ? { lint: "eslint ." } : {},
24335
24963
  ...!hasFormat ? { format: "prettier --write .", "format:check": "prettier --check ." } : {}
24336
24964
  });
24337
24965
  if (scripts) changes.push(scripts);
24338
24966
  return { changes, devDeps, details };
24339
24967
  };
24340
24968
  var addPreCommit = ({ root, ctx, vars }) => {
24341
- if (existsSync15(path22.join(root, ".husky/pre-commit"))) return { skip: "A husky pre-commit hook already exists." };
24969
+ if (existsSync17(path24.join(root, ".husky/pre-commit"))) return { skip: "A husky pre-commit hook already exists." };
24342
24970
  const changes = [{ path: ".husky/pre-commit", content: render("configs/husky-pre-commit", vars) }];
24343
24971
  const scripts = withScripts(root, { prepare: "husky" }, false);
24344
24972
  if (scripts) changes.push(scripts);
@@ -24351,7 +24979,7 @@ var addPreCommit = ({ root, ctx, vars }) => {
24351
24979
  };
24352
24980
  var addReadme = ({ root, ctx, vars }) => {
24353
24981
  const file2 = ctx.files.find((f) => /^readme\.mdx?$/i.test(f)) ?? "README.md";
24354
- const current2 = read(root, file2);
24982
+ const current2 = read2(root, file2);
24355
24983
  const boilerplate = current2 && /create-next-app|bootstrapped with/i.test(current2);
24356
24984
  const words2 = current2?.split(/\s+/).filter(Boolean).length ?? 0;
24357
24985
  if (current2 && !boilerplate && words2 >= 80) return { skip: `${file2} already describes the app.` };
@@ -24368,7 +24996,7 @@ ${current2.trim()}
24368
24996
  };
24369
24997
  };
24370
24998
  var enableDependencyUpdates = ({ root, vars }) => {
24371
- if ([".github/dependabot.yml", ".github/dependabot.yaml", "renovate.json", ".github/renovate.json"].some((f) => existsSync15(path22.join(root, f)))) {
24999
+ if ([".github/dependabot.yml", ".github/dependabot.yaml", "renovate.json", ".github/renovate.json"].some((f) => existsSync17(path24.join(root, f)))) {
24372
25000
  return { skip: "Dependabot or Renovate is already configured." };
24373
25001
  }
24374
25002
  return {
@@ -24396,40 +25024,6 @@ var fixVulnerableDeps = ({ ctx, scan }) => {
24396
25024
  details: [...direct.map((f) => `- Upgrades \`${f}\``), ...pm === "npm" ? ["- Runs `npm audit fix` (no breaking major upgrades)"] : []]
24397
25025
  };
24398
25026
  };
24399
- var SECURITY_HEADERS2 = `const securityHeaders = [
24400
- { key: "Strict-Transport-Security", value: "max-age=63072000" },
24401
- { key: "X-Content-Type-Options", value: "nosniff" },
24402
- { key: "X-Frame-Options", value: "DENY" },
24403
- { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
24404
- { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
24405
- ];
24406
- `;
24407
- var addSecurityHeaders = ({ root, ctx }) => {
24408
- const file2 = ctx.files.find((f) => /^next\.config\.[cm]?[jt]s$/.test(f));
24409
- if (!file2) return { skip: "No next.config file found." };
24410
- const text = read(root, file2);
24411
- if (text === null) return { skip: `Aura couldn\u2019t read ${file2}.` };
24412
- if (/\bheaders\s*\(/.test(text))
24413
- return { skip: `${file2} already has a headers() function. Run \`aura fix add-security-headers\` to have Aura\u2019s agent merge them in.` };
24414
- const m = text.match(/(const\s+nextConfig(?:\s*:\s*NextConfig)?\s*=\s*\{)/);
24415
- if (!m || m.index === void 0) return { skip: `${file2} isn\u2019t in a shape Aura can edit safely. Run \`aura fix add-security-headers\` instead.` };
24416
- const insertAt = m.index + m[0].length;
24417
- const headersFn = `
24418
- async headers() {
24419
- return [{ source: "/:path*", headers: securityHeaders }];
24420
- },`;
24421
- const withFn = text.slice(0, insertAt) + headersFn + text.slice(insertAt);
24422
- const content2 = withFn.replace(m[0], `${SECURITY_HEADERS2}
24423
- ${m[0]}`);
24424
- return {
24425
- verifyTypes: true,
24426
- changes: [{ path: file2, content: content2 }],
24427
- details: [
24428
- "- HSTS, nosniff, frame protection, referrer policy and permissions policy on every page",
24429
- "- No Content Security Policy yet (it often breaks apps; add it separately)"
24430
- ]
24431
- };
24432
- };
24433
25027
  var removeDeadFiles = ({ scan, only }) => {
24434
25028
  const files = (findingMeta(scan, "structure.dead-files", "files") ?? []).filter((f) => !only || only.includes(f));
24435
25029
  if (files.length === 0) return { skip: "No dead files found." };
@@ -24448,7 +25042,7 @@ function planMoves(ctx, moves) {
24448
25042
  const alias = aliasFor(toFile);
24449
25043
  if (alias) return alias[0] + noExt.slice(alias[1].length);
24450
25044
  }
24451
- let rel = path22.posix.relative(path22.posix.dirname(fromFile), noExt);
25045
+ let rel = path24.posix.relative(path24.posix.dirname(fromFile), noExt);
24452
25046
  if (!rel.startsWith(".")) rel = `./${rel}`;
24453
25047
  return rel;
24454
25048
  };
@@ -24482,7 +25076,7 @@ function planMoves(ctx, moves) {
24482
25076
  }
24483
25077
  var moveMisplacedFiles = ({ root, ctx, scan, only }) => {
24484
25078
  const raw = findingMeta(scan, "structure.layout", "moves") ?? [];
24485
- const moves = raw.map((m) => m.split("=>")).filter(([from, to]) => !existsSync15(path22.join(root, to)) && (!only || only.includes(from))).slice(0, MAX_MOVES);
25079
+ const moves = raw.map((m) => m.split("=>")).filter(([from, to]) => !existsSync17(path24.join(root, to)) && (!only || only.includes(from))).slice(0, MAX_MOVES);
24486
25080
  if (moves.length === 0) return { skip: "No files to move (or their targets already exist)." };
24487
25081
  return { changes: planMoves(ctx, moves), details: moves.map(([from, to]) => `- \`${from}\` \u2192 \`${to}\``), verifyTypes: true };
24488
25082
  };
@@ -24490,7 +25084,7 @@ var addAuraWorkflow = ({ root, vars }) => {
24490
25084
  const changes = [];
24491
25085
  for (const name of ["aura.yml", "aura-weekly.yml"]) {
24492
25086
  const content2 = render(`github/workflows/${name}`, vars);
24493
- if (read(root, `.github/workflows/${name}`) !== content2) changes.push({ path: `.github/workflows/${name}`, content: content2 });
25087
+ if (read2(root, `.github/workflows/${name}`) !== content2) changes.push({ path: `.github/workflows/${name}`, content: content2 });
24494
25088
  }
24495
25089
  if (changes.length === 0) return { skip: "The Aura workflows are already installed and up to date." };
24496
25090
  return {
@@ -24522,7 +25116,8 @@ var GENERATORS = {
24522
25116
  "ignore-local-files": ignoreLocalFiles,
24523
25117
  "save-aura-setup": saveAuraSetup,
24524
25118
  "protect-agent-secrets": protectAgentSecrets,
24525
- "add-error-pages": addErrorPages
25119
+ "add-error-pages": addErrorPages,
25120
+ "add-error-boundary": addErrorBoundary
24526
25121
  };
24527
25122
 
24528
25123
  // src/actions/foundation.ts
@@ -24544,7 +25139,7 @@ async function foundationPlan(g) {
24544
25139
  }
24545
25140
 
24546
25141
  // src/actions/large-change.ts
24547
- import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
25142
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
24548
25143
 
24549
25144
  // src/ui.ts
24550
25145
  import * as p from "@clack/prompts";
@@ -24729,9 +25324,9 @@ function writePlanFile(root, plan) {
24729
25324
  }
24730
25325
  function readProgress(root, actionId, now = Date.now()) {
24731
25326
  const file2 = auraPath(root, "plans", `${actionId}.json`);
24732
- if (!existsSync16(file2)) return { started: [] };
25327
+ if (!existsSync18(file2)) return { started: [] };
24733
25328
  try {
24734
- const data = JSON.parse(readFileSync13(file2, "utf8"));
25329
+ const data = JSON.parse(readFileSync14(file2, "utf8"));
24735
25330
  const started = Array.isArray(data.started) ? data.started : [];
24736
25331
  return { started: started.filter((s) => Array.isArray(s.files) && now - Date.parse(s.at) < PROGRESS_TTL_MS) };
24737
25332
  } catch {
@@ -24784,16 +25379,16 @@ function afterChunkMessage(plan, next) {
24784
25379
  }
24785
25380
 
24786
25381
  // src/fix-tracking.ts
24787
- import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
25382
+ import { existsSync as existsSync19, readFileSync as readFileSync15 } from "fs";
24788
25383
 
24789
25384
  // src/api/usage.ts
24790
25385
  import { createHash } from "crypto";
24791
25386
 
24792
25387
  // src/api/client.ts
24793
25388
  var AuraApiError = class extends Error {
24794
- constructor(code, message) {
25389
+ constructor(code2, message) {
24795
25390
  super(message);
24796
- this.code = code;
25391
+ this.code = code2;
24797
25392
  }
24798
25393
  code;
24799
25394
  };
@@ -24829,7 +25424,7 @@ var AuraClient = class _AuraClient {
24829
25424
  get hasKey() {
24830
25425
  return Boolean(this.key);
24831
25426
  }
24832
- async request(method, path52, body, timeoutMs = 6e4) {
25427
+ async request(method, path55, body, timeoutMs = 6e4) {
24833
25428
  if (!this.key) throw new AuraApiError("no_key", "No pilot key");
24834
25429
  let payload;
24835
25430
  if (body !== void 0) {
@@ -24839,7 +25434,7 @@ var AuraClient = class _AuraClient {
24839
25434
  }
24840
25435
  let res;
24841
25436
  try {
24842
- res = await fetch(`${this.baseUrl}/v1/${path52}`, {
25437
+ res = await fetch(`${this.baseUrl}/v1/${path55}`, {
24843
25438
  method,
24844
25439
  headers: { authorization: `Bearer ${this.key}`, "content-type": "application/json", "user-agent": `getaura/${AURA_VERSION}` },
24845
25440
  body: payload,
@@ -24856,8 +25451,8 @@ var AuraClient = class _AuraClient {
24856
25451
  }
24857
25452
  if (!res.ok) {
24858
25453
  const error62 = json2?.error;
24859
- const code = error62?.code ?? (res.status === 401 ? "unauthorized" : res.status === 402 ? "cap_reached" : "model_error");
24860
- throw new AuraApiError(code, error62?.message ?? `HTTP ${res.status}`);
25454
+ const code2 = error62?.code ?? (res.status === 401 ? "unauthorized" : res.status === 402 ? "cap_reached" : "model_error");
25455
+ throw new AuraApiError(code2, error62?.message ?? `HTTP ${res.status}`);
24861
25456
  }
24862
25457
  return json2;
24863
25458
  }
@@ -24923,7 +25518,7 @@ var MAX_AGE_MS = 60 * 24 * 60 * 60 * 1e3;
24923
25518
  var MAX_CHECKS_PER_SCAN = 5;
24924
25519
  function loadTrackedFixes(root) {
24925
25520
  try {
24926
- return existsSync17(auraPath(root, FILE)) ? JSON.parse(readFileSync14(auraPath(root, FILE), "utf8")) : [];
25521
+ return existsSync19(auraPath(root, FILE)) ? JSON.parse(readFileSync15(auraPath(root, FILE), "utf8")) : [];
24927
25522
  } catch {
24928
25523
  return [];
24929
25524
  }
@@ -25000,11 +25595,11 @@ async function recordAction(root, config2, actionId, report, startedAt, via) {
25000
25595
 
25001
25596
  // src/scan.ts
25002
25597
  import { execFileSync } from "child_process";
25003
- import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
25598
+ import { existsSync as existsSync23, readFileSync as readFileSync20 } from "fs";
25004
25599
 
25005
25600
  // src/api/assess.ts
25006
25601
  import { createHash as createHash4 } from "crypto";
25007
- import { readFileSync as readFileSync18 } from "fs";
25602
+ import { readFileSync as readFileSync19 } from "fs";
25008
25603
 
25009
25604
  // src/checks/foundations.ts
25010
25605
  import YAML4 from "yaml";
@@ -25400,12 +25995,12 @@ function i18nInfo(ctx) {
25400
25995
  const briefFile = BRIEF_PATHS.map((p2) => ctx.read(p2)).find((t) => t !== null);
25401
25996
  const brief2 = briefFile ? briefLanguageSignals(briefFile) : { multilingual: false, rtl: false };
25402
25997
  if (brief2.multilingual) reasons.push("the brief says the app supports several languages");
25403
- const list9 = [...locales].sort();
25404
- return { reasons, library, locales: list9, defaultLocale, rtl: brief2.rtl || list9.some(isRtlLocale), messages };
25998
+ const list11 = [...locales].sort();
25999
+ return { reasons, library, locales: list11, defaultLocale, rtl: brief2.rtl || list11.some(isRtlLocale), messages };
25405
26000
  }
25406
26001
 
25407
26002
  // src/checks/ui-files.ts
25408
- import path23 from "path";
26003
+ import path25 from "path";
25409
26004
  var UI_FILE = /\.(?:tsx|jsx|vue|svelte|css|scss|less)$/;
25410
26005
  var STYLE_FILE = /\.(?:css|scss|less)$/;
25411
26006
  var SCRIPT_FILE = /\.[cm]?[jt]sx?$/;
@@ -25418,7 +26013,7 @@ function nameWords(stem) {
25418
26013
  return stem.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
25419
26014
  }
25420
26015
  function exclusionReason(file2, text) {
25421
- const base2 = path23.posix.basename(file2);
26016
+ const base2 = path25.posix.basename(file2);
25422
26017
  const stem = base2.replace(/\.[^.]+$/, "").replace(/\.module$/, "");
25423
26018
  const dirs = file2.split("/").slice(0, -1);
25424
26019
  if (isTestFile(file2) || /\.stories\.[cm]?[jt]sx?$/.test(file2)) return "test";
@@ -25466,7 +26061,7 @@ function zoomBlocks(text) {
25466
26061
  return n;
25467
26062
  }
25468
26063
  var MAX_LISTED = 20;
25469
- var KINDS = ["image-alt", "field-label", "click-keyboard", "control-name", "link-button", "positive-tabindex", "page-lang", "zoom"];
26064
+ var KINDS2 = ["image-alt", "field-label", "click-keyboard", "control-name", "link-button", "positive-tabindex", "page-lang", "zoom"];
25470
26065
  var CERTAIN = /* @__PURE__ */ new Set(["page-lang", "zoom"]);
25471
26066
  function listCounts(items, withCounts) {
25472
26067
  const shown = items.slice(0, MAX_LISTED).map((i) => withCounts ? `${i.file} (${i.count})` : i.file);
@@ -25496,7 +26091,7 @@ var accessibility = {
25496
26091
  }
25497
26092
  if (i18nInfo(ctx).reasons.length === 0) for (const file2 of missingLang(ctx)) add("page-lang", file2, 1);
25498
26093
  const items = [];
25499
- for (const kind of KINDS) {
26094
+ for (const kind of KINDS2) {
25500
26095
  const found = (perKind.get(kind) ?? []).sort((a, b) => b.count - a.count || a.file.localeCompare(b.file));
25501
26096
  if (found.length === 0) continue;
25502
26097
  const total2 = found.reduce((s, i) => s + i.count, 0);
@@ -25520,120 +26115,6 @@ var accessibility = {
25520
26115
  }
25521
26116
  };
25522
26117
 
25523
- // src/core/code-text.ts
25524
- var REGEX_BEFORE = /[(,=:[!&|?{};+\-*%~^]/;
25525
- var REGEX_KEYWORD = /\b(?:return|typeof|case|in|of|void|yield|await|delete|throw|new)$/;
25526
- function maskCode(src) {
25527
- const out = src.split("");
25528
- const blank = (from, to) => {
25529
- for (let k = from; k < to && k < out.length; k++) if (out[k] !== "\n") out[k] = " ";
25530
- };
25531
- const n = src.length;
25532
- const stack = [];
25533
- let depth = 0;
25534
- let inTemplate = false;
25535
- let i = 0;
25536
- while (i < n) {
25537
- const c = src[i];
25538
- if (inTemplate) {
25539
- if (c === "\\") {
25540
- blank(i, i + 2);
25541
- i += 2;
25542
- } else if (c === "`") {
25543
- inTemplate = false;
25544
- i++;
25545
- } else if (c === "$" && src[i + 1] === "{") {
25546
- stack.push(depth++);
25547
- inTemplate = false;
25548
- i += 2;
25549
- } else {
25550
- blank(i, i + 1);
25551
- i++;
25552
- }
25553
- continue;
25554
- }
25555
- const next = src[i + 1];
25556
- if (c === "/" && next === "/") {
25557
- const end = src.indexOf("\n", i);
25558
- const e = end < 0 ? n : end;
25559
- blank(i, e);
25560
- i = e;
25561
- } else if (c === "/" && next === "*") {
25562
- const end = src.indexOf("*/", i + 2);
25563
- const e = end < 0 ? n : end + 2;
25564
- blank(i, e);
25565
- i = e;
25566
- } else if (c === '"' || c === "'") {
25567
- let j = i + 1;
25568
- while (j < n && src[j] !== c && src[j] !== "\n") j += src[j] === "\\" ? 2 : 1;
25569
- blank(i + 1, j);
25570
- i = j + 1;
25571
- } else if (c === "`") {
25572
- inTemplate = true;
25573
- i++;
25574
- } else if (c === "/" && regexCanStart(out, i)) {
25575
- let j = i + 1;
25576
- let inClass = false;
25577
- while (j < n && src[j] !== "\n" && (src[j] !== "/" || inClass)) {
25578
- if (src[j] === "\\") j++;
25579
- else if (src[j] === "[") inClass = true;
25580
- else if (src[j] === "]") inClass = false;
25581
- j++;
25582
- }
25583
- if (src[j] === "/") {
25584
- blank(i + 1, j);
25585
- i = j + 1;
25586
- } else i++;
25587
- } else {
25588
- if (c === "{") depth++;
25589
- else if (c === "}") {
25590
- depth--;
25591
- if (stack.length && stack[stack.length - 1] === depth) {
25592
- stack.pop();
25593
- inTemplate = true;
25594
- }
25595
- }
25596
- i++;
25597
- }
25598
- }
25599
- return out.join("");
25600
- }
25601
- function regexCanStart(out, i) {
25602
- let k = i - 1;
25603
- while (k >= 0 && /\s/.test(out[k] ?? "")) k--;
25604
- if (k < 0) return true;
25605
- const prev = out[k] ?? "";
25606
- if (REGEX_BEFORE.test(prev)) return true;
25607
- return REGEX_KEYWORD.test(out.slice(Math.max(0, k - 7), k + 1).join(""));
25608
- }
25609
- function closingIndex(masked, open2, limit = 4e3) {
25610
- let depth = 0;
25611
- const end = Math.min(masked.length, open2 + limit);
25612
- for (let i = open2; i < end; i++) {
25613
- const c = masked[i];
25614
- if (c === "(" || c === "[" || c === "{") depth++;
25615
- else if (c === ")" || c === "]" || c === "}") {
25616
- depth--;
25617
- if (depth === 0) return i + 1;
25618
- }
25619
- }
25620
- return -1;
25621
- }
25622
- function lineFinder(text) {
25623
- const starts = [0];
25624
- for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) starts.push(i + 1);
25625
- return (index) => {
25626
- let lo = 0;
25627
- let hi = starts.length - 1;
25628
- while (lo < hi) {
25629
- const mid = lo + hi + 1 >> 1;
25630
- if ((starts[mid] ?? 0) <= index) lo = mid;
25631
- else hi = mid - 1;
25632
- }
25633
- return lo + 1;
25634
- };
25635
- }
25636
-
25637
26118
  // src/checks/security/webhook-signals.ts
25638
26119
  var WEBHOOK_TEXT = [
25639
26120
  /stripe-signature/i,
@@ -25684,10 +26165,10 @@ function isWebhookRoute(file2, text) {
25684
26165
  }
25685
26166
  function verifiesInText(text) {
25686
26167
  const raw = text.replace(/\r\n/g, "\n");
25687
- const code = maskCode(raw);
25688
- if (matches(VERIFIED_CODE, code)) return true;
25689
- if (VERIFY_LIBRARY.test(raw) && /\bverify\s*\(/.test(code)) return true;
25690
- return SHARED_SECRET.test(raw) && /[!=]==?|timingSafeEqual/.test(code);
26168
+ const code2 = maskCode(raw);
26169
+ if (matches(VERIFIED_CODE, code2)) return true;
26170
+ if (VERIFY_LIBRARY.test(raw) && /\bverify\s*\(/.test(code2)) return true;
26171
+ return SHARED_SECRET.test(raw) && /[!=]==?|timingSafeEqual/.test(code2);
25691
26172
  }
25692
26173
  function isUnverifiedWebhook(file2, text) {
25693
26174
  return isWebhookRoute(file2, text) && !verifiesInText(text);
@@ -25749,9 +26230,9 @@ function namedImports(text) {
25749
26230
  }
25750
26231
  return out;
25751
26232
  }
25752
- var callsName = (code, name) => new RegExp(String.raw`\b${name.replace(/\$/g, "\\$")}\s*\(`).test(code);
25753
- function importsAuthHelper(text, code) {
25754
- return namedImports(text).some((i) => /auth|session/i.test(i.from) && callsName(code ?? text.slice(i.end), i.local));
26233
+ var callsName = (code2, name) => new RegExp(String.raw`\b${name.replace(/\$/g, "\\$")}\s*\(`).test(code2);
26234
+ function importsAuthHelper(text, code2) {
26235
+ return namedImports(text).some((i) => /auth|session/i.test(i.from) && callsName(code2 ?? text.slice(i.end), i.local));
25755
26236
  }
25756
26237
  function exportedCode(text, name) {
25757
26238
  const start = text.search(new RegExp(String.raw`export\s+(?:async\s+)?(?:function\s*\*?\s*|const\s+|let\s+)${name.replace(/\$/g, "\\$")}\b`));
@@ -25766,8 +26247,8 @@ function wrapperChecksAuth(ctx, file2, text) {
25766
26247
  if (!callsName(text.slice(end), local)) return false;
25767
26248
  const target = resolveImport(file2, from, files, aliases);
25768
26249
  const helper = target ? ctx.read(target) ?? "" : "";
25769
- const code = exportedCode(helper, name);
25770
- return Boolean(code && (AUTH_SIGNALS.some((re) => re.test(code)) || importsAuthHelper(helper, code)));
26250
+ const code2 = exportedCode(helper, name);
26251
+ return Boolean(code2 && (AUTH_SIGNALS.some((re) => re.test(code2)) || importsAuthHelper(helper, code2)));
25771
26252
  });
25772
26253
  }
25773
26254
  function classifyRoute(text) {
@@ -25817,210 +26298,6 @@ var apiRoutes = {
25817
26298
  }
25818
26299
  };
25819
26300
 
25820
- // src/checks/security/logs.ts
25821
- var NOT_APP_CODE = /(?:^|\/)(?:node_modules|\.next|\.nuxt|\.svelte-kit|\.vercel|\.turbo|\.output|dist|build|out|coverage|vendor|public|generated|__generated__|__mocks__|\.aura|\.claude|\.agents|\.cursor|\.codex|\.pi)\/|\.min\.[cm]?js$|\.stories\.[cm]?[jt]sx?$|(?:^|\/)[^/]+\.config\.[cm]?[jt]s$|(?:^|\/)\.[\w-]+rc\.[cm]?js$|(?:^|\/)(?:database|supabase)\.types\.ts$/;
25822
- function appCodeFiles(ctx) {
25823
- return ctx.files.filter((f) => isSourceFile(f) && !isTestFile(f) && !NOT_APP_CODE.test(f));
25824
- }
25825
- var LOG_CALL = /(?<![\w$])(?:console|logger|log)\s*\??\.\s*(?:log|info|debug|warn|error|trace|fatal)\s*\(/g;
25826
- var REDACTED_CALL = /(?<![\w$.])[\w$]*(?:redact|mask|sanitiz|scrub|obfuscat)[\w$]*\s*\(/gi;
25827
- var CHAIN = /(?<![\w$.])[A-Za-z_$][\w$]*(?:\s*\??\.\s*[A-Za-z_$][\w$]*)*/g;
25828
- var SECRET_ENV = /KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|DATABASE_URL|POSTGRES(?:QL)?_URL|REDIS_URL|MONGO(?:DB)?_URI|CONNECTION_STRING|SALT/;
25829
- var PUBLIC_ENV = /^(?:NEXT_PUBLIC_|VITE_|PUBLIC_|EXPO_PUBLIC_|REACT_APP_|GATSBY_)/;
25830
- var REQUEST = /^(?:req|request|ctx|context|event)$/;
25831
- var PRESENCE_BEFORE = /(?:typeof\s+|!\s*|Boolean\(\s*|\bas\s+|\bkeyof\s+|Object\.keys\(\s*)$/;
25832
- var CONTENT_METHODS = /* @__PURE__ */ new Set([
25833
- "slice",
25834
- "substring",
25835
- "substr",
25836
- "toString",
25837
- "trim",
25838
- "split",
25839
- "padStart",
25840
- "padEnd",
25841
- "toUpperCase",
25842
- "toLowerCase",
25843
- "concat",
25844
- "entries",
25845
- "values",
25846
- "toJSON"
25847
- ]);
25848
- var SECRET_NAME2 = /(token|secret|password|passwd|passphrase|(?:api|private|secret|access|service|servicerole|signing|encryption|master|client)key|authorization|authheader|cookies?|jwt|credentials?|bearer)$/;
25849
- var NOT_A_SECRET = /hash|masked|redacted|sanitiz|prefix|hint|last4|preview/i;
25850
- var YES_NO_OR_VERB = /^(?:is|has|show|hide|should|needs?|can|toggle|validate|verify|check|create|generate|get|set|use|missing|invalid|no|with|requires?)[A-Z_]/;
25851
- var SECRET_STRING_KEY = /^(?:authorization|cookie|setcookie|session|sessionid|jwt|credentials?)$|(?:token|secret|password|passwd|apikey)$/;
25852
- var PERSON = /^(?:(?:[a-z][a-zA-Z0-9]*)?(User|Session|Profile|Customer)|(user|session|profile|customer))s?$/;
25853
- function secretKind(word) {
25854
- const w = word.toLowerCase();
25855
- if (/pass/.test(w)) return "a password";
25856
- if (/cookie/.test(w)) return "a cookie";
25857
- if (/token$/.test(w)) return "a token";
25858
- if (/key$/.test(w)) return "an API key";
25859
- if (/credential/.test(w)) return "login credentials";
25860
- if (/secret/.test(w)) return "a secret";
25861
- if (/auth/.test(w)) return "an authorization header";
25862
- if (/session/.test(w)) return "a session id";
25863
- return "a token";
25864
- }
25865
- function isSecretName(name) {
25866
- return !NOT_A_SECRET.test(name) && !YES_NO_OR_VERB.test(name) && SECRET_NAME2.test(name.toLowerCase().replace(/[_$]/g, ""));
25867
- }
25868
- function splitTopLevel(s) {
25869
- const parts = [];
25870
- let depth = 0;
25871
- let start = 0;
25872
- for (let i = 0; i < s.length; i++) {
25873
- const c = s[i];
25874
- if (c === "(" || c === "[" || c === "{") depth++;
25875
- else if (c === ")" || c === "]" || c === "}") depth--;
25876
- else if (c === "," && depth === 0) {
25877
- parts.push(s.slice(start, i));
25878
- start = i + 1;
25879
- }
25880
- }
25881
- parts.push(s.slice(start));
25882
- return parts.map((p2) => p2.trim()).filter(Boolean);
25883
- }
25884
- function wholeValues(expr, depth = 0) {
25885
- const e = expr.trim().replace(/^(?:await\s+|\.\.\.)/, "").trim();
25886
- if (depth > 2) return [e];
25887
- const json2 = e.match(/^JSON\s*\.\s*stringify\s*\(/);
25888
- if (json2 && closingIndex(e, json2[0].length - 1) === e.length) return wholeValues(splitTopLevel(e.slice(json2[0].length, -1))[0] ?? "", depth + 1);
25889
- if (e.startsWith("{") && closingIndex(e, 0) === e.length) {
25890
- return splitTopLevel(e.slice(1, -1)).flatMap((entry) => {
25891
- const colon = entry.match(/^[\w$\s]+:(?!:)/);
25892
- return wholeValues(colon ? entry.slice(colon[0].length) : entry, depth + 1);
25893
- });
25894
- }
25895
- return [e];
25896
- }
25897
- function analyzeLogArgs(masked, raw) {
25898
- const hits = [];
25899
- let m = masked;
25900
- let r = raw;
25901
- for (const call of masked.matchAll(REDACTED_CALL)) {
25902
- const end = closingIndex(masked, call.index + call[0].length - 1);
25903
- const to = end < 0 ? masked.length : end;
25904
- const pad = " ".repeat(to - call.index);
25905
- m = m.slice(0, call.index) + pad + m.slice(to);
25906
- r = r.slice(0, call.index) + pad + r.slice(to);
25907
- }
25908
- for (const chain of m.matchAll(CHAIN)) {
25909
- const start = chain.index;
25910
- const end = start + chain[0].length;
25911
- const before = m.slice(Math.max(0, start - 16), start);
25912
- const after = m.slice(end).trimStart();
25913
- const segments2 = chain[0].split(/\s*\??\.\s*/);
25914
- const last = segments2[segments2.length - 1] ?? "";
25915
- if (segments2.length === 1 && /[{,]\s*$/.test(before) && /^:(?!:)/.test(after)) continue;
25916
- if (PRESENCE_BEFORE.test(before) || last === "length" || last === "size") continue;
25917
- let value = segments2;
25918
- if (after.startsWith("(")) {
25919
- if (segments2.length === 1) {
25920
- if ((last === "headers" || last === "cookies") && !/^\(\s*\)\s*\??\.\s*(?:get|has)\s*\(/.test(after))
25921
- hits.push({ severity: "high", kind: last === "headers" ? "the request headers" : "the cookies" });
25922
- continue;
25923
- }
25924
- const receiver = segments2[segments2.length - 2] ?? "";
25925
- if (REQUEST.test(receiver) && /^(?:json|formData|text)$/.test(last)) {
25926
- hits.push({ severity: "medium", kind: last === "formData" ? "the whole form submission" : "the whole request body" });
25927
- continue;
25928
- }
25929
- if (!CONTENT_METHODS.has(last)) continue;
25930
- value = segments2.slice(0, -1);
25931
- }
25932
- const name = value[value.length - 1] ?? "";
25933
- const owner = value[value.length - 2] ?? "";
25934
- if (value[0] === "process" && value[1] === "env") {
25935
- if (value.length === 2 && !after.startsWith("[")) hits.push({ severity: "high", kind: "every environment variable (process.env)" });
25936
- else if (value.length === 3 && SECRET_ENV.test(name) && !PUBLIC_ENV.test(name) && !/^\?(?![.?])/.test(after))
25937
- hits.push({ severity: "high", kind: `the ${name} environment variable` });
25938
- continue;
25939
- }
25940
- if (after.startsWith("[")) continue;
25941
- if (name === "headers" && REQUEST.test(owner)) hits.push({ severity: "high", kind: "the request headers" });
25942
- else if (name === "body" && REQUEST.test(owner)) hits.push({ severity: "medium", kind: "the whole request body" });
25943
- else if (name === "formData" && value.length === 1) hits.push({ severity: "medium", kind: "the whole form submission" });
25944
- else if (isSecretName(name)) hits.push({ severity: "high", kind: secretKind(name) });
25945
- }
25946
- for (const key of r.matchAll(/(?:\.\s*get\s*\(|\[)\s*(['"`])([\w-]{2,40})\1\s*[)\]]/g)) {
25947
- const word = (key[2] ?? "").toLowerCase().replace(/[-_]/g, "");
25948
- if (m[key.index] !== r[key.index] || !SECRET_STRING_KEY.test(word)) continue;
25949
- hits.push({ severity: "high", kind: word === "authorization" ? "the authorization header" : secretKind(word) });
25950
- }
25951
- for (const arg of splitTopLevel(m)) {
25952
- for (const v of wholeValues(arg)) {
25953
- const person = v.match(/^(?:[\w$]+\s*\??\.\s*)*([\w$]+)$/)?.[1]?.match(PERSON);
25954
- if (person) hits.push({ severity: "medium", kind: `a whole ${(person[1] ?? person[2] ?? "user").toLowerCase()} object` });
25955
- }
25956
- }
25957
- return hits;
25958
- }
25959
- function findLogCalls(text, pattern = LOG_CALL) {
25960
- const src = text.replace(/\r\n/g, "\n");
25961
- const masked = maskCode(src);
25962
- const line = lineFinder(src);
25963
- const calls = [];
25964
- for (const m of masked.matchAll(new RegExp(pattern.source, "g"))) {
25965
- const open2 = m.index + m[0].length - 1;
25966
- const close = closingIndex(masked, open2);
25967
- calls.push({
25968
- index: m.index,
25969
- line: line(m.index),
25970
- masked: close < 0 ? null : masked.slice(open2 + 1, close - 1),
25971
- raw: close < 0 ? null : src.slice(open2 + 1, close - 1)
25972
- });
25973
- }
25974
- return calls;
25975
- }
25976
- var KINDS2 = { high: "secret-logs", medium: "personal-logs" };
25977
- var logLeaks = {
25978
- id: "security.log-leaks",
25979
- category: "security",
25980
- title: "Logs don\u2019t leak secrets or personal data",
25981
- obsV: 1,
25982
- applies: (ctx) => appCodeFiles(ctx).length > 0,
25983
- observe(ctx) {
25984
- let logging = 0;
25985
- const flagged = /* @__PURE__ */ new Set();
25986
- const items = [];
25987
- for (const file2 of appCodeFiles(ctx)) {
25988
- const text = ctx.read(file2);
25989
- if (!text) continue;
25990
- const calls = findLogCalls(text);
25991
- if (calls.length === 0) continue;
25992
- logging++;
25993
- const bySeverity = /* @__PURE__ */ new Map();
25994
- for (const call of calls) {
25995
- if (call.masked === null || call.raw === null) continue;
25996
- for (const hit of analyzeLogArgs(call.masked, call.raw)) {
25997
- const lines = bySeverity.get(hit.severity) ?? /* @__PURE__ */ new Map();
25998
- bySeverity.set(hit.severity, lines);
25999
- lines.set(call.line, (lines.get(call.line) ?? /* @__PURE__ */ new Set()).add(hit.kind));
26000
- }
26001
- }
26002
- for (const severity2 of ["high", "medium"]) {
26003
- const lines = bySeverity.get(severity2);
26004
- if (!lines) continue;
26005
- flagged.add(file2);
26006
- const entries = [...lines.entries()].sort((a, b) => a[0] - b[0]);
26007
- const shown = entries.slice(0, 20);
26008
- const kinds = [...new Set(entries.flatMap(([, k]) => [...k]))];
26009
- items.push({
26010
- kind: KINDS2[severity2],
26011
- facts: { more: entries.length - shown.length },
26012
- key: `${file2}|${severity2}`,
26013
- locations: shown.map(([line]) => ({ file: file2, line })),
26014
- // What each line logs can name the app's own variables, so it stays on this computer.
26015
- vars: { file: file2, lines: shown.map(([line, k]) => `Line ${line} logs ${[...k].join(" and ")}`).join("; ") },
26016
- meta: { kinds, lines: entries.length }
26017
- });
26018
- }
26019
- }
26020
- return { facts: { logging, flagged: flagged.size }, items };
26021
- }
26022
- };
26023
-
26024
26301
  // src/checks/security/ai-calls.ts
26025
26302
  var AI_SDK = /^(?:openai|ai|groq-sdk|replicate|together-ai|cohere-ai|@google\/genai|@google\/generative-ai|@mistralai\/mistralai)$|^@(?:anthropic-ai|ai-sdk|langchain)\//;
26026
26303
  function isAiSdk(name) {
@@ -26514,7 +26791,7 @@ function untestedFiles(ctx, areas2) {
26514
26791
  const dirs = workspaceDirs2(ctx);
26515
26792
  const texts = tests2.map((file2) => ({ file: file2, dir: dirOf(file2, dirs), text: ctx.read(file2) ?? "" }));
26516
26793
  const e2e = texts.filter((t) => isE2eSpec(t.file, t.text)).map((t) => ({ ...t, paths: visitedPaths(t.text) }));
26517
- const near = (list9, dir) => list9.filter((t) => t.dir === dir || t.dir === ".");
26794
+ const near = (list11, dir) => list11.filter((t) => t.dir === dir || t.dir === ".");
26518
26795
  return areas2.filter((a) => {
26519
26796
  if (reached.has(a.file)) return false;
26520
26797
  const route = a.route;
@@ -26591,7 +26868,7 @@ var criticalTests = {
26591
26868
  };
26592
26869
 
26593
26870
  // src/checks/deps-registry.ts
26594
- import { readFileSync as readFileSync15 } from "fs";
26871
+ import { readFileSync as readFileSync16 } from "fs";
26595
26872
  var REGISTRY = "https://registry.npmjs.org";
26596
26873
  var fetchFromRegistry = async (name) => {
26597
26874
  const encoded = name.startsWith("@") ? `@${encodeURIComponent(name.slice(1))}` : encodeURIComponent(name);
@@ -26609,7 +26886,7 @@ var CACHE_MS = 24 * 60 * 60 * 1e3;
26609
26886
  var CONCURRENCY = 8;
26610
26887
  function loadCache(root) {
26611
26888
  try {
26612
- const data = JSON.parse(readFileSync15(auraPath(root, CACHE_FILE), "utf8"));
26889
+ const data = JSON.parse(readFileSync16(auraPath(root, CACHE_FILE), "utf8"));
26613
26890
  return data && typeof data === "object" ? data : {};
26614
26891
  } catch {
26615
26892
  return {};
@@ -26670,7 +26947,7 @@ async function lookupPackages(root, wanted, fetcher, now) {
26670
26947
  }
26671
26948
 
26672
26949
  // src/checks/deps-runtimes.ts
26673
- import path24 from "path";
26950
+ import path26 from "path";
26674
26951
  var NODE_LINES = {
26675
26952
  12: { eol: "2022-04-30", lts: "2019-10-21" },
26676
26953
  13: { eol: "2020-06-01" },
@@ -26778,7 +27055,7 @@ function nodePinSources(ctx, workspaces) {
26778
27055
  if (typeof volta === "string") add(ws.file, "volta.node", volta, nodeMajorOf(volta));
26779
27056
  }
26780
27057
  for (const file2 of ctx.files) {
26781
- const base2 = path24.posix.basename(file2);
27058
+ const base2 = path26.posix.basename(file2);
26782
27059
  if (base2 === ".nvmrc" || base2 === ".node-version") {
26783
27060
  const first = (ctx.read(file2) ?? "").replace(/\r\n/g, "\n").split("\n").map((l) => l.replace(/#.*/, "").trim()).find(Boolean);
26784
27061
  if (first) add(file2, base2, first, nodeMajorOf(first));
@@ -27085,8 +27362,8 @@ function fakeSuccess(file2, v) {
27085
27362
  const strong = STRONG_SUCCESS.flatMap(at);
27086
27363
  const hits = [...at(WEAK_SUCCESS), ...strong];
27087
27364
  if (hits.length === 0) return null;
27088
- const code = contentLines(v).join("\n");
27089
- if (PROVIDER_CALL.test(code) || PAYMENT_FETCH.test(code)) return null;
27365
+ const code2 = contentLines(v).join("\n");
27366
+ if (PROVIDER_CALL.test(code2) || PAYMENT_FETCH.test(code2)) return null;
27090
27367
  const rest = v.masked;
27091
27368
  const delegated = imps.some(
27092
27369
  (i) => !i.typeOnly && (PAYMENT_WORD.test(i.spec) || i.names.some((n) => PAYMENT_WORD.test(n))) && i.names.some((n) => countWord(rest, n) > 1)
@@ -27132,8 +27409,8 @@ function commentedCodeBlocks(v) {
27132
27409
  return commentBlocks(v).filter((b) => {
27133
27410
  const lines = b.lines.filter((l) => l.trim());
27134
27411
  if (lines.length < COMMENTED_BLOCK_LINES || LICENSE.test(lines.join("\n"))) return false;
27135
- const code = lines.filter((l) => CODE_LINE.some((re) => re.test(l))).length;
27136
- return code >= COMMENTED_BLOCK_LINES && code / lines.length >= CODE_SHARE;
27412
+ const code2 = lines.filter((l) => CODE_LINE.some((re) => re.test(l))).length;
27413
+ return code2 >= COMMENTED_BLOCK_LINES && code2 / lines.length >= CODE_SHARE;
27137
27414
  }).length;
27138
27415
  }
27139
27416
  var TS_DIRECTIVE = /^\s*(?:\{\s*)?(?:\/\/|\/\*)\s*@ts-(ignore|expect-error|nocheck)\b/;
@@ -27179,7 +27456,7 @@ var leftovers = {
27179
27456
  observe(ctx) {
27180
27457
  const files = leftoverFiles(ctx.files);
27181
27458
  const views = /* @__PURE__ */ new Map();
27182
- const read2 = (f) => {
27459
+ const read3 = (f) => {
27183
27460
  const cached2 = views.get(f);
27184
27461
  if (cached2) return cached2;
27185
27462
  const v = view(f, ctx.read(f) ?? "");
@@ -27192,7 +27469,7 @@ var leftovers = {
27192
27469
  const mocks = {};
27193
27470
  const commented = {};
27194
27471
  for (const file2 of files) {
27195
- const v = read2(file2);
27472
+ const v = read3(file2);
27196
27473
  const kinds = placeholderKinds(v);
27197
27474
  if (kinds.length) placeholders[file2] = kinds;
27198
27475
  if (v.mdx) continue;
@@ -27205,7 +27482,7 @@ var leftovers = {
27205
27482
  const blocks = commentedCodeBlocks(v);
27206
27483
  if (blocks) commented[file2] = blocks;
27207
27484
  }
27208
- const escapes = typeCounts(ctx, read2);
27485
+ const escapes = typeCounts(ctx, read3);
27209
27486
  const items = [
27210
27487
  ...placeholderItem(placeholders),
27211
27488
  ...stubItem(stubs),
@@ -27279,11 +27556,11 @@ function commentedItem(byFile) {
27279
27556
  }
27280
27557
  ];
27281
27558
  }
27282
- function typeCounts(ctx, read2) {
27559
+ function typeCounts(ctx, read3) {
27283
27560
  const out = { nocheck: [], ignores: 0, anys: 0, ignoreFiles: [], anyFiles: [] };
27284
27561
  const app2 = new Set(leftoverFiles(ctx.files));
27285
27562
  for (const file2 of codeFiles(ctx.files)) {
27286
- const e = typeEscapes(file2, read2(file2));
27563
+ const e = typeEscapes(file2, read3(file2));
27287
27564
  if (e.nocheck && app2.has(file2)) out.nocheck.push(file2);
27288
27565
  out.ignores += e.ignores;
27289
27566
  out.anys += e.anys;
@@ -27320,7 +27597,7 @@ function typeItems(t) {
27320
27597
  }
27321
27598
 
27322
27599
  // src/connectors/snapshot.ts
27323
- import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
27600
+ import { existsSync as existsSync20, readFileSync as readFileSync17 } from "fs";
27324
27601
  var REFUSAL_MS = 30 * 24 * 60 * 60 * 1e3;
27325
27602
  function withRefusals(states, snap, now = Date.now()) {
27326
27603
  return Object.fromEntries(
@@ -27333,7 +27610,7 @@ function withRefusals(states, snap, now = Date.now()) {
27333
27610
  var FILE2 = "cache/connectors.json";
27334
27611
  function loadSnapshots(root) {
27335
27612
  try {
27336
- return existsSync18(auraPath(root, FILE2)) ? JSON.parse(readFileSync16(auraPath(root, FILE2), "utf8")) : {};
27613
+ return existsSync20(auraPath(root, FILE2)) ? JSON.parse(readFileSync17(auraPath(root, FILE2), "utf8")) : {};
27337
27614
  } catch {
27338
27615
  return {};
27339
27616
  }
@@ -27420,7 +27697,7 @@ var migrationsInSync = {
27420
27697
  };
27421
27698
 
27422
27699
  // src/checks/npm-publish-workflows.ts
27423
- import path25 from "path";
27700
+ import path27 from "path";
27424
27701
  import YAML3 from "yaml";
27425
27702
  var PUBLISH_CMD = /(?:^|[\s;&|(])(?:npx\s+)?(npm|pnpm|yarn\s+npm|yarn\s+workspaces\s+foreach(?:\s+-{1,2}[\w-]+)*\s+npm|changeset)\s+((?:-{1,2}[\w-]+(?:[= ](?!publish\b)[^\s-]\S*)?\s+)*)publish(?![\w:-])([^\n;&|]*)/;
27426
27703
  function flagValue(text, flags) {
@@ -27442,14 +27719,14 @@ function parsePublishCommand(script, workingDir = ".") {
27442
27719
  const named = flagValue(`${flags} ${rest}`, tool === "npm" ? ["--workspace", "-w"] : ["--filter", "-F", "--workspace"]).map(
27443
27720
  (t) => t.replace(/^\{|\}$/g, "").replace(/\.\.\.$/, "")
27444
27721
  );
27445
- if (named.length) return { all: false, targets: named.map((t) => t.startsWith(".") ? normDir(path25.posix.join(workingDir, t)) : t), provenance };
27722
+ if (named.length) return { all: false, targets: named.map((t) => t.startsWith(".") ? normDir(path27.posix.join(workingDir, t)) : t), provenance };
27446
27723
  if (tool === "changeset" || /foreach/.test(tool) || /(?:^|\s)(?:-r|--recursive|--workspaces|-ws)(?:\s|$)/.test(` ${flags} ${rest} `)) {
27447
27724
  return { all: true, targets: [], provenance };
27448
27725
  }
27449
27726
  const before = text.slice(0, m.index);
27450
27727
  const cd = [...before.matchAll(/(?:^|[\s;&(])cd\s+["']?([^\s"';&|]+)/g)].pop()?.[1];
27451
27728
  const positional = rest.trim().split(/\s+/).find((a) => a && !a.startsWith("-") && /^[./]|\//.test(a) && !a.endsWith(".tgz"));
27452
- const dir = path25.posix.join(workingDir.replace(/\\/g, "/"), (cd ?? ".").replace(/\\/g, "/"), (positional ?? ".").replace(/\\/g, "/"));
27729
+ const dir = path27.posix.join(workingDir.replace(/\\/g, "/"), (cd ?? ".").replace(/\\/g, "/"), (positional ?? ".").replace(/\\/g, "/"));
27453
27730
  return { all: false, targets: [normDir(dir)], provenance };
27454
27731
  }
27455
27732
  var asObj = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
@@ -27589,10 +27866,10 @@ function npmPackageCheck(pack = npmPack) {
27589
27866
  var npmPackage = npmPackageCheck();
27590
27867
 
27591
27868
  // src/checks/readme-commands.ts
27592
- import path27 from "path";
27869
+ import path29 from "path";
27593
27870
 
27594
27871
  // src/checks/readme-scripts.ts
27595
- import path26 from "path";
27872
+ import path28 from "path";
27596
27873
  var SHELL_LANGS = /* @__PURE__ */ new Set(["", "sh", "bash", "zsh", "shell", "fish", "powershell", "ps1", "pwsh", "cmd", "bat", "batch", "text", "txt", "plaintext"]);
27597
27874
  var PROMPT_LANGS = /* @__PURE__ */ new Set(["console", "shell-session", "shellsession", "sh-session", "terminal"]);
27598
27875
  var PROMPT = /^\s*(?:\$|%|>|PS>|PS [^>]*>)\s+/;
@@ -27646,14 +27923,14 @@ function readmeCommands(markdown) {
27646
27923
  for (const snippet of shellSnippets(markdown)) {
27647
27924
  let cwd = ".";
27648
27925
  for (const line of snippet.lines) {
27649
- const code = line.replace(/(^|\s)#.*$/, "").trim();
27650
- if (!code) continue;
27651
- for (const segment of code.split(/&&|\|\||;|\|/)) {
27926
+ const code2 = line.replace(/(^|\s)#.*$/, "").trim();
27927
+ if (!code2) continue;
27928
+ for (const segment of code2.split(/&&|\|\||;|\|/)) {
27652
27929
  const words2 = segment.trim().split(/\s+/).filter(Boolean).map(unquote2);
27653
27930
  while (words2[0] && (/^[A-Za-z_][A-Za-z0-9_]*=/.test(words2[0]) || words2[0] === "sudo" || words2[0] === "corepack")) words2.shift();
27654
27931
  if (words2[0] === "cd") {
27655
27932
  const to = words2[1];
27656
- cwd = !to || to === "~" || /^[/~$]/.test(to) ? "?" : cwd === "?" ? "?" : path26.posix.normalize(path26.posix.join(cwd, to.replace(/\\/g, "/")));
27933
+ cwd = !to || to === "~" || /^[/~$]/.test(to) ? "?" : cwd === "?" ? "?" : path28.posix.normalize(path28.posix.join(cwd, to.replace(/\\/g, "/")));
27657
27934
  continue;
27658
27935
  }
27659
27936
  if (snippet.inline && words2.length < 2) continue;
@@ -27723,11 +28000,11 @@ var LOCKFILES = [
27723
28000
  ["bun.lock", "bun"],
27724
28001
  ["bun.lockb", "bun"]
27725
28002
  ];
27726
- var isReadme = (file2) => /^readme(?:\.md|\.mdx|\.markdown)?$/i.test(path27.posix.basename(file2));
28003
+ var isReadme = (file2) => /^readme(?:\.md|\.mdx|\.markdown)?$/i.test(path29.posix.basename(file2));
27727
28004
  function readmes(ctx, workspaces) {
27728
28005
  const byDir = new Map(workspaces.map((ws) => [ws.dir, ws]));
27729
28006
  return ctx.files.flatMap((file2) => {
27730
- const ws = isReadme(file2) ? byDir.get(path27.posix.dirname(file2)) : void 0;
28007
+ const ws = isReadme(file2) ? byDir.get(path29.posix.dirname(file2)) : void 0;
27731
28008
  return ws ? [{ file: file2, ws }] : [];
27732
28009
  });
27733
28010
  }
@@ -27742,7 +28019,7 @@ function selectPackages(selector, workspaces) {
27742
28019
  const sel = selector.replace(/^\.\.\.\^?|\^?\.\.\.$/g, "").replace(/^\{(.*)\}$/, "$1");
27743
28020
  if (!sel || sel.startsWith("!") || sel.includes("[")) return null;
27744
28021
  const byFolder = sel.startsWith("./") || sel.startsWith(".\\") || selector.startsWith("{") || sel === ".";
27745
- const target = byFolder ? path27.posix.normalize(sel.replace(/\\/g, "/")).replace(/\/+$/, "") || "." : sel;
28022
+ const target = byFolder ? path29.posix.normalize(sel.replace(/\\/g, "/")).replace(/\/+$/, "") || "." : sel;
27746
28023
  const re = globToRegex(target);
27747
28024
  const matched = workspaces.filter((ws) => byFolder ? re.test(ws.dir) : re.test(ws.pkg.name ?? "") || re.test(ws.dir));
27748
28025
  return matched.length ? matched : null;
@@ -27761,7 +28038,7 @@ function scriptExists(script, scope, home, workspaces) {
27761
28038
  }
27762
28039
  case "dir": {
27763
28040
  if (scope.dir === "?") return null;
27764
- const dir = scope.dir === "/" ? "." : path27.posix.normalize(path27.posix.join(home.dir, scope.dir.replace(/\\/g, "/"))).replace(/\/+$/, "") || ".";
28041
+ const dir = scope.dir === "/" ? "." : path29.posix.normalize(path29.posix.join(home.dir, scope.dir.replace(/\\/g, "/"))).replace(/\/+$/, "") || ".";
27765
28042
  const ws = workspaces.find((w) => w.dir === dir);
27766
28043
  return ws ? hasScript(ws, script) : null;
27767
28044
  }
@@ -27968,7 +28245,7 @@ var vercelEnv = {
27968
28245
 
27969
28246
  // src/checks/performance.ts
27970
28247
  import { statSync as statSync3 } from "fs";
27971
- import path28 from "path";
28248
+ import path30 from "path";
27972
28249
  var BIG_ASSET_BYTES = 500 * 1024;
27973
28250
  var MEDIA = /\.(?:png|jpe?g|gif|webp|avif|bmp|tiff?|svg|mp4|webm|mov|woff2?|ttf|otf)$/i;
27974
28251
  var MAX_LISTED6 = 10;
@@ -27983,7 +28260,7 @@ function bigAssets(ctx, limit = BIG_ASSET_BYTES) {
27983
28260
  const out = [];
27984
28261
  for (const file2 of ctx.files.filter((f) => f.startsWith("public/") && MEDIA.test(f))) {
27985
28262
  try {
27986
- const size = statSync3(path28.join(ctx.root, ...file2.split("/"))).size;
28263
+ const size = statSync3(path30.join(ctx.root, ...file2.split("/"))).size;
27987
28264
  if (size > limit) out.push({ file: file2, kb: Math.round(size / 1024) });
27988
28265
  } catch {
27989
28266
  }
@@ -28201,10 +28478,10 @@ var typecheck = {
28201
28478
  applies: (ctx) => ctx.stack.typescript,
28202
28479
  async observe(ctx) {
28203
28480
  if (ctx.options.fast) return { facts: { outcome: "skipped-fast-mode" }, items: [] };
28204
- const { existsSync: existsSync31 } = await import("fs");
28205
- const path52 = await import("path");
28206
- const tsc = path52.join(ctx.root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
28207
- if (!existsSync31(tsc)) return { facts: { outcome: "not-installed" }, items: [] };
28481
+ const { existsSync: existsSync33 } = await import("fs");
28482
+ const path55 = await import("path");
28483
+ const tsc = path55.join(ctx.root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
28484
+ if (!existsSync33(tsc)) return { facts: { outcome: "not-installed" }, items: [] };
28208
28485
  const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: ctx.root, timeoutMs: 18e4 });
28209
28486
  if (r.timedOut) return { facts: { outcome: "timed-out" }, items: [] };
28210
28487
  if (r.code === 0) return { facts: { outcome: "ran" }, items: [], engine: "tsc" };
@@ -28233,6 +28510,7 @@ var foundationChecks = [
28233
28510
  vercelEnv,
28234
28511
  migrationsInSync,
28235
28512
  errorPages,
28513
+ spaErrors,
28236
28514
  readmeCommandsCheck,
28237
28515
  seo,
28238
28516
  performance
@@ -28255,9 +28533,9 @@ function toolSettingsCheck(connector) {
28255
28533
  observe(ctx) {
28256
28534
  const snap = loadSnapshots(ctx.root)[connector.id];
28257
28535
  if (!snap) return { facts: { settings: {} }, items: [{ kind: "no-reading", key: "no-reading", locations: [] }] };
28258
- const read2 = connector.settings.filter((s) => STATES.has(snap.settings[s.id] ?? ""));
28259
- const settings = Object.fromEntries(read2.map((s) => [s.id, snap.settings[s.id]]));
28260
- const off = read2.filter((s) => snap.settings[s.id] === "off");
28536
+ const read3 = connector.settings.filter((s) => STATES.has(snap.settings[s.id] ?? ""));
28537
+ const settings = Object.fromEntries(read3.map((s) => [s.id, snap.settings[s.id]]));
28538
+ const off = read3.filter((s) => snap.settings[s.id] === "off");
28261
28539
  return {
28262
28540
  facts: { settings },
28263
28541
  items: off.length ? [{ kind: "settings-off", key: `${connector.id}-settings`, locations: [], vars: { off: off.map((s) => s.label).join("; ") } }] : [],
@@ -28433,9 +28711,9 @@ var databaseBackups = {
28433
28711
 
28434
28712
  // src/core/tools.ts
28435
28713
  import { createHash as createHash2 } from "crypto";
28436
- import { chmodSync as chmodSync2, existsSync as existsSync19, mkdirSync as mkdirSync3, mkdtempSync, readdirSync as readdirSync5, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
28714
+ import { chmodSync as chmodSync2, existsSync as existsSync21, mkdirSync as mkdirSync3, mkdtempSync, readdirSync as readdirSync5, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
28437
28715
  import os3 from "os";
28438
- import path29 from "path";
28716
+ import path31 from "path";
28439
28717
  var GITLEAKS_SHA = {
28440
28718
  darwin_arm64: "b40ab0ae55c505963e365f271a8d3846efbc170aa17f2607f13df610a9aeb6a5",
28441
28719
  darwin_x64: "dfe101a4db2255fc85120ac7f3d25e4342c3c20cf749f2c20a18081af1952709",
@@ -28453,7 +28731,7 @@ var OSV_SHA = {
28453
28731
  windows_arm64: "ca1379da0e408279ef2c85cde0846a903495738a91c7b065a208048437c1686c"
28454
28732
  };
28455
28733
  function tarCommand(platform = process.platform, systemRoot = process.env.SystemRoot) {
28456
- return platform === "win32" ? path29.win32.join(systemRoot || "C:\\Windows", "System32", "tar.exe") : "tar";
28734
+ return platform === "win32" ? path31.win32.join(systemRoot || "C:\\Windows", "System32", "tar.exe") : "tar";
28457
28735
  }
28458
28736
  var TOOLS = {
28459
28737
  gitleaks: {
@@ -28493,8 +28771,8 @@ function ensureTool(name, noDownload = false) {
28493
28771
  async function resolveTool(name, noDownload) {
28494
28772
  const spec = TOOLS[name];
28495
28773
  const exe = process.platform === "win32" ? `${name}.exe` : name;
28496
- const cached2 = path29.join(auraBinDir(), `${name}-${spec.version}`, exe);
28497
- if (existsSync19(cached2)) return { path: cached2, version: spec.version };
28774
+ const cached2 = path31.join(auraBinDir(), `${name}-${spec.version}`, exe);
28775
+ if (existsSync21(cached2)) return { path: cached2, version: spec.version };
28498
28776
  if (await commandExists(name)) {
28499
28777
  const v = await run(name, ["version"]);
28500
28778
  return { path: name, version: (v.stdout.match(/\d+\.\d+\.\d+/)?.[0] ?? "system").trim() };
@@ -28514,20 +28792,20 @@ async function download(name, spec, target) {
28514
28792
  const buf = Buffer.from(await res.arrayBuffer());
28515
28793
  const digest = createHash2("sha256").update(buf).digest("hex");
28516
28794
  if (digest !== asset.sha256) throw new Error(`Checksum mismatch for ${asset.file}`);
28517
- const dir = path29.dirname(target);
28795
+ const dir = path31.dirname(target);
28518
28796
  mkdirSync3(dir, { recursive: true });
28519
- const tmp = mkdtempSync(path29.join(os3.tmpdir(), `aura-${name}-`));
28797
+ const tmp = mkdtempSync(path31.join(os3.tmpdir(), `aura-${name}-`));
28520
28798
  try {
28521
28799
  if (asset.archive === null) {
28522
28800
  writeFileSync3(target, buf);
28523
28801
  } else {
28524
- const archivePath = path29.join(tmp, asset.file);
28802
+ const archivePath = path31.join(tmp, asset.file);
28525
28803
  writeFileSync3(archivePath, buf);
28526
28804
  const r = await run(tarCommand(), ["-xf", archivePath, "-C", tmp]);
28527
28805
  if (r.code !== 0) return null;
28528
28806
  const exe = readdirSync5(tmp).find((f) => f === name || f === `${name}.exe`);
28529
28807
  if (!exe) return null;
28530
- renameSync(path29.join(tmp, exe), target);
28808
+ renameSync(path31.join(tmp, exe), target);
28531
28809
  }
28532
28810
  chmodSync2(target, 493);
28533
28811
  return { path: target, version: spec.version };
@@ -28646,6 +28924,7 @@ var WANTED = [
28646
28924
  { id: "x-frame-options", pattern: /X-Frame-Options|frame-ancestors/i, name: "X-Frame-Options" },
28647
28925
  { id: "referrer-policy", pattern: /Referrer-Policy/i, name: "Referrer-Policy" }
28648
28926
  ];
28927
+ var HEADER_SOURCES = /^next\.config\.[cm]?[jt]s$|^(?:src\/)?(?:middleware|proxy)\.[cm]?[jt]s$|^vercel\.json$|^netlify\.toml$|^(?:public\/)?_headers$|^firebase\.json$|^staticwebapp\.config\.json$/;
28649
28928
  var headers = {
28650
28929
  id: "security.headers",
28651
28930
  scope: "project",
@@ -28653,14 +28932,14 @@ var headers = {
28653
28932
  title: "Security headers set",
28654
28933
  obsV: 1,
28655
28934
  // Headers protect pages in a browser; an API with no pages has nothing for them to guard.
28656
- applies: (ctx) => ctx.stack.nextjs !== null && ctx.project.traits.servesPages,
28935
+ applies: (ctx) => (ctx.stack.nextjs !== null || Boolean(ctx.stack.vite)) && ctx.project.traits.servesPages,
28657
28936
  observe(ctx) {
28658
- const sources = ctx.files.filter((f) => /^next\.config\.[cm]?[jt]s$|^vercel\.json$|^(?:src\/)?(?:middleware|proxy)\.[cm]?[jt]s$/.test(f));
28937
+ const sources = ctx.files.filter((f) => HEADER_SOURCES.test(f));
28659
28938
  const text = sources.map((f) => ctx.read(f) ?? "").join("\n");
28660
28939
  const missing = WANTED.filter((h) => !h.pattern.test(text));
28661
28940
  const facts = { checked: WANTED.length };
28662
28941
  if (missing.length === 0) return { facts, items: [] };
28663
- const configFile = ctx.files.find((f) => /^next\.config\./.test(f));
28942
+ const configFile = ctx.files.find((f) => /^next\.config\./.test(f)) ?? ctx.files.find((f) => /^(?:vercel\.json|netlify\.toml)$/.test(f));
28664
28943
  return {
28665
28944
  facts,
28666
28945
  items: [
@@ -28785,7 +29064,7 @@ var rls = {
28785
29064
  };
28786
29065
 
28787
29066
  // src/checks/security/server-only.ts
28788
- import path30 from "path";
29067
+ import path32 from "path";
28789
29068
 
28790
29069
  // src/checks/security/browser-code.ts
28791
29070
  var posix = (p2) => p2.replace(/\\/g, "/");
@@ -28830,12 +29109,12 @@ function splitSql(sql) {
28830
29109
  const src = sql.replace(/\r\n/g, "\n");
28831
29110
  const out = [];
28832
29111
  let text = "";
28833
- let code = "";
29112
+ let code2 = "";
28834
29113
  let bodies = [];
28835
29114
  const push = () => {
28836
- if (text.trim()) out.push({ text: text.trim(), code: code.trim(), bodies });
29115
+ if (text.trim()) out.push({ text: text.trim(), code: code2.trim(), bodies });
28837
29116
  text = "";
28838
- code = "";
29117
+ code2 = "";
28839
29118
  bodies = [];
28840
29119
  };
28841
29120
  const n = src.length;
@@ -28847,12 +29126,12 @@ function splitSql(sql) {
28847
29126
  const end = src.indexOf("\n", i);
28848
29127
  i = end < 0 ? n : end;
28849
29128
  text += " ";
28850
- code += " ";
29129
+ code2 += " ";
28851
29130
  } else if (c === "/" && next === "*") {
28852
29131
  const end = src.indexOf("*/", i + 2);
28853
29132
  i = end < 0 ? n : end + 2;
28854
29133
  text += " ";
28855
- code += " ";
29134
+ code2 += " ";
28856
29135
  } else if (c === "'" || c === '"') {
28857
29136
  let j = i + 1;
28858
29137
  while (j < n) {
@@ -28862,7 +29141,7 @@ function splitSql(sql) {
28862
29141
  }
28863
29142
  const seg = src.slice(i, j + 1);
28864
29143
  text += seg;
28865
- code += seg;
29144
+ code2 += seg;
28866
29145
  i = j + 1;
28867
29146
  } else if (c === "$" && /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/.test(src.slice(i, i + 64))) {
28868
29147
  const tag = /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/.exec(src.slice(i, i + 64))?.[0] ?? "$$";
@@ -28871,14 +29150,14 @@ function splitSql(sql) {
28871
29150
  const body = src.slice(i + tag.length, end);
28872
29151
  bodies.push(body);
28873
29152
  text += tag + body + tag;
28874
- code += " $$ $$ ";
29153
+ code2 += " $$ $$ ";
28875
29154
  i = close < 0 ? n : end + tag.length;
28876
29155
  } else if (c === ";") {
28877
29156
  push();
28878
29157
  i++;
28879
29158
  } else {
28880
29159
  text += c;
28881
- code += c;
29160
+ code2 += c;
28882
29161
  i++;
28883
29162
  }
28884
29163
  }
@@ -28993,17 +29272,17 @@ function analyzeFunctions(migrations) {
28993
29272
  const reDrop = /^drop\s+function\s+(?:if\s+exists\s+)?([\s\S]*?)(?:\s+(?:cascade|restrict))?$/i;
28994
29273
  const reGrant = /^grant\s+([\s\S]+?)\s+on\s+(?:(?:function|routine)\s+([\s\S]+?)|all\s+(?:functions|routines)\s+in\s+schema\s+([\s\S]+?))\s+to\s+([\s\S]+?)(?:\s+with\s+grant\s+option)?$/i;
28995
29274
  const reRevoke = /^revoke\s+(?:grant\s+option\s+for\s+)?([\s\S]+?)\s+on\s+(?:(?:function|routine)\s+([\s\S]+?)|all\s+(?:functions|routines)\s+in\s+schema\s+([\s\S]+?))\s+from\s+([\s\S]+?)(?:\s+(?:cascade|restrict))?$/i;
28996
- const targets2 = (list9, schemas2) => {
29275
+ const targets2 = (list11, schemas2) => {
28997
29276
  if (schemas2) {
28998
29277
  const wanted = new Set(splitTopLevel2(schemas2).map((s) => unquote3(s).toLowerCase()));
28999
29278
  return [...fns.values()].filter((f) => wanted.has(f.schema));
29000
29279
  }
29001
- return splitTopLevel2(list9 ?? "").flatMap((raw) => fns.get(keyOf(raw).key) ?? []);
29280
+ return splitTopLevel2(list11 ?? "").flatMap((raw) => fns.get(keyOf(raw).key) ?? []);
29002
29281
  };
29003
29282
  for (const { file: file2, sql } of [...migrations].sort((a, b) => a.file.localeCompare(b.file))) {
29004
29283
  for (const stmt of splitSql(sql)) {
29005
- const { code } = stmt;
29006
- const create = reCreate.exec(code);
29284
+ const { code: code2 } = stmt;
29285
+ const create = reCreate.exec(code2);
29007
29286
  if (create) {
29008
29287
  const { schema, name, key } = keyOf(create[2] ?? "");
29009
29288
  const previous = create[1] ? fns.get(key) : void 0;
@@ -29011,15 +29290,15 @@ function analyzeFunctions(migrations) {
29011
29290
  schema,
29012
29291
  name,
29013
29292
  file: file2,
29014
- definer: /\bsecurity\s+definer\b/i.test(code),
29015
- searchPath: SEARCH_PATH.test(code),
29293
+ definer: /\bsecurity\s+definer\b/i.test(code2),
29294
+ searchPath: SEARCH_PATH.test(code2),
29016
29295
  writes: WRITES.test(bodyOf(stmt).replace(/--[^\n]*/g, "")),
29017
29296
  // CREATE OR REPLACE keeps the grants; a fresh CREATE starts without any.
29018
29297
  grants: previous ? previous.grants : /* @__PURE__ */ new Set()
29019
29298
  });
29020
29299
  continue;
29021
29300
  }
29022
- const alter = reAlter.exec(code);
29301
+ const alter = reAlter.exec(code2);
29023
29302
  if (alter) {
29024
29303
  const { key, schema } = keyOf(alter[1] ?? "");
29025
29304
  const fn = fns.get(key);
@@ -29040,17 +29319,17 @@ function analyzeFunctions(migrations) {
29040
29319
  }
29041
29320
  continue;
29042
29321
  }
29043
- const drop = reDrop.exec(code);
29322
+ const drop = reDrop.exec(code2);
29044
29323
  if (drop) {
29045
29324
  for (const raw of splitTopLevel2(drop[1] ?? "")) fns.delete(keyOf(raw).key);
29046
29325
  continue;
29047
29326
  }
29048
- const grant = reGrant.exec(code);
29327
+ const grant = reGrant.exec(code2);
29049
29328
  if (grant && /\b(?:execute|all)\b/i.test(grant[1] ?? "")) {
29050
29329
  for (const fn of targets2(grant[2], grant[3])) for (const r of roles(grant[4] ?? "")) fn.grants.add(r);
29051
29330
  continue;
29052
29331
  }
29053
- const revoke = reRevoke.exec(code);
29332
+ const revoke = reRevoke.exec(code2);
29054
29333
  if (revoke && /\b(?:execute|all)\b/i.test(revoke[1] ?? "")) {
29055
29334
  for (const fn of targets2(revoke[2], revoke[3])) for (const r of roles(revoke[4] ?? "")) fn.grants.delete(r);
29056
29335
  }
@@ -29081,20 +29360,20 @@ function adminSignal(raw) {
29081
29360
  const name = m[1] ?? m[2] ?? "";
29082
29361
  if (SERVICE_ENV.test(name) && masked[m.index] !== " ") return name;
29083
29362
  }
29084
- const code = withoutImports(masked);
29085
- const ident = /\b([A-Z0-9_]*(?:SERVICE_ROLE|SUPABASE_SECRET)[A-Z0-9_]*)\b/.exec(code);
29363
+ const code2 = withoutImports(masked);
29364
+ const ident = /\b([A-Z0-9_]*(?:SERVICE_ROLE|SUPABASE_SECRET)[A-Z0-9_]*)\b/.exec(code2);
29086
29365
  if (ident?.[1]) return ident[1];
29087
29366
  for (const m of text.matchAll(/service_role/g)) {
29088
- if (code[m.index] !== " " || masked[m.index] === " " && inStringLiteral(text, m.index)) return "service_role";
29367
+ if (code2[m.index] !== " " || masked[m.index] === " " && inStringLiteral(text, m.index)) return "service_role";
29089
29368
  }
29090
- const named = ADMIN_NAMES.exec(code)?.[1];
29369
+ const named = ADMIN_NAMES.exec(code2)?.[1];
29091
29370
  if (named) return named;
29092
- if (/\.auth\s*\.\s*admin\s*\./.test(code)) return "auth.admin";
29093
- for (const m of code.matchAll(/(?<![\w$.])createClient\s*\(/g)) {
29371
+ if (/\.auth\s*\.\s*admin\s*\./.test(code2)) return "auth.admin";
29372
+ for (const m of code2.matchAll(/(?<![\w$.])createClient\s*\(/g)) {
29094
29373
  const open2 = m.index + m[0].length - 1;
29095
- const close = closingIndex(code, open2);
29374
+ const close = closingIndex(code2, open2);
29096
29375
  if (close < 0) continue;
29097
- const second = splitTopLevel2(code.slice(open2 + 1, close - 1))[1] ?? "";
29376
+ const second = splitTopLevel2(code2.slice(open2 + 1, close - 1))[1] ?? "";
29098
29377
  if (/service|secret|admin/i.test(second)) return "createClient";
29099
29378
  }
29100
29379
  return null;
@@ -29165,7 +29444,7 @@ function serverReasons(raw) {
29165
29444
  return [...reasons].sort();
29166
29445
  }
29167
29446
  function nestedPackages(files) {
29168
- return files.filter((f) => f.endsWith("/package.json")).map((f) => `${path30.posix.dirname(f)}/`);
29447
+ return files.filter((f) => f.endsWith("/package.json")).map((f) => `${path32.posix.dirname(f)}/`);
29169
29448
  }
29170
29449
  function clientReachable(ctx, files) {
29171
29450
  const importsOf = localImports(ctx);
@@ -29231,10 +29510,242 @@ var serverOnly = {
29231
29510
  }
29232
29511
  };
29233
29512
 
29234
- // src/checks/security/supabase-code.ts
29513
+ // src/checks/security/browser-secrets.ts
29235
29514
  var MAX_LIST4 = 20;
29515
+ var ENV_USE4 = /\b(?:import\.meta\.env|process\.env)(?:\.([A-Z][A-Z0-9_]*)|\[\s*['"]([A-Z][A-Z0-9_]*)['"]\s*\])/g;
29516
+ var PUBLIC_PREFIX4 = /^(?:VITE_|REACT_APP_|PUBLIC_)/;
29517
+ var ELECTRON_NODE = /^(?:src\/)?(?:electron|main|preload)\//;
29518
+ var SERVER_KEY = /(?:^|_)(?:RESEND|SENDGRID|TWILIO|POSTMARK|MAILGUN|MAILCHIMP|BREVO|SENDINBLUE|LOOPS|PLUNK)_|PASSWORD|PASSWD|PRIVATE_KEY|DATABASE_URL|DB_URL|POSTGRES|MONGO(?:DB)?_URI|REDIS_URL|(?:GITHUB|SLACK|DISCORD|TELEGRAM|NOTION|AIRTABLE|HUBSPOT|SHOPIFY_ADMIN)_(?:\w+_)?(?:TOKEN|KEY|SECRET)|STRIPE_(?:SECRET|RESTRICTED)|WEBHOOK_SECRET|AWS_SECRET|ADMIN_(?:KEY|TOKEN|PASSWORD)/;
29519
+ var SERVER_PACKAGE = /^(?:stripe|resend|twilio|@sendgrid\/mail|postmark|nodemailer|mailgun\.js|firebase-admin(?:\/.*)?|mongodb|mongoose|pg|postgres|mysql2|@octokit\/rest)$/;
29520
+ function isBrowserServerKey(name, supabase2) {
29521
+ if (!PUBLIC_PREFIX4.test(name) || isPublicAiKey(name)) return false;
29522
+ if (supabase2 && isPublicSecretName(name)) return false;
29523
+ const rest = name.replace(PUBLIC_PREFIX4, "");
29524
+ return SERVER_KEY.test(rest) || /SECRET|SERVICE_ROLE/.test(rest);
29525
+ }
29526
+ function browserKeysUsed(raw, supabase2) {
29527
+ const masked = maskCode(raw);
29528
+ const names = [...raw.matchAll(ENV_USE4)].filter((m) => masked[m.index] !== " ").map((m) => m[1] ?? m[2] ?? "").filter((n) => isBrowserServerKey(n, supabase2));
29529
+ return [...new Set(names)].sort();
29530
+ }
29531
+ function serverPackagesImported(raw) {
29532
+ const masked = maskCode(raw);
29533
+ const names = parseImports(raw).filter((ref) => !ref.typeOnly && masked[ref.start - 1] !== " " && SERVER_PACKAGE.test(ref.specifier)).map((ref) => ref.specifier);
29534
+ return [...new Set(names)].sort();
29535
+ }
29536
+ function browserKeysListed(text, supabase2) {
29537
+ const names = /* @__PURE__ */ new Set();
29538
+ for (const line of text.replace(/\r\n/g, "\n").split("\n")) {
29539
+ const name = /^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/.exec(line)?.[1];
29540
+ if (name && isBrowserServerKey(name, supabase2)) names.add(name);
29541
+ }
29542
+ return [...names].sort();
29543
+ }
29544
+ var list4 = (names) => names.length > MAX_LIST4 ? `${names.slice(0, MAX_LIST4).join(", ")} and ${names.length - MAX_LIST4} more` : names.join(", ");
29545
+ var code = (names) => list4(names.map((n) => `\`${n}\``));
29546
+ var browserSecrets = {
29547
+ id: "security.browser-secrets",
29548
+ scope: "project",
29549
+ category: "security",
29550
+ title: "Server keys stay out of the browser",
29551
+ obsV: 1,
29552
+ applies: (ctx) => Boolean(ctx.stack.vite),
29553
+ observe(ctx) {
29554
+ const supabase2 = ctx.stack.supabase !== null;
29555
+ const electron = Boolean(ctx.pkg?.dependencies?.electron || ctx.pkg?.devDependencies?.electron);
29556
+ const items = [];
29557
+ let files = 0;
29558
+ for (const file2 of appCodeFiles(ctx)) {
29559
+ const text = ctx.read(file2);
29560
+ if (!text || !isBrowserFile(file2, text, true) || electron && ELECTRON_NODE.test(file2)) continue;
29561
+ files++;
29562
+ const names = browserKeysUsed(text, supabase2);
29563
+ if (names.length)
29564
+ items.push({
29565
+ kind: "public-env",
29566
+ facts: { names: names.length },
29567
+ key: `${file2}:env`,
29568
+ locations: [{ file: file2 }],
29569
+ vars: { file: file2, names: code(names) },
29570
+ meta: { names }
29571
+ });
29572
+ const packages = serverPackagesImported(text);
29573
+ if (packages.length)
29574
+ items.push({
29575
+ kind: "server-package",
29576
+ facts: { packages: packages.length },
29577
+ key: `${file2}:package`,
29578
+ locations: [{ file: file2 }],
29579
+ vars: { file: file2, packages: code(packages) },
29580
+ meta: { packages }
29581
+ });
29582
+ }
29583
+ for (const file2 of ctx.files.filter((f) => ENV_EXAMPLE.test(f))) {
29584
+ const names = browserKeysListed(ctx.read(file2) ?? "", supabase2);
29585
+ if (names.length)
29586
+ items.push({
29587
+ kind: "env-example",
29588
+ facts: { names: names.length },
29589
+ key: `${file2}:example`,
29590
+ locations: [{ file: file2 }],
29591
+ vars: { file: file2, names: code(names) },
29592
+ meta: { names }
29593
+ });
29594
+ }
29595
+ return { facts: { files }, items };
29596
+ }
29597
+ };
29598
+
29599
+ // src/checks/security/edge-functions.ts
29600
+ import path33 from "path";
29601
+
29602
+ // src/checks/hardcoded-urls.ts
29603
+ var STRING_OR_COMMENT = /("(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|`(?:[^`\\]|\\.)*`)|\/\/[^\n]*|\/\*[\s\S]*?\*\//g;
29604
+ function stripComments2(text) {
29605
+ return text.replace(/\r\n/g, "\n").replace(STRING_OR_COMMENT, (m, str) => str ?? m.replace(/[^\n]/g, " "));
29606
+ }
29607
+ var URL_RE = /\bhttps?:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*)(?::\d+)?[^\s'"`)<>]*/gi;
29608
+ var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0"]);
29609
+ var IGNORED_HOSTS = [
29610
+ "schema.org",
29611
+ "json-schema.org",
29612
+ "w3.org",
29613
+ "github.com",
29614
+ "githubusercontent.com",
29615
+ "npmjs.org",
29616
+ "npmjs.com",
29617
+ "fonts.googleapis.com",
29618
+ "fonts.gstatic.com",
29619
+ // Package hosts Deno code (Supabase edge functions) imports from.
29620
+ "esm.sh",
29621
+ "deno.land",
29622
+ "jsr.io",
29623
+ "cdn.jsdelivr.net",
29624
+ "unpkg.com",
29625
+ "cdn.skypack.dev",
29626
+ "example.com",
29627
+ "example.org",
29628
+ "example.net"
29629
+ ];
29630
+ var RESERVED_TLD = /\.(?:example|test|invalid|local|localhost)$/;
29631
+ function isIgnoredHost(host) {
29632
+ return RESERVED_TLD.test(host) || IGNORED_HOSTS.some((h) => host === h || host.endsWith(`.${h}`));
29633
+ }
29634
+ function inCdnTag(text, index) {
29635
+ const open2 = text.lastIndexOf("<", index);
29636
+ return open2 >= 0 && /^<(?:script|Script|link)\b[^>]*$/.test(text.slice(open2, index));
29637
+ }
29638
+ function findUrls(text) {
29639
+ const code2 = stripComments2(text);
29640
+ const hits = [];
29641
+ for (const m of code2.matchAll(URL_RE)) {
29642
+ const host = (m[1] ?? "").toLowerCase().replace(/^www\./, "");
29643
+ if (!host || inCdnTag(code2, m.index)) continue;
29644
+ hits.push({ host, url: m[0], index: m.index });
29645
+ }
29646
+ return hits;
29647
+ }
29648
+ var DEV_GUARD = /NODE_ENV\s*[!=]==?\s*['"`](?:development|production|test)['"`]|['"`](?:development|production|test)['"`]\s*[!=]==?\s*process\.env\.NODE_ENV|import\.meta\.env\.(?:DEV|PROD)\b|__DEV__|\bisDev(?:elopment)?\b|\bisProd(?:uction)?\b/;
29649
+ var ENV_FALLBACK = /(?:process\.env|import\.meta\.env)(?:\.\w+|\[['"]\w+['"]\])\s*(?:\?\?|\|\|)\s*$/;
29650
+ function isDevOnlyUse(code2, index) {
29651
+ const lineStart = code2.lastIndexOf("\n", index - 1) + 1;
29652
+ const before = code2.slice(lineStart, index).replace(/['"`]$/, "");
29653
+ if (ENV_FALLBACK.test(before.trimEnd())) return true;
29654
+ let start = lineStart;
29655
+ for (let i = 0; i < 6 && start > 0; i++) start = code2.lastIndexOf("\n", start - 2) + 1;
29656
+ const lineEnd = code2.indexOf("\n", index);
29657
+ return DEV_GUARD.test(code2.slice(start, lineEnd === -1 ? void 0 : lineEnd));
29658
+ }
29659
+ var RUNS_LOCAL_SERVER = /\bcreateServer\s*\(|\.listen\s*\(/;
29660
+
29661
+ // src/checks/security/edge-functions.ts
29662
+ var MAX_LIST5 = 20;
29663
+ var FUNCTION_ENTRY = /^(?:(.+)\/)?supabase\/functions\/([^/_.][^/]*)\/index\.[cm]?[jt]sx?$/;
29664
+ var IGNORE2 = /aura-ignore/;
29665
+ var USER_CHECK = /\.auth\s*\.\s*(?:getUser|getClaims)\s*\(|\bjwtVerify\s*\(|\bverifyJwt\s*\(/;
29666
+ var SIGNATURE_CHECK = /\.constructEvent(?:Async)?\s*\(|\bverifyWebhook\s*\(|\bnew\s+Webhook\s*\(|\.verify\s*\(\s*(?:payload|body|rawBody|text)|createHmac\s*\(|timingSafeEqual\s*\(|crypto\.subtle\.verify\s*\(|crypto\.subtle\.sign\s*\(\s*['"`]HMAC|\bverify\w*Signature\s*\(/;
29667
+ var HEADER_COMPARE = /(?:headers\.get\s*\([^)]*\)|authHeader|authorization|token)[^\n;]{0,160}(?:!==|===|!=|==)|(?:!==|===|!=|==)[^\n;]{0,160}(?:headers\.get\s*\(|authHeader|authorization)/i;
29668
+ var SHARED_SECRET2 = /Deno\.env\.get\s*\(\s*['"`][A-Z0-9_]*(?:SECRET|CRON|TOKEN|WEBHOOK)[A-Z0-9_]*['"`]\s*\)/;
29669
+ var ADMIN = /SUPABASE_SERVICE_ROLE_KEY|SERVICE_ROLE_KEY|SUPABASE_SECRET_KEY|['"`]service_role['"`]/;
29670
+ var ENV_GET = /Deno\.env\.get\s*\(\s*['"`]([A-Z][A-Z0-9_]*)['"`]\s*\)/g;
29671
+ var KEY_NAME = /KEY|SECRET|TOKEN|PASSWORD/;
29672
+ var SUPABASE_OWN = /^SUPABASE_/;
29673
+ function checksCaller(raw) {
29674
+ const code2 = stripComments2(raw);
29675
+ return USER_CHECK.test(code2) || SIGNATURE_CHECK.test(code2) || SHARED_SECRET2.test(code2) && HEADER_COMPARE.test(code2);
29676
+ }
29677
+ function keysRead(raw) {
29678
+ const code2 = stripComments2(raw);
29679
+ const names = [...code2.matchAll(ENV_GET)].map((m) => m[1] ?? "").filter((n) => KEY_NAME.test(n) && !SUPABASE_OWN.test(n));
29680
+ return [...new Set(names)].sort();
29681
+ }
29682
+ function jwtOffFunctions(toml) {
29683
+ const off = /* @__PURE__ */ new Set();
29684
+ let current2 = null;
29685
+ for (const line of toml.replace(/\r\n/g, "\n").split("\n")) {
29686
+ const section = /^\s*\[functions\.(?:"([^"]+)"|([\w-]+))\]\s*$/.exec(line);
29687
+ if (section) current2 = section[1] ?? section[2] ?? null;
29688
+ else if (/^\s*\[/.test(line)) current2 = null;
29689
+ else if (current2 && /^\s*verify_jwt\s*=\s*false\b/.test(line)) off.add(current2);
29690
+ }
29691
+ return off;
29692
+ }
29693
+ function sharedHelpers(ctx, file2, raw) {
29694
+ return parseImports(raw).filter((ref) => ref.specifier.startsWith(".") && ref.specifier.includes("_shared")).map((ref) => path33.posix.normalize(path33.posix.join(path33.posix.dirname(file2), ref.specifier))).map((target) => ctx.read(target) ?? ctx.read(`${target}.ts`) ?? ctx.read(`${target}/index.ts`) ?? "");
29695
+ }
29696
+ var list5 = (names) => names.length > MAX_LIST5 ? `${names.slice(0, MAX_LIST5).join(", ")} and ${names.length - MAX_LIST5} more` : names.join(", ");
29697
+ var edgeFunctions = {
29698
+ id: "security.edge-functions",
29699
+ category: "security",
29700
+ title: "Edge functions check who is calling",
29701
+ obsV: 1,
29702
+ applies: (ctx) => ctx.files.some((f) => FUNCTION_ENTRY.test(f)),
29703
+ observe(ctx) {
29704
+ const items = [];
29705
+ let functions2 = 0;
29706
+ let guarded2 = 0;
29707
+ let ignored = 0;
29708
+ const configs = /* @__PURE__ */ new Map();
29709
+ for (const file2 of ctx.files.filter((f) => FUNCTION_ENTRY.test(f)).sort()) {
29710
+ const [, prefix = "", name = ""] = FUNCTION_ENTRY.exec(file2) ?? [];
29711
+ const raw = ctx.read(file2);
29712
+ if (!raw) continue;
29713
+ functions2++;
29714
+ if (IGNORE2.test(raw)) {
29715
+ ignored++;
29716
+ continue;
29717
+ }
29718
+ if (checksCaller(raw) || sharedHelpers(ctx, file2, raw).some(checksCaller)) {
29719
+ guarded2++;
29720
+ continue;
29721
+ }
29722
+ const config2 = `${prefix ? `${prefix}/` : ""}supabase/config.toml`;
29723
+ if (!configs.has(config2)) configs.set(config2, jwtOffFunctions(ctx.read(config2) ?? ""));
29724
+ const jwtOff = configs.get(config2)?.has(name) ?? false;
29725
+ const vars = { function: name, file: file2 };
29726
+ if (ADMIN.test(stripComments2(raw))) {
29727
+ items.push({ kind: "open-admin", facts: { jwtOff }, key: `${file2}:admin`, locations: [{ file: file2 }], vars, meta: { function: name, jwtOff } });
29728
+ continue;
29729
+ }
29730
+ const keys = keysRead(raw);
29731
+ if (keys.length)
29732
+ items.push({
29733
+ kind: "open-keys",
29734
+ facts: { jwtOff, keys: keys.length },
29735
+ key: `${file2}:keys`,
29736
+ locations: [{ file: file2 }],
29737
+ vars: { ...vars, keys: list5(keys.map((k) => `\`${k}\``)) },
29738
+ meta: { function: name, jwtOff, keys }
29739
+ });
29740
+ }
29741
+ return { facts: { functions: functions2, guarded: guarded2, ignored }, items };
29742
+ }
29743
+ };
29744
+
29745
+ // src/checks/security/supabase-code.ts
29746
+ var MAX_LIST6 = 20;
29236
29747
  var NOT_AURA2 = /(?:^|\/)(?:\.aura|\.claude|\.agents|\.cursor|\.codex|\.pi|node_modules)\//;
29237
- var list4 = (items) => items.length > MAX_LIST4 ? `${items.slice(0, MAX_LIST4).join(", ")} and ${items.length - MAX_LIST4} more` : items.join(", ");
29748
+ var list6 = (items) => items.length > MAX_LIST6 ? `${items.slice(0, MAX_LIST6).join(", ")} and ${items.length - MAX_LIST6} more` : items.join(", ");
29238
29749
  function browserReason(file2, text) {
29239
29750
  if (isClientFile(text)) return "use-client";
29240
29751
  if (/(?:^|\/)(?:components|hooks)\//.test(file2)) return "browser-folder";
@@ -29308,8 +29819,8 @@ function bucketItems(buckets2) {
29308
29819
  kind,
29309
29820
  facts: { buckets: names.length },
29310
29821
  key: kind === "asset-buckets" ? "public-buckets|assets" : "public-buckets",
29311
- locations: files.slice(0, MAX_LIST4).map((file2) => ({ file: file2 })),
29312
- vars: { buckets: list4(names.map((n) => `"${n}"`)) },
29822
+ locations: files.slice(0, MAX_LIST6).map((file2) => ({ file: file2 })),
29823
+ vars: { buckets: list6(names.map((n) => `"${n}"`)) },
29313
29824
  meta: { buckets: names, files }
29314
29825
  });
29315
29826
  }
@@ -29325,8 +29836,8 @@ function functionItems(fns) {
29325
29836
  kind,
29326
29837
  facts: { functions: group.length },
29327
29838
  key: kind,
29328
- locations: [...new Set(group.map((f) => f.file))].slice(0, MAX_LIST4).map((file2) => ({ file: file2 })),
29329
- vars: { functions: list4(group.map(label2)) },
29839
+ locations: [...new Set(group.map((f) => f.file))].slice(0, MAX_LIST6).map((file2) => ({ file: file2 })),
29840
+ vars: { functions: list6(group.map(label2)) },
29330
29841
  meta: { functions: group.map(label2) }
29331
29842
  }));
29332
29843
  }
@@ -29365,9 +29876,9 @@ var supabaseCode = {
29365
29876
  };
29366
29877
 
29367
29878
  // src/checks/security/secrets.ts
29368
- import { existsSync as existsSync20, mkdtempSync as mkdtempSync2, readFileSync as readFileSync17, rmSync as rmSync3 } from "fs";
29879
+ import { existsSync as existsSync22, mkdtempSync as mkdtempSync2, readFileSync as readFileSync18, rmSync as rmSync3 } from "fs";
29369
29880
  import os4 from "os";
29370
- import path31 from "path";
29881
+ import path34 from "path";
29371
29882
 
29372
29883
  // src/checks/types.ts
29373
29884
  import { createHash as createHash3 } from "crypto";
@@ -29404,7 +29915,7 @@ function scanFilesForSecrets(ctx) {
29404
29915
  if (/(?:^|\/)\.env(?:\.[^/]+)?$/.test(file2) && !/\.(?:example|sample|template)$/.test(file2)) continue;
29405
29916
  const text = ctx.read(file2);
29406
29917
  if (!text) continue;
29407
- for (const m of findSecrets(text)) {
29918
+ for (const m of findLeakedSecrets(text)) {
29408
29919
  hits.push({
29409
29920
  file: file2,
29410
29921
  line: lineOf(text, m.index),
@@ -29422,15 +29933,15 @@ async function gitleaksHistory(ctx) {
29422
29933
  if (!ctx.tracked) return null;
29423
29934
  const bin = await ensureTool("gitleaks", ctx.options.noDownload);
29424
29935
  if (!bin) return null;
29425
- const dir = mkdtempSync2(path31.join(os4.tmpdir(), "aura-gitleaks-"));
29426
- const report = path31.join(dir, "report.json");
29936
+ const dir = mkdtempSync2(path34.join(os4.tmpdir(), "aura-gitleaks-"));
29937
+ const report = path34.join(dir, "report.json");
29427
29938
  try {
29428
29939
  const r = await run(bin.path, ["git", "--no-banner", "--redact", "--report-format", "json", "--report-path", report, "--exit-code", "0", "."], {
29429
29940
  cwd: ctx.root,
29430
29941
  timeoutMs: 18e4
29431
29942
  });
29432
- if (r.code !== 0 || !existsSync20(report)) return null;
29433
- return { hits: JSON.parse(readFileSync17(report, "utf8")), engine: `gitleaks ${bin.version}` };
29943
+ if (r.code !== 0 || !existsSync22(report)) return null;
29944
+ return { hits: JSON.parse(readFileSync18(report, "utf8")), engine: `gitleaks ${bin.version}` };
29434
29945
  } catch {
29435
29946
  return null;
29436
29947
  } finally {
@@ -29504,7 +30015,7 @@ var envFiles = {
29504
30015
  return {
29505
30016
  facts: {},
29506
30017
  items: committed.map((file2) => {
29507
- const leaked = findSecrets(ctx.read(file2) ?? "").filter((m) => m.confidence === "high");
30018
+ const leaked = findLeakedSecrets(ctx.read(file2) ?? "").filter((m) => m.confidence === "high");
29508
30019
  return {
29509
30020
  kind: "env-file",
29510
30021
  facts: { secrets: leaked.length },
@@ -29562,7 +30073,7 @@ function workflowProblems(doc) {
29562
30073
  }
29563
30074
  return { unpinned, noPermissions, prTargetCheckout, injections: [...injections].sort() };
29564
30075
  }
29565
- var list5 = (items, max = 20) => `${items.slice(0, max).join(", ")}${items.length > max ? ` and ${items.length - max} more` : ""}`;
30076
+ var list7 = (items, max = 20) => `${items.slice(0, max).join(", ")}${items.length > max ? ` and ${items.length - max} more` : ""}`;
29566
30077
  var workflows = {
29567
30078
  id: "security.workflows",
29568
30079
  category: "security",
@@ -29592,7 +30103,7 @@ var workflows = {
29592
30103
  items.push({
29593
30104
  kind: "script-injection",
29594
30105
  key: `${file2}:script-injection`,
29595
- vars: { file: file2, expressions: list5(p2.injections) },
30106
+ vars: { file: file2, expressions: list7(p2.injections) },
29596
30107
  meta: { expressions: p2.injections },
29597
30108
  ...at
29598
30109
  });
@@ -29602,7 +30113,7 @@ var workflows = {
29602
30113
  key: `${file2}:unpinned-actions`,
29603
30114
  ...at,
29604
30115
  facts: { workflow: parsed, actions: p2.unpinned.length },
29605
- vars: { file: file2, actions: list5(p2.unpinned) },
30116
+ vars: { file: file2, actions: list7(p2.unpinned) },
29606
30117
  meta: { actions: p2.unpinned }
29607
30118
  });
29608
30119
  if (p2.noPermissions) items.push({ kind: "no-permissions", key: `${file2}:no-permissions`, vars: { file: file2 }, ...at });
@@ -29611,14 +30122,14 @@ var workflows = {
29611
30122
  return {
29612
30123
  facts: { parsed, unreadable: unreadable.length },
29613
30124
  items,
29614
- ...unreadable.length ? { vars: { unreadable: list5(unreadable, 5) } } : {}
30125
+ ...unreadable.length ? { vars: { unreadable: list7(unreadable, 5) } } : {}
29615
30126
  };
29616
30127
  }
29617
30128
  };
29618
30129
 
29619
30130
  // src/checks/security/auth-signals.ts
29620
- var inCode = (re) => (code) => re.test(code);
29621
- var imports2 = (from, call) => (code, raw) => from.test(raw) && call.test(code);
30131
+ var inCode = (re) => (code2) => re.test(code2);
30132
+ var imports2 = (from, call) => (code2, raw) => from.test(raw) && call.test(code2);
29622
30133
  var CUSTOM = [
29623
30134
  ["passwords", inCode(/\b(?:bcrypt(?:js)?|argon2)\.(?:compare|verify|hash)(?:Sync)?\s*\(/)],
29624
30135
  ["passwords", imports2(/from\s+['"](?:bcrypt|bcryptjs|argon2|@node-rs\/(?:argon2|bcrypt))['"]/, /(?<![\w$.])(?:compare|verify|hash)(?:Sync)?\s*\(/)],
@@ -29684,18 +30195,18 @@ function pathReason(file2) {
29684
30195
  }
29685
30196
  function customReasons(text) {
29686
30197
  const raw = text.replace(/\r\n/g, "\n");
29687
- const code = maskCode(raw);
30198
+ const code2 = maskCode(raw);
29688
30199
  const out = /* @__PURE__ */ new Set();
29689
- for (const [reason, test] of CUSTOM) if (!out.has(reason) && test(code, raw)) out.add(reason);
30200
+ for (const [reason, test] of CUSTOM) if (!out.has(reason) && test(code2, raw)) out.add(reason);
29690
30201
  return [...out];
29691
30202
  }
29692
30203
  function usesProviderAuth(text) {
29693
- const code = maskCode(text.replace(/\r\n/g, "\n"));
29694
- return PROVIDER_AUTH.some((re) => re.test(code));
30204
+ const code2 = maskCode(text.replace(/\r\n/g, "\n"));
30205
+ return PROVIDER_AUTH.some((re) => re.test(code2));
29695
30206
  }
29696
30207
  function hasCredentialsSignIn(text) {
29697
- const code = maskCode(text.replace(/\r\n/g, "\n"));
29698
- return CREDENTIALS.test(code) && /\bauthorize\b/.test(code);
30208
+ const code2 = maskCode(text.replace(/\r\n/g, "\n"));
30209
+ return CREDENTIALS.test(code2) && /\bauthorize\b/.test(code2);
29699
30210
  }
29700
30211
  function authReasons(file2, text) {
29701
30212
  const custom2 = customReasons(text);
@@ -29719,7 +30230,7 @@ var LABELS2 = {
29719
30230
  var reasonText = (reasons) => reasons.map((r) => LABELS2[r]).join(" and ");
29720
30231
 
29721
30232
  // src/checks/security/auth-rate-limits.ts
29722
- var MAX_LIST5 = 20;
30233
+ var MAX_LIST7 = 20;
29723
30234
  var cache2 = /* @__PURE__ */ new WeakMap();
29724
30235
  function authEntries(ctx) {
29725
30236
  const cached2 = cache2.get(ctx);
@@ -29752,7 +30263,7 @@ function label3(e) {
29752
30263
  const what = e.kind === "route" ? routePath(e) : e.kind === "action" ? `the server action in ${e.file}` : `the password sign-in in ${e.file}`;
29753
30264
  return `${what} (${reasonText(e.reasons)})`;
29754
30265
  }
29755
- var list6 = (items) => items.length > MAX_LIST5 ? `${items.slice(0, MAX_LIST5).join(", ")} and ${items.length - MAX_LIST5} more` : items.join(", ");
30266
+ var list8 = (items) => items.length > MAX_LIST7 ? `${items.slice(0, MAX_LIST7).join(", ")} and ${items.length - MAX_LIST7} more` : items.join(", ");
29756
30267
  var authRateLimits = {
29757
30268
  id: "security.auth-rate-limits",
29758
30269
  category: "security",
@@ -29770,8 +30281,8 @@ var authRateLimits = {
29770
30281
  kind: "no-rate-limit",
29771
30282
  facts: { routes: open2.length },
29772
30283
  key: "auth-rate-limit",
29773
- locations: open2.slice(0, MAX_LIST5).map((e) => ({ file: e.file })),
29774
- vars: { list: list6(open2.map(label3)) },
30284
+ locations: open2.slice(0, MAX_LIST7).map((e) => ({ file: e.file })),
30285
+ vars: { list: list8(open2.map(label3)) },
29775
30286
  meta: { files: open2.map((e) => e.file), reasons: open2.map((e) => `${e.file}: ${e.reasons.join(", ")}`) }
29776
30287
  }
29777
30288
  ] : [],
@@ -29798,7 +30309,7 @@ var PROVIDER_MARKS = [
29798
30309
  ["Paddle", /paddle-signature/i]
29799
30310
  ];
29800
30311
  function webhookProvider(file2, text) {
29801
- const find = (list9, s) => list9.find(([, re]) => re.test(s))?.[0];
30312
+ const find = (list11, s) => list11.find(([, re]) => re.test(s))?.[0];
29802
30313
  return find(PROVIDERS, file2) ?? find(PROVIDER_MARKS, text) ?? find(PROVIDERS, text) ?? null;
29803
30314
  }
29804
30315
  function importedNames(clause) {
@@ -29817,15 +30328,15 @@ function importedNames(clause) {
29817
30328
  return { calls, namespaces };
29818
30329
  }
29819
30330
  var escapeName = (s) => s.replace(/[$]/g, "\\$");
29820
- function helperVerifies(from, text, files, read2, aliases) {
30331
+ function helperVerifies(from, text, files, read3, aliases) {
29821
30332
  const raw = text.replace(/\r\n/g, "\n");
29822
- const code = maskCode(raw);
30333
+ const code2 = maskCode(raw);
29823
30334
  for (const m of raw.matchAll(/import\s+(?!type\s)([^'";]+?)\s+from\s+['"]([^'"\n]+)['"]/g)) {
29824
30335
  const [, clause = "", spec = ""] = m;
29825
30336
  const target = resolveFrom(from, spec, files, aliases);
29826
- if (!target || !verifiesInText(read2(target) ?? "")) continue;
30337
+ if (!target || !verifiesInText(read3(target) ?? "")) continue;
29827
30338
  const { calls, namespaces } = importedNames(clause);
29828
- const called = calls.some((n) => new RegExp(String.raw`(?<![\w$.])${escapeName(n)}\s*\(`).test(code)) || namespaces.some((n) => new RegExp(String.raw`(?<![\w$.])${escapeName(n)}\s*\.\s*[\w$]+\s*\(`).test(code));
30339
+ const called = calls.some((n) => new RegExp(String.raw`(?<![\w$.])${escapeName(n)}\s*\(`).test(code2)) || namespaces.some((n) => new RegExp(String.raw`(?<![\w$.])${escapeName(n)}\s*\.\s*[\w$]+\s*\(`).test(code2));
29829
30340
  if (called) return target;
29830
30341
  }
29831
30342
  return null;
@@ -29876,33 +30387,33 @@ function readCode(text) {
29876
30387
  const masked = maskCode(src);
29877
30388
  return { src, masked, requestVars: requestVariables(masked) };
29878
30389
  }
29879
- function span(code, from, to) {
29880
- return { raw: code.src.slice(from, to), masked: code.masked.slice(from, to), start: from };
30390
+ function span(code2, from, to) {
30391
+ return { raw: code2.src.slice(from, to), masked: code2.masked.slice(from, to), start: from };
29881
30392
  }
29882
30393
  function trimSpan(s) {
29883
30394
  const lead = s.masked.length - s.masked.trimStart().length;
29884
30395
  const len = s.masked.trim().length;
29885
30396
  return { raw: s.raw.slice(lead, lead + len), masked: s.masked.slice(lead, lead + len), start: s.start + lead };
29886
30397
  }
29887
- function splitTop(code, from, to, sep = ",") {
30398
+ function splitTop(code2, from, to, sep = ",") {
29888
30399
  const parts = [];
29889
30400
  let depth = 0;
29890
30401
  let start = from;
29891
30402
  for (let i = from; i < to; i++) {
29892
- const c = code.masked[i];
30403
+ const c = code2.masked[i];
29893
30404
  if (c === "(" || c === "[" || c === "{") depth++;
29894
30405
  else if (c === ")" || c === "]" || c === "}") depth--;
29895
- else if (c === sep && depth === 0 && !(sep === "+" && (code.masked[i + 1] === "+" || code.masked[i - 1] === "+"))) {
29896
- parts.push(trimSpan(span(code, start, i)));
30406
+ else if (c === sep && depth === 0 && !(sep === "+" && (code2.masked[i + 1] === "+" || code2.masked[i - 1] === "+"))) {
30407
+ parts.push(trimSpan(span(code2, start, i)));
29897
30408
  start = i + 1;
29898
30409
  }
29899
30410
  }
29900
- parts.push(trimSpan(span(code, start, to)));
30411
+ parts.push(trimSpan(span(code2, start, to)));
29901
30412
  return parts.filter((p2) => p2.masked.length > 0);
29902
30413
  }
29903
- function callArgs(code, open2) {
29904
- const close = closingIndex(code.masked, open2);
29905
- return close < 0 ? null : splitTop(code, open2 + 1, close - 1);
30414
+ function callArgs(code2, open2) {
30415
+ const close = closingIndex(code2.masked, open2);
30416
+ return close < 0 ? null : splitTop(code2, open2 + 1, close - 1);
29906
30417
  }
29907
30418
  var CONTINUES_AFTER = /[=+\-*/%|&?:,(<>!~^[{.]$/;
29908
30419
  var CONTINUES_BEFORE = /^[.?:+\-*/%|&,)\]}]/;
@@ -29927,13 +30438,13 @@ function statementEnd(masked, from, limit = 2e3) {
29927
30438
  }
29928
30439
  return end;
29929
30440
  }
29930
- function assignedValue(code, name, before = code.masked.length) {
30441
+ function assignedValue2(code2, name, before = code2.masked.length) {
29931
30442
  const re = new RegExp(`(?<![\\w$])(?:const|let|var)\\s+${name.replace(/\$/g, "\\$")}\\s*(?::[^=;\\n]+)?=(?![=>])`, "g");
29932
30443
  let last = null;
29933
- for (const m of code.masked.matchAll(re)) if (m.index < before) last = m;
30444
+ for (const m of code2.masked.matchAll(re)) if (m.index < before) last = m;
29934
30445
  if (!last) return null;
29935
30446
  const from = last.index + last[0].length;
29936
- return trimSpan(span(code, from, statementEnd(code.masked, from)));
30447
+ return trimSpan(span(code2, from, statementEnd(code2.masked, from)));
29937
30448
  }
29938
30449
  function unwrap2(s) {
29939
30450
  let cur = trimSpan(s);
@@ -29957,39 +30468,39 @@ function unwrap2(s) {
29957
30468
  }
29958
30469
  var QUOTED = /^(['"])\s*\1$/;
29959
30470
  var PLAIN_TEMPLATE = /^`[^`]*`$/;
29960
- function isLiteral(code, s) {
30471
+ function isLiteral(code2, s) {
29961
30472
  const u = unwrap2(s);
29962
30473
  const m = u.masked;
29963
30474
  if (QUOTED.test(m) || PLAIN_TEMPLATE.test(m) && !m.includes("${") || /^-?\d[\d_.eE]*$/.test(m) || /^(?:null|undefined|true|false)$/.test(m)) return true;
29964
- const parts = splitTop(code, u.start, u.start + m.length, "+");
29965
- return parts.length > 1 && parts.every((p2) => isLiteral(code, p2));
30475
+ const parts = splitTop(code2, u.start, u.start + m.length, "+");
30476
+ return parts.length > 1 && parts.every((p2) => isLiteral(code2, p2));
29966
30477
  }
29967
30478
  function startsAsString(s) {
29968
30479
  return /^['"`]/.test(unwrap2(s).masked);
29969
30480
  }
29970
- function templateParts(code, s) {
30481
+ function templateParts(code2, s) {
29971
30482
  const u = unwrap2(s);
29972
30483
  if (!u.masked.startsWith("`")) return null;
29973
30484
  const parts = [];
29974
30485
  const end = u.start + u.masked.length;
29975
30486
  for (let i = u.start + 1; i < end; i++) {
29976
- if (code.masked[i] === "`") break;
29977
- if (code.masked[i] === "$" && code.masked[i + 1] === "{") {
29978
- const close = closingIndex(code.masked, i + 1);
30487
+ if (code2.masked[i] === "`") break;
30488
+ if (code2.masked[i] === "$" && code2.masked[i + 1] === "{") {
30489
+ const close = closingIndex(code2.masked, i + 1);
29979
30490
  if (close < 0) break;
29980
- parts.push(trimSpan(span(code, i + 2, close - 1)));
30491
+ parts.push(trimSpan(span(code2, i + 2, close - 1)));
29981
30492
  i = close - 1;
29982
30493
  }
29983
30494
  }
29984
30495
  return parts;
29985
30496
  }
29986
- function builtParts(code, s) {
29987
- const tpl = templateParts(code, s);
30497
+ function builtParts(code2, s) {
30498
+ const tpl = templateParts(code2, s);
29988
30499
  if (tpl) return tpl.length ? tpl : null;
29989
30500
  const u = unwrap2(s);
29990
- const operands = splitTop(code, u.start, u.start + u.masked.length, "+");
30501
+ const operands = splitTop(code2, u.start, u.start + u.masked.length, "+");
29991
30502
  if (operands.length < 2 || !operands.some((p2) => startsAsString(p2))) return null;
29992
- const dynamic = operands.flatMap((p2) => isLiteral(code, p2) ? [] : templateParts(code, p2) ?? [p2]);
30503
+ const dynamic = operands.flatMap((p2) => isLiteral(code2, p2) ? [] : templateParts(code2, p2) ?? [p2]);
29993
30504
  return dynamic.length ? dynamic : null;
29994
30505
  }
29995
30506
  var isConstant = (s) => /^[A-Z][A-Z0-9_]*(?:\.[A-Z][A-Z0-9_]*)*$/.test(unwrap2(s).masked);
@@ -29997,8 +30508,8 @@ var REQUEST2 = /(?<![\w$])(?:req|request|event|ctx|context|c\.req)\s*\??\.\s*(?:
29997
30508
  var DECLARATION = /(?<![\w$])(?:const|let|var)\s+(?:([\w$]+)|\{([^}]*)\}|\[([^\]]*)\])\s*(?::[^=;\n]+)?=(?![=>])/g;
29998
30509
  function boundNames(m) {
29999
30510
  if (m[1]) return [m[1]];
30000
- const list9 = m[2] ?? m[3] ?? "";
30001
- return list9.split(",").map((part) => {
30511
+ const list11 = m[2] ?? m[3] ?? "";
30512
+ return list11.split(",").map((part) => {
30002
30513
  const p2 = part.trim().replace(/^\.\.\./, "");
30003
30514
  const target = p2.includes(":") ? p2.slice(p2.indexOf(":") + 1) : p2;
30004
30515
  return target.split("=")[0]?.trim() ?? "";
@@ -30020,13 +30531,13 @@ function mentionsRequest(masked, vars) {
30020
30531
  for (const word of masked.matchAll(/(?<![\w$.])[A-Za-z_$][\w$]*/g)) if (vars.has(word[0])) return true;
30021
30532
  return false;
30022
30533
  }
30023
- function fromRequest(code, s) {
30024
- return mentionsRequest(s.masked, code.requestVars);
30534
+ function fromRequest(code2, s) {
30535
+ return mentionsRequest(s.masked, code2.requestVars);
30025
30536
  }
30026
- var readsRequest = (code) => REQUEST2.test(code.masked);
30537
+ var readsRequest = (code2) => REQUEST2.test(code2.masked);
30027
30538
  var FETCHED = /(?<![\w$.])fetch\s*\(|(?<!(?:Array|Buffer|Object|Uint8Array|Observable|Set|Map)\s*)\.\s*from\s*\(|\.\s*select\s*\(|\.\s*find(?:Many|Unique|First)\w*\s*\(/;
30028
- var readsFetchedData = (code) => FETCHED.test(code.masked);
30029
- var isCode2 = (code, index) => code.masked[index] === code.src[index];
30539
+ var readsFetchedData = (code2) => FETCHED.test(code2.masked);
30540
+ var isCode2 = (code2, index) => code2.masked[index] === code2.src[index];
30030
30541
 
30031
30542
  // src/checks/security/risky-code-cors.ts
30032
30543
  var ANY = "cors-any";
@@ -30041,9 +30552,9 @@ var CORS_IMPORT = [
30041
30552
  /import\s*\{[^}]*\bcors\b(?:\s+as\s+([\w$]+))?[^}]*\}\s*from\s*(['"])hono\/cors\2/g,
30042
30553
  /(?:const|let|var)\s+([\w$]+)\s*=\s*require\s*\(\s*(['"])cors\2\s*\)/g
30043
30554
  ];
30044
- function corsNames(code) {
30555
+ function corsNames(code2) {
30045
30556
  const names = /* @__PURE__ */ new Set();
30046
- for (const re of CORS_IMPORT) for (const m of code.src.matchAll(re)) if (isCode2(code, m.index)) names.add(m[1] ?? "cors");
30557
+ for (const re of CORS_IMPORT) for (const m of code2.src.matchAll(re)) if (isCode2(code2, m.index)) names.add(m[1] ?? "cors");
30047
30558
  return [...names];
30048
30559
  }
30049
30560
  function corsOptions(options) {
@@ -30055,22 +30566,22 @@ function corsOptions(options) {
30055
30566
  const value = options.raw.slice((origin.index ?? 0) + origin[0].length);
30056
30567
  return { open: /^(?:true\b|(['"`])\*\1)/.test(value), credentials };
30057
30568
  }
30058
- function corsInCode(code) {
30569
+ function corsInCode(code2) {
30059
30570
  const open2 = [];
30060
- for (const re of [HEADER_ANY, KV_ANY]) for (const m of code.src.matchAll(re)) if (isCode2(code, m.index)) open2.push({ index: m.index, what: ANY });
30061
- if (READS_ORIGIN.test(code.src) && !CHECKS_ORIGIN.test(code.masked)) {
30062
- for (const m of code.src.matchAll(HEADER_VALUE)) {
30063
- const after = code.masked.slice(m.index + m[0].length, m.index + m[0].length + 120).split(/[)},;\n]/)[0] ?? "";
30064
- if (isCode2(code, m.index) && /origin/i.test(after) && !/^\s*['"`]/.test(after)) open2.push({ index: m.index, what: "cors-echo" });
30571
+ for (const re of [HEADER_ANY, KV_ANY]) for (const m of code2.src.matchAll(re)) if (isCode2(code2, m.index)) open2.push({ index: m.index, what: ANY });
30572
+ if (READS_ORIGIN.test(code2.src) && !CHECKS_ORIGIN.test(code2.masked)) {
30573
+ for (const m of code2.src.matchAll(HEADER_VALUE)) {
30574
+ const after = code2.masked.slice(m.index + m[0].length, m.index + m[0].length + 120).split(/[)},;\n]/)[0] ?? "";
30575
+ if (isCode2(code2, m.index) && /origin/i.test(after) && !/^\s*['"`]/.test(after)) open2.push({ index: m.index, what: "cors-echo" });
30065
30576
  }
30066
30577
  }
30067
- let credentials = [...code.src.matchAll(CREDENTIALS2)].some((m) => isCode2(code, m.index));
30068
- for (const name of corsNames(code)) {
30578
+ let credentials = [...code2.src.matchAll(CREDENTIALS2)].some((m) => isCode2(code2, m.index));
30579
+ for (const name of corsNames(code2)) {
30069
30580
  const esc3 = name.replace(/\$/g, "\\$");
30070
30581
  const calls = new RegExp(`(?<![\\w$.])${esc3}\\s*\\(|(?<![\\w$])register\\s*\\(\\s*${esc3}\\s*(?=[,)])`, "g");
30071
- for (const m of code.masked.matchAll(calls)) {
30582
+ for (const m of code2.masked.matchAll(calls)) {
30072
30583
  const isRegister = m[0].startsWith("register");
30073
- const args = callArgs(code, isRegister ? m.index + m[0].indexOf("(") : m.index + m[0].length - 1);
30584
+ const args = callArgs(code2, isRegister ? m.index + m[0].indexOf("(") : m.index + m[0].length - 1);
30074
30585
  if (!args) continue;
30075
30586
  const opts = corsOptions(isRegister ? args[1] : args[0]);
30076
30587
  credentials ||= opts.credentials;
@@ -30090,8 +30601,8 @@ function corsInVercelJson(text) {
30090
30601
  let open2 = false;
30091
30602
  let credentials = false;
30092
30603
  for (const group of Array.isArray(groups) ? groups : []) {
30093
- const list9 = group.headers;
30094
- for (const h of Array.isArray(list9) ? list9 : []) {
30604
+ const list11 = group.headers;
30605
+ for (const h of Array.isArray(list11) ? list11 : []) {
30095
30606
  const key = String(h.key ?? "").toLowerCase();
30096
30607
  const value = String(h.value ?? "").trim();
30097
30608
  if (key === "access-control-allow-origin" && value === "*") open2 = true;
@@ -30103,8 +30614,8 @@ function corsInVercelJson(text) {
30103
30614
  }
30104
30615
  var SIGN_IN = /\.\s*auth\s*\.\s*get(?:User|Session|Claims)\s*\(|(?<![\w$.])(?:getServerSession|getSession|getUser|currentUser|getAuth|requireAuth|requireUser|withAuth|verifyToken|getToken|jwtVerify)\s*\(|(?<![\w$.])auth\s*\(\s*\)|(?<![\w$.])cookies\s*\(|\.\s*cookies\b|\bjwt\s*\.\s*verify\s*\(/;
30105
30616
  var AUTH_HEADER = /(?:get\s*\(\s*|\[\s*)(['"`])(?:authorization|cookie)\1/gi;
30106
- function checksSignIn(code) {
30107
- return SIGN_IN.test(code.masked) || [...code.src.matchAll(AUTH_HEADER)].some((m) => isCode2(code, m.index));
30617
+ function checksSignIn(code2) {
30618
+ return SIGN_IN.test(code2.masked) || [...code2.src.matchAll(AUTH_HEADER)].some((m) => isCode2(code2, m.index));
30108
30619
  }
30109
30620
  function corsHits(reading, signIn2) {
30110
30621
  if (!reading.open.length || !reading.credentials && !signIn2) return [];
@@ -30116,60 +30627,60 @@ function corsHits(reading, signIn2) {
30116
30627
  // src/checks/security/risky-code-inject.ts
30117
30628
  var SANITISER = /(?<![\w$])[\w$]*(?:saniti[sz]e|purify|escape|safe)[\w$]*(?=\s*\()|(?<![\w$.])xss(?=\s*\()|\bsanitize\s*:\s*true\b/gi;
30118
30629
  var cleans = (masked) => [...masked.matchAll(SANITISER)].some((m) => !/unsafe/i.test(m[0]));
30119
- function cleanHtml(code, s, depth = 0) {
30630
+ function cleanHtml(code2, s, depth = 0) {
30120
30631
  const u = unwrap2(s);
30121
- if (isLiteral(code, u) || cleans(u.masked)) return true;
30632
+ if (isLiteral(code2, u) || cleans(u.masked)) return true;
30122
30633
  if (depth < 2 && /^[\w$]+$/.test(u.masked)) {
30123
- const value = assignedValue(code, u.masked, u.start);
30124
- return value ? cleanHtml(code, value, depth + 1) : false;
30634
+ const value = assignedValue2(code2, u.masked, u.start);
30635
+ return value ? cleanHtml(code2, value, depth + 1) : false;
30125
30636
  }
30126
30637
  return false;
30127
30638
  }
30128
- function htmlValue(code, expr, depth = 0) {
30639
+ function htmlValue(code2, expr, depth = 0) {
30129
30640
  const u = unwrap2(expr);
30130
- if (u.masked.startsWith("{") && closingIndex(code.masked, u.start) === u.start + u.masked.length) {
30131
- for (const entry of splitTop(code, u.start + 1, u.start + u.masked.length - 1)) {
30641
+ if (u.masked.startsWith("{") && closingIndex(code2.masked, u.start) === u.start + u.masked.length) {
30642
+ for (const entry of splitTop(code2, u.start + 1, u.start + u.masked.length - 1)) {
30132
30643
  const key = entry.masked.match(/^__html\s*(:)?/);
30133
30644
  if (!key) continue;
30134
- return key[1] ? trimSpan(span(code, entry.start + key[0].length, entry.start + entry.masked.length)) : entry;
30645
+ return key[1] ? trimSpan(span(code2, entry.start + key[0].length, entry.start + entry.masked.length)) : entry;
30135
30646
  }
30136
30647
  }
30137
30648
  if (depth < 1 && /^[\w$]+$/.test(u.masked)) {
30138
- const value = assignedValue(code, u.masked, u.start);
30139
- if (value?.masked.startsWith("{")) return htmlValue(code, value, depth + 1);
30649
+ const value = assignedValue2(code2, u.masked, u.start);
30650
+ if (value?.masked.startsWith("{")) return htmlValue(code2, value, depth + 1);
30140
30651
  }
30141
30652
  return u;
30142
30653
  }
30143
- function tagOf(code, index) {
30144
- const open2 = code.masked.lastIndexOf("<", index);
30145
- return open2 < 0 ? "" : code.src.slice(open2, index);
30654
+ function tagOf(code2, index) {
30655
+ const open2 = code2.masked.lastIndexOf("<", index);
30656
+ return open2 < 0 ? "" : code2.src.slice(open2, index);
30146
30657
  }
30147
- function unsafeHtml(code) {
30148
- if (/(['"])rehype-sanitize\1/.test(code.src) || /\brehypeSanitize\b/.test(code.masked)) return [];
30658
+ function unsafeHtml(code2) {
30659
+ if (/(['"])rehype-sanitize\1/.test(code2.src) || /\brehypeSanitize\b/.test(code2.masked)) return [];
30149
30660
  const hits = [];
30150
- const fileHigh = readsRequest(code) || readsFetchedData(code);
30661
+ const fileHigh = readsRequest(code2) || readsFetchedData(code2);
30151
30662
  const push = (index, value, what, styleTag = false) => {
30152
- if (cleanHtml(code, value)) return;
30153
- const high = fileHigh || fromRequest(code, value);
30663
+ if (cleanHtml(code2, value)) return;
30664
+ const high = fileHigh || fromRequest(code2, value);
30154
30665
  if (styleTag && !high) return;
30155
30666
  hits.push({ kind: "html", severity: high ? "high" : "medium", index, what });
30156
30667
  };
30157
- for (const m of code.masked.matchAll(/(?<![\w$])dangerouslySetInnerHTML\s*=\s*\{/g)) {
30668
+ for (const m of code2.masked.matchAll(/(?<![\w$])dangerouslySetInnerHTML\s*=\s*\{/g)) {
30158
30669
  const open2 = m.index + m[0].length - 1;
30159
- const close = closingIndex(code.masked, open2);
30670
+ const close = closingIndex(code2.masked, open2);
30160
30671
  if (close < 0) continue;
30161
- const value = htmlValue(code, trimSpan(span(code, open2 + 1, close - 1)));
30162
- const tag = tagOf(code, m.index);
30672
+ const value = htmlValue(code2, trimSpan(span(code2, open2 + 1, close - 1)));
30673
+ const tag = tagOf(code2, m.index);
30163
30674
  const json2 = /^<script\b/.test(tag) && /type\s*=\s*\{?\s*(['"`])application\/(?:ld\+)?json\1/.test(tag);
30164
30675
  if (json2 && /JSON\s*\.\s*stringify\s*\(/.test(value.masked)) continue;
30165
30676
  push(m.index, value, "html-prop", /^<style\b/.test(tag));
30166
30677
  }
30167
- for (const m of code.masked.matchAll(/\.\s*(?:inner|outer)HTML\s*\+?=(?![=>])/g)) {
30678
+ for (const m of code2.masked.matchAll(/\.\s*(?:inner|outer)HTML\s*\+?=(?![=>])/g)) {
30168
30679
  const from = m.index + m[0].length;
30169
- push(m.index, trimSpan(span(code, from, statementEnd(code.masked, from))), "html-inner");
30680
+ push(m.index, trimSpan(span(code2, from, statementEnd(code2.masked, from))), "html-inner");
30170
30681
  }
30171
- for (const m of code.masked.matchAll(/\.\s*insertAdjacentHTML\s*\(/g)) {
30172
- const html = callArgs(code, m.index + m[0].length - 1)?.[1];
30682
+ for (const m of code2.masked.matchAll(/\.\s*insertAdjacentHTML\s*\(/g)) {
30683
+ const html = callArgs(code2, m.index + m[0].length - 1)?.[1];
30173
30684
  if (html) push(m.index, html, "html-adjacent");
30174
30685
  }
30175
30686
  return hits;
@@ -30180,72 +30691,72 @@ var TIMER = /(?<![\w$.])(?:(?:window|globalThis|self)\s*\.\s*)?set(?:Timeout|Int
30180
30691
  var VM = /(?<![\w$])vm\s*\.\s*(?:runIn(?:New|This)?Context|compileFunction)\s*\(|(?<![\w$])new\s+vm\s*\.\s*Script\s*\(/g;
30181
30692
  var VM_BARE = /(?<![\w$.])(?:runIn(?:New|This)?Context|compileFunction)\s*\(|(?<![\w$.])new\s+Script\s*\(/g;
30182
30693
  var IMPORTS_VM = /(?:\bfrom\s*|\brequire\s*\(\s*)(['"])(?:node:)?vm\1/;
30183
- function codeFromText(code) {
30694
+ function codeFromText(code2) {
30184
30695
  const hits = [];
30185
30696
  const check2 = (re, what, bad) => {
30186
- for (const m of code.masked.matchAll(re)) {
30187
- const args = callArgs(code, m.index + m[0].length - 1);
30697
+ for (const m of code2.masked.matchAll(re)) {
30698
+ const args = callArgs(code2, m.index + m[0].length - 1);
30188
30699
  if (!args || !bad(args)) continue;
30189
- hits.push({ kind: "eval", severity: args.some((a) => fromRequest(code, a)) ? "high" : "medium", index: m.index, what });
30700
+ hits.push({ kind: "eval", severity: args.some((a) => fromRequest(code2, a)) ? "high" : "medium", index: m.index, what });
30190
30701
  }
30191
30702
  };
30192
- const dynamic = (args) => args.length > 0 && !args.every((a) => isLiteral(code, a));
30703
+ const dynamic = (args) => args.length > 0 && !args.every((a) => isLiteral(code2, a));
30193
30704
  check2(EVAL, "eval", dynamic);
30194
30705
  check2(FUNCTION, "new-function", dynamic);
30195
30706
  check2(TIMER, "timer", (args) => args[0] !== void 0 && startsAsString(args[0]));
30196
- const firstDynamic = (args) => args[0] !== void 0 && !isLiteral(code, args[0]);
30707
+ const firstDynamic = (args) => args[0] !== void 0 && !isLiteral(code2, args[0]);
30197
30708
  check2(VM, "vm", firstDynamic);
30198
- if (IMPORTS_VM.test(code.src)) check2(VM_BARE, "vm", firstDynamic);
30709
+ if (IMPORTS_VM.test(code2.src)) check2(VM_BARE, "vm", firstDynamic);
30199
30710
  return hits;
30200
30711
  }
30201
30712
  var SQL_TEXT = /\bselect\b[\s\S]*?\bfrom\b|\binsert\s+into\b|\bupdate\s+[\w$."`{}]+\s+set\b|\bdelete\s+from\b|\bwhere\b/i;
30202
30713
  var SQL_SINK = /\.\s*(?:query|execute|raw|unsafe|\$queryRawUnsafe|\$executeRawUnsafe)\s*\(/g;
30203
30714
  var DB_SINK = /(?<![\w$])(?:[\w$]*(?:db|database|sqlite)|DB)\s*\.\s*(?:run|all|get|exec|prepare|each)\s*\(/gi;
30204
30715
  var FILTER_TEXT = /\.(?:eq|neq|gt|gte|lt|lte|like|ilike|is|in|cs|cd|ov|fts|plfts|phfts|wfts|match|imatch)\./;
30205
- var PLACEHOLDER = /(['"`])\s*[$?]/;
30716
+ var PLACEHOLDER2 = /(['"`])\s*[$?]/;
30206
30717
  var ID_LIKE = /(?:^|\.)id$|[a-z]Id$|_id$|ID$/;
30207
- function riskyParts(code, s, ids = false) {
30208
- const parts = builtParts(code, s) ?? [];
30718
+ function riskyParts(code2, s, ids = false) {
30719
+ const parts = builtParts(code2, s) ?? [];
30209
30720
  return parts.filter((p2) => {
30210
- if (isConstant(p2) || PLACEHOLDER.test(p2.raw)) return false;
30211
- if (ids && !fromRequest(code, p2) && ID_LIKE.test(unwrap2(p2).masked)) return false;
30721
+ if (isConstant(p2) || PLACEHOLDER2.test(p2.raw)) return false;
30722
+ if (ids && !fromRequest(code2, p2) && ID_LIKE.test(unwrap2(p2).masked)) return false;
30212
30723
  return true;
30213
30724
  });
30214
30725
  }
30215
- function queryText(code, arg) {
30726
+ function queryText(code2, arg) {
30216
30727
  const u = unwrap2(arg);
30217
- if (/^[\w$]+$/.test(u.masked)) return assignedValue(code, u.masked, u.start) ?? u;
30728
+ if (/^[\w$]+$/.test(u.masked)) return assignedValue2(code2, u.masked, u.start) ?? u;
30218
30729
  return u;
30219
30730
  }
30220
- function stringSql(code) {
30731
+ function stringSql(code2) {
30221
30732
  const hits = [];
30222
- const add = (index, parts, what) => hits.push({ kind: "sql", severity: parts.some((p2) => fromRequest(code, p2)) ? "high" : "medium", index, what });
30733
+ const add = (index, parts, what) => hits.push({ kind: "sql", severity: parts.some((p2) => fromRequest(code2, p2)) ? "high" : "medium", index, what });
30223
30734
  for (const re of [SQL_SINK, DB_SINK]) {
30224
- for (const m of code.masked.matchAll(re)) {
30225
- const first = callArgs(code, m.index + m[0].length - 1)?.[0];
30735
+ for (const m of code2.masked.matchAll(re)) {
30736
+ const first = callArgs(code2, m.index + m[0].length - 1)?.[0];
30226
30737
  if (!first) continue;
30227
- const text = queryText(code, first);
30228
- const parts = riskyParts(code, text);
30738
+ const text = queryText(code2, first);
30739
+ const parts = riskyParts(code2, text);
30229
30740
  if (parts.length && SQL_TEXT.test(text.raw)) add(m.index, parts, "sql-text");
30230
30741
  }
30231
30742
  }
30232
- for (const m of code.masked.matchAll(/\.\s*rpc\s*\(/g)) {
30233
- const params = callArgs(code, m.index + m[0].length - 1)?.[1];
30743
+ for (const m of code2.masked.matchAll(/\.\s*rpc\s*\(/g)) {
30744
+ const params = callArgs(code2, m.index + m[0].length - 1)?.[1];
30234
30745
  if (!params?.masked.startsWith("{")) continue;
30235
- const values = splitTop(code, params.start + 1, params.start + params.masked.length - 1).map((e) => {
30746
+ const values = splitTop(code2, params.start + 1, params.start + params.masked.length - 1).map((e) => {
30236
30747
  const colon = e.masked.match(/^[\w$]+\s*:/);
30237
- return colon ? trimSpan(span(code, e.start + colon[0].length, e.start + e.masked.length)) : e;
30748
+ return colon ? trimSpan(span(code2, e.start + colon[0].length, e.start + e.masked.length)) : e;
30238
30749
  });
30239
- const parts = values.flatMap((v) => SQL_TEXT.test(v.raw) ? riskyParts(code, v) : []);
30750
+ const parts = values.flatMap((v) => SQL_TEXT.test(v.raw) ? riskyParts(code2, v) : []);
30240
30751
  if (parts.length) add(m.index, parts, "sql-rpc");
30241
30752
  }
30242
- for (const m of code.masked.matchAll(/\.\s*(or|filter)\s*\(/g)) {
30243
- const args = callArgs(code, m.index + m[0].length - 1);
30753
+ for (const m of code2.masked.matchAll(/\.\s*(or|filter)\s*\(/g)) {
30754
+ const args = callArgs(code2, m.index + m[0].length - 1);
30244
30755
  if (!args) continue;
30245
30756
  const isOr = m[1] === "or";
30246
30757
  if (!isOr && args.length < 2) continue;
30247
30758
  const candidates2 = isOr ? args.slice(0, 1).filter((a) => FILTER_TEXT.test(a.raw)) : args;
30248
- const parts = candidates2.flatMap((a) => riskyParts(code, a, true));
30759
+ const parts = candidates2.flatMap((a) => riskyParts(code2, a, true));
30249
30760
  if (parts.length) add(m.index, parts, isOr ? "sql-or" : "sql-filter");
30250
30761
  }
30251
30762
  return hits;
@@ -30273,13 +30784,13 @@ function callsValidator(masked) {
30273
30784
  var DOUBLE_SLASH_CHECK = /startsWith\s*\(\s*(['"`])(?:\/\/|\\\\)\1|\^\\\/\(\?!|\^\/\(\?!\//;
30274
30785
  var SINGLE_SLASH_CHECK = /startsWith\s*\(\s*(['"`])\/\1/;
30275
30786
  var ORIGIN_PREFIX = /^`\$\{\s*[\w$.]*origin\s*\}|^[\w$.]*origin\s*\+/i;
30276
- function nextVariables(code) {
30787
+ function nextVariables(code2) {
30277
30788
  const vars = /* @__PURE__ */ new Set();
30278
30789
  const decl = /(?<![\w$])(?:const|let|var)\s+(?:([\w$]+)|\{([^}]*)\})\s*(?::[^=;\n]+)?=(?![=>])/g;
30279
30790
  for (let pass = 0; pass < 2; pass++) {
30280
- for (const m of code.masked.matchAll(decl)) {
30791
+ for (const m of code2.masked.matchAll(decl)) {
30281
30792
  const from = m.index + m[0].length;
30282
- const value = trimSpan(span(code, from, statementEnd(code.masked, from)));
30793
+ const value = trimSpan(span(code2, from, statementEnd(code2.masked, from)));
30283
30794
  if (m[1]) {
30284
30795
  if (isSource(value, vars)) vars.add(m[1]);
30285
30796
  } else if (PARAM_OBJECT.test(value.masked)) {
@@ -30302,10 +30813,10 @@ function startsWithLocalPath(s) {
30302
30813
  const raw = unwrap2(s).raw;
30303
30814
  return /^(['"`])\/(?![/\\])[^'"`$]/.test(raw);
30304
30815
  }
30305
- function openRedirects(code) {
30306
- if (VALIDATED_MASKED.test(code.masked) || callsValidator(code.masked) || DOUBLE_SLASH_CHECK.test(code.src)) return [];
30307
- const vars = nextVariables(code);
30308
- const singleSlash = SINGLE_SLASH_CHECK.test(code.src);
30816
+ function openRedirects(code2) {
30817
+ if (VALIDATED_MASKED.test(code2.masked) || callsValidator(code2.masked) || DOUBLE_SLASH_CHECK.test(code2.src)) return [];
30818
+ const vars = nextVariables(code2);
30819
+ const singleSlash = SINGLE_SLASH_CHECK.test(code2.src);
30309
30820
  const hits = [];
30310
30821
  const consider = (index, value) => {
30311
30822
  const u = unwrap2(value);
@@ -30313,12 +30824,12 @@ function openRedirects(code) {
30313
30824
  if (singleSlash && ORIGIN_PREFIX.test(u.masked)) return;
30314
30825
  hits.push({ kind: "redirect", severity: "medium", index, what: "redirect" });
30315
30826
  };
30316
- for (const m of code.masked.matchAll(SINK)) {
30317
- for (const arg of callArgs(code, m.index + m[0].length - 1) ?? []) consider(m.index, arg);
30827
+ for (const m of code2.masked.matchAll(SINK)) {
30828
+ for (const arg of callArgs(code2, m.index + m[0].length - 1) ?? []) consider(m.index, arg);
30318
30829
  }
30319
- for (const m of code.masked.matchAll(ASSIGN)) {
30830
+ for (const m of code2.masked.matchAll(ASSIGN)) {
30320
30831
  const from = m.index + m[0].length;
30321
- consider(m.index, trimSpan(span(code, from, statementEnd(code.masked, from))));
30832
+ consider(m.index, trimSpan(span(code2, from, statementEnd(code2.masked, from))));
30322
30833
  }
30323
30834
  return hits;
30324
30835
  }
@@ -30333,8 +30844,8 @@ var NOT_CONFIG = /(?:^|\/)(?:node_modules|\.next|\.vercel|dist|build|out|\.aura|
30333
30844
  var NEXT_CONFIG = /(?:^|\/)next\.config\.[cm]?[jt]s$/;
30334
30845
  var VERCEL_JSON = /(?:^|\/)vercel\.json$/;
30335
30846
  var ROUTE_FILE3 = /(?:^|\/)(?:route|middleware|proxy)\.[cm]?[jt]s$|(?:^|\/)pages\/api\//;
30336
- function hitsIn(code) {
30337
- return [...unsafeHtml(code), ...codeFromText(code), ...stringSql(code), ...openRedirects(code), ...corsHits(corsInCode(code), checksSignIn(code))];
30847
+ function hitsIn(code2) {
30848
+ return [...unsafeHtml(code2), ...codeFromText(code2), ...stringSql(code2), ...openRedirects(code2), ...corsHits(corsInCode(code2), checksSignIn(code2))];
30338
30849
  }
30339
30850
  var riskyCode = {
30340
30851
  id: "security.risky-code",
@@ -30349,9 +30860,9 @@ var riskyCode = {
30349
30860
  for (const file2 of files) {
30350
30861
  const text = ctx.read(file2);
30351
30862
  if (!text) continue;
30352
- const code = readCode(text);
30353
- byFile.set(file2, { text, hits: hitsIn(code) });
30354
- if (ROUTE_FILE3.test(file2) && checksSignIn(code)) signInRoutes = true;
30863
+ const code2 = readCode(text);
30864
+ byFile.set(file2, { text, hits: hitsIn(code2) });
30865
+ if (ROUTE_FILE3.test(file2) && checksSignIn(code2)) signInRoutes = true;
30355
30866
  }
30356
30867
  for (const file2 of ctx.files.filter((f) => !NOT_CONFIG.test(f) && (NEXT_CONFIG.test(f) || VERCEL_JSON.test(f)))) {
30357
30868
  const text = ctx.read(file2);
@@ -30401,6 +30912,7 @@ var securityChecks = [
30401
30912
  deps,
30402
30913
  rls,
30403
30914
  apiRoutes,
30915
+ edgeFunctions,
30404
30916
  headers,
30405
30917
  ...CONNECTORS2.map(toolSettingsCheck),
30406
30918
  workflows,
@@ -30411,17 +30923,18 @@ var securityChecks = [
30411
30923
  webhooks,
30412
30924
  supabaseCode,
30413
30925
  serverOnly,
30926
+ browserSecrets,
30414
30927
  riskyCode,
30415
30928
  databaseBackups
30416
30929
  ];
30417
30930
 
30418
30931
  // src/checks/structure.ts
30419
- import path38 from "path";
30932
+ import path41 from "path";
30420
30933
 
30421
30934
  // src/checks/debug-logs.ts
30422
30935
  var USE_CLIENT = /^?(?:\s|\/\/[^\n]*\n|\/\*[\s\S]*?\*\/)*['"]use client['"]/;
30423
30936
  var DEBUG_CALL = /(?<![\w$])console\s*\??\.\s*(?:log|debug|info|table|dir)\s*\(/;
30424
- var DEV_GUARD = /NODE_ENV|import\.meta\.env\.DEV|__DEV__|\bisDev(?:elopment)?\b/;
30937
+ var DEV_GUARD2 = /NODE_ENV|import\.meta\.env\.DEV|__DEV__|\bisDev(?:elopment)?\b/;
30425
30938
  var SERVER_DEPS = [
30426
30939
  "express",
30427
30940
  "fastify",
@@ -30453,7 +30966,7 @@ function isBrowserFile2(file2, text, ctx) {
30453
30966
  }
30454
30967
  function debugLogLines(text) {
30455
30968
  const lines = text.replace(/\r\n/g, "\n").split("\n");
30456
- const found = findLogCalls(text, DEBUG_CALL).map((c) => c.line).filter((line) => !DEV_GUARD.test(`${lines[line - 2] ?? ""}
30969
+ const found = findLogCalls(text, DEBUG_CALL).map((c) => c.line).filter((line) => !DEV_GUARD2.test(`${lines[line - 2] ?? ""}
30457
30970
  ${lines[line - 1] ?? ""}`));
30458
30971
  return [...new Set(found)];
30459
30972
  }
@@ -30489,7 +31002,7 @@ var debugLogs = {
30489
31002
 
30490
31003
  // src/checks/repo-hygiene.ts
30491
31004
  import { statSync as statSync4 } from "fs";
30492
- import path32 from "path";
31005
+ import path35 from "path";
30493
31006
  var MB = 1024 * 1024;
30494
31007
  var LARGE_FILE_BYTES = 5 * MB;
30495
31008
  var HEAVY_FILE_BYTES = 1 * MB;
@@ -30527,7 +31040,7 @@ function lfsPatterns(gitattributes) {
30527
31040
  return pattern.includes("/") ? globToRegex(pattern) : globToRegex(`**/${pattern}`);
30528
31041
  });
30529
31042
  }
30530
- function largeFiles(root, tracked, lfs = [], stat = statSync4, p2 = path32) {
31043
+ function largeFiles(root, tracked, lfs = [], stat = statSync4, p2 = path35) {
30531
31044
  const out = [];
30532
31045
  for (const file2 of tracked) {
30533
31046
  if (AURA_FILES.test(file2) || lfs.some((re) => re.test(file2))) continue;
@@ -30580,10 +31093,10 @@ var repoHygiene = {
30580
31093
  };
30581
31094
 
30582
31095
  // src/checks/naming.ts
30583
- import path34 from "path";
31096
+ import path37 from "path";
30584
31097
 
30585
31098
  // src/checks/name-styles.ts
30586
- import path33 from "path";
31099
+ import path36 from "path";
30587
31100
  var STYLES = ["kebab", "pascal", "camel", "snake"];
30588
31101
  var SHORT_LABEL = {
30589
31102
  kebab: "kebab-case",
@@ -30671,7 +31184,7 @@ function isAppCode(f) {
30671
31184
  return !f.split("/").some((seg) => seg.startsWith(".")) && !isTestFile(f) && !GENERATED.test(f) && !NOT_APP_CODE2.test(f);
30672
31185
  }
30673
31186
  function baseName(file2) {
30674
- let name = path33.posix.basename(file2).replace(NAMED_EXT, "");
31187
+ let name = path36.posix.basename(file2).replace(NAMED_EXT, "");
30675
31188
  while (SUFFIX.test(name)) name = name.replace(SUFFIX, "");
30676
31189
  return name;
30677
31190
  }
@@ -30716,8 +31229,8 @@ function toStyle(name, style) {
30716
31229
  return words2.map((w, i) => i === 0 ? w : cap(w)).join("");
30717
31230
  }
30718
31231
  function renamed(file2, style) {
30719
- const dir = path33.posix.dirname(file2);
30720
- const base2 = path33.posix.basename(file2);
31232
+ const dir = path36.posix.dirname(file2);
31233
+ const base2 = path36.posix.basename(file2);
30721
31234
  const name = baseName(file2);
30722
31235
  const next = toStyle(name, style) + base2.slice(name.length);
30723
31236
  return dir === "." ? next : `${dir}/${next}`;
@@ -30786,10 +31299,10 @@ function folderProblem(folder, entries) {
30786
31299
  if (off.length === 0) return null;
30787
31300
  return { folder, style: target, files: off.map((e) => ({ ...e, suggested: renamed(e.file, target) })) };
30788
31301
  }
30789
- function componentFolderProblem(byFolder, read2) {
31302
+ function componentFolderProblem(byFolder, read3) {
30790
31303
  const styled2 = [];
30791
31304
  for (const [folder, entries] of byFolder) {
30792
- const comps = entries.filter((e) => JSX_EXT.test(e.file) && exportsComponent(read2(e.file) ?? ""));
31305
+ const comps = entries.filter((e) => JSX_EXT.test(e.file) && exportsComponent(read3(e.file) ?? ""));
30793
31306
  if (comps.length < 2) continue;
30794
31307
  const pascal2 = comps.filter((e) => e.style === "pascal").length;
30795
31308
  const lower = comps.filter((e) => e.style === "kebab" || e.style === "single").length;
@@ -30808,7 +31321,7 @@ function componentFolderProblem(byFolder, read2) {
30808
31321
  minority: minor.map((s) => ({ folder: s.folder, files: s.files }))
30809
31322
  };
30810
31323
  }
30811
- function analyzeNaming(files, read2) {
31324
+ function analyzeNaming(files, read3) {
30812
31325
  const byFolder = /* @__PURE__ */ new Map();
30813
31326
  const classified = [];
30814
31327
  const codeNames = [];
@@ -30819,22 +31332,22 @@ function analyzeNaming(files, read2) {
30819
31332
  const style = fileStyle(file2);
30820
31333
  if (style) {
30821
31334
  classified.push(file2);
30822
- const folder = path34.posix.dirname(file2);
31335
+ const folder = path37.posix.dirname(file2);
30823
31336
  byFolder.set(folder, [...byFolder.get(folder) ?? [], { file: file2, style }]);
30824
31337
  }
30825
31338
  if (!CODE_EXT.test(file2) || file2.endsWith(".d.ts") || !isAppCode(file2)) continue;
30826
- const text = read2(raw);
31339
+ const text = read3(raw);
30827
31340
  if (!text) continue;
30828
31341
  const names = badCodeNames(file2, text);
30829
31342
  if (names.length) codeNames.push({ file: file2, names });
30830
31343
  }
30831
31344
  const folders = [...byFolder].map(([folder, entries]) => folderProblem(folder, entries)).filter((p2) => p2 !== null).sort((a, b) => a.folder < b.folder ? -1 : a.folder > b.folder ? 1 : 0);
30832
- return { classified, folders, componentFolders: componentFolderProblem(byFolder, (f) => read2(raws.get(f) ?? f)), codeNames };
31345
+ return { classified, folders, componentFolders: componentFolderProblem(byFolder, (f) => read3(raws.get(f) ?? f)), codeNames };
30833
31346
  }
30834
31347
  var MAX_LISTED8 = 20;
30835
31348
  var MAX_FILE_FINDINGS = 20;
30836
31349
  var where = (folder) => folder === "." ? "the top level of the repo" : `${folder}/`;
30837
- var list7 = (items) => `${items.slice(0, MAX_LISTED8).join(", ")}${items.length > MAX_LISTED8 ? ` and ${items.length - MAX_LISTED8} more` : ""}`;
31350
+ var list9 = (items) => `${items.slice(0, MAX_LISTED8).join(", ")}${items.length > MAX_LISTED8 ? ` and ${items.length - MAX_LISTED8} more` : ""}`;
30838
31351
  var naming = {
30839
31352
  id: "structure.naming",
30840
31353
  category: "structure",
@@ -30847,7 +31360,7 @@ var naming = {
30847
31360
  const items = [];
30848
31361
  for (const p2 of a.folders) {
30849
31362
  for (const f of p2.files) flagged.add(f.file);
30850
- const example = a.classified.find((f) => path34.posix.dirname(f) === p2.folder && fileStyle(f) === p2.style);
31363
+ const example = a.classified.find((f) => path37.posix.dirname(f) === p2.folder && fileStyle(f) === p2.style);
30851
31364
  items.push({
30852
31365
  kind: "folder-style",
30853
31366
  facts: { files: p2.files.length, style: p2.style, example: Boolean(example) },
@@ -30855,8 +31368,8 @@ var naming = {
30855
31368
  locations: p2.files.slice(0, MAX_LISTED8).map((f) => ({ file: f.file })),
30856
31369
  vars: {
30857
31370
  where: where(p2.folder),
30858
- example: example ? path34.posix.basename(example) : "",
30859
- files: list7(p2.files.map((f) => `${f.file} (${SHORT_LABEL[f.style]}, would be ${path34.posix.basename(f.suggested)})`))
31371
+ example: example ? path37.posix.basename(example) : "",
31372
+ files: list9(p2.files.map((f) => `${f.file} (${SHORT_LABEL[f.style]}, would be ${path37.posix.basename(f.suggested)})`))
30860
31373
  },
30861
31374
  meta: { folder: p2.folder, style: p2.style, files: p2.files.map((f) => f.file), renames: p2.files.map((f) => `${f.file}=>${f.suggested}`) }
30862
31375
  });
@@ -30871,9 +31384,9 @@ var naming = {
30871
31384
  key: "component-folders",
30872
31385
  locations: minorityFiles.slice(0, MAX_LISTED8).map((file2) => ({ file: file2 })),
30873
31386
  vars: {
30874
- majority: list7(cf.majority.map(where)),
30875
- minority: list7(cf.minority.map((m) => where(m.folder))),
30876
- example: path34.posix.basename(minorityFiles[0] ?? "")
31387
+ majority: list9(cf.majority.map(where)),
31388
+ minority: list9(cf.minority.map((m) => where(m.folder))),
31389
+ example: path37.posix.basename(minorityFiles[0] ?? "")
30877
31390
  },
30878
31391
  meta: { style: cf.style, folders: cf.minority.map((m) => m.folder), files: minorityFiles }
30879
31392
  });
@@ -30899,7 +31412,7 @@ var naming = {
30899
31412
  facts: { files: rest.length },
30900
31413
  key: "code-names:more",
30901
31414
  locations: rest.slice(0, MAX_LISTED8).map((g) => ({ file: g.file })),
30902
- vars: { files: list7(rest.map((g) => `${g.file} (${g.names.map((n) => n.name).join(", ")})`)) },
31415
+ vars: { files: list9(rest.map((g) => `${g.file} (${g.names.map((n) => n.name).join(", ")})`)) },
30903
31416
  meta: { files: rest.map((g) => g.file) }
30904
31417
  });
30905
31418
  }
@@ -30909,70 +31422,18 @@ var naming = {
30909
31422
  };
30910
31423
 
30911
31424
  // src/checks/reuse.ts
30912
- import path36 from "path";
30913
-
30914
- // src/checks/hardcoded-urls.ts
30915
- var STRING_OR_COMMENT = /("(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|`(?:[^`\\]|\\.)*`)|\/\/[^\n]*|\/\*[\s\S]*?\*\//g;
30916
- function stripComments2(text) {
30917
- return text.replace(/\r\n/g, "\n").replace(STRING_OR_COMMENT, (m, str) => str ?? m.replace(/[^\n]/g, " "));
30918
- }
30919
- var URL_RE = /\bhttps?:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*)(?::\d+)?[^\s'"`)<>]*/gi;
30920
- var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0"]);
30921
- var IGNORED_HOSTS = [
30922
- "schema.org",
30923
- "json-schema.org",
30924
- "w3.org",
30925
- "github.com",
30926
- "githubusercontent.com",
30927
- "npmjs.org",
30928
- "npmjs.com",
30929
- "fonts.googleapis.com",
30930
- "fonts.gstatic.com",
30931
- "example.com",
30932
- "example.org",
30933
- "example.net"
30934
- ];
30935
- var RESERVED_TLD = /\.(?:example|test|invalid|local|localhost)$/;
30936
- function isIgnoredHost(host) {
30937
- return RESERVED_TLD.test(host) || IGNORED_HOSTS.some((h) => host === h || host.endsWith(`.${h}`));
30938
- }
30939
- function inCdnTag(text, index) {
30940
- const open2 = text.lastIndexOf("<", index);
30941
- return open2 >= 0 && /^<(?:script|Script|link)\b[^>]*$/.test(text.slice(open2, index));
30942
- }
30943
- function findUrls(text) {
30944
- const code = stripComments2(text);
30945
- const hits = [];
30946
- for (const m of code.matchAll(URL_RE)) {
30947
- const host = (m[1] ?? "").toLowerCase().replace(/^www\./, "");
30948
- if (!host || inCdnTag(code, m.index)) continue;
30949
- hits.push({ host, url: m[0], index: m.index });
30950
- }
30951
- return hits;
30952
- }
30953
- var DEV_GUARD2 = /NODE_ENV\s*[!=]==?\s*['"`](?:development|production|test)['"`]|['"`](?:development|production|test)['"`]\s*[!=]==?\s*process\.env\.NODE_ENV|import\.meta\.env\.(?:DEV|PROD)\b|__DEV__|\bisDev(?:elopment)?\b|\bisProd(?:uction)?\b/;
30954
- var ENV_FALLBACK = /(?:process\.env|import\.meta\.env)(?:\.\w+|\[['"]\w+['"]\])\s*(?:\?\?|\|\|)\s*$/;
30955
- function isDevOnlyUse(code, index) {
30956
- const lineStart = code.lastIndexOf("\n", index - 1) + 1;
30957
- const before = code.slice(lineStart, index).replace(/['"`]$/, "");
30958
- if (ENV_FALLBACK.test(before.trimEnd())) return true;
30959
- let start = lineStart;
30960
- for (let i = 0; i < 6 && start > 0; i++) start = code.lastIndexOf("\n", start - 2) + 1;
30961
- const lineEnd = code.indexOf("\n", index);
30962
- return DEV_GUARD2.test(code.slice(start, lineEnd === -1 ? void 0 : lineEnd));
30963
- }
30964
- var RUNS_LOCAL_SERVER = /\bcreateServer\s*\(|\.listen\s*\(/;
31425
+ import path39 from "path";
30965
31426
 
30966
31427
  // src/checks/import-cycles.ts
30967
- function buildReuseGraph(files, read2) {
30968
- const aliases = readAliases(read2);
31428
+ function buildReuseGraph(files, read3) {
31429
+ const aliases = readAliases(read3);
30969
31430
  const fileSet = new Set(files);
30970
31431
  const app2 = new Set(appSource(files));
30971
31432
  const edges = /* @__PURE__ */ new Map();
30972
31433
  const importers = /* @__PURE__ */ new Map();
30973
31434
  for (const f of files) {
30974
31435
  if (!isSourceFile(f) || SKIP.test(f)) continue;
30975
- const text = read2(f);
31436
+ const text = read3(f);
30976
31437
  if (text === null) continue;
30977
31438
  for (const ref of parseImports(text)) {
30978
31439
  const target = resolveImport(f, ref.specifier, fileSet, aliases);
@@ -31078,8 +31539,8 @@ function fetchTarget(arg) {
31078
31539
  }
31079
31540
  var isUiFile2 = (f) => (/\.(?:tsx|jsx)$/.test(f) || /(?:^|\/)(?:components|pages|views|screens)\//.test(f)) && !/(?:^|\/)route\.[cm]?[jt]sx?$/.test(f) && !/(?:^|\/)pages\/api\//.test(f);
31080
31541
  var CLIENT_MODULE = /(?:^|\/)(?:lib|utils|shared|common|src)\/(?:api|client|http|fetcher|fetch|request|requests|axios)(?:[-._][\w.-]*)?\.[cm]?[jt]sx?$|(?:^|\/)(?:lib\/api|lib\/clients?|services|api-client|clients)\/[^/]+\.[cm]?[jt]sx?$|(?:^|\/)hooks\/use(?:Api|Fetch|Fetcher|Request)\w*\.[cm]?[jt]sx?$/i;
31081
- function hasSharedClient(files, read2) {
31082
- return files.some((f) => CLIENT_MODULE.test(f) || /\baxios\.create\s*\(/.test(read2(f) ?? ""));
31542
+ function hasSharedClient(files, read3) {
31543
+ return files.some((f) => CLIENT_MODULE.test(f) || /\baxios\.create\s*\(/.test(read3(f) ?? ""));
31083
31544
  }
31084
31545
  function scatteredFetches(texts) {
31085
31546
  const byTarget = /* @__PURE__ */ new Map();
@@ -31096,7 +31557,7 @@ function scatteredFetches(texts) {
31096
31557
  }
31097
31558
 
31098
31559
  // src/checks/spare-copies.ts
31099
- import path35 from "path";
31560
+ import path38 from "path";
31100
31561
  var COPY_PATTERNS = [
31101
31562
  /^(.+?)[ ._-]copy(?:[ _-]?\d+)?$/i,
31102
31563
  // Button copy, Button-copy, Button.copy, Button copy 2
@@ -31129,9 +31590,9 @@ function findSpareCopies(files) {
31129
31590
  continue;
31130
31591
  }
31131
31592
  if (!isSourceFile(f)) continue;
31132
- const ext = path35.posix.extname(f);
31133
- const dir = path35.posix.dirname(f);
31134
- const stem = path35.posix.basename(f, ext);
31593
+ const ext = path38.posix.extname(f);
31594
+ const dir = path38.posix.dirname(f);
31595
+ const stem = path38.posix.basename(f, ext);
31135
31596
  for (const re of COPY_PATTERNS) {
31136
31597
  const base2 = stem.match(re)?.[1];
31137
31598
  if (!base2) continue;
@@ -31151,7 +31612,7 @@ var DEV_ONLY = /(?:^|\/)(?:scripts?|cypress|mocks|__mocks__|\.storybook|playwrig
31151
31612
  var CONFIG = /(?:^|\/)(?:[^/]+\.config\.[cm]?[jt]s|config\.[cm]?[jt]s|env\.[cm]?[jt]s)$|(?:^|\/)config\//;
31152
31613
  var MAX_LOCATIONS = 20;
31153
31614
  var MAX_PER_KIND = 20;
31154
- var list8 = (files, max = 10) => `${files.slice(0, max).join(", ")}${files.length > max ? ` and ${files.length - max} more` : ""}`;
31615
+ var list10 = (files, max = 10) => `${files.slice(0, max).join(", ")}${files.length > max ? ` and ${files.length - max} more` : ""}`;
31155
31616
  var locations2 = (files) => files.slice(0, MAX_LOCATIONS).map((file2) => ({ file: file2 }));
31156
31617
  var reuse = {
31157
31618
  id: "structure.reuse",
@@ -31175,7 +31636,7 @@ var reuse = {
31175
31636
  facts: { files: component.length, loop: loop.length },
31176
31637
  key: component.join("|"),
31177
31638
  locations: locations2(loop),
31178
- vars: { loop: [...loop, loop[0]].join(" \u2192 "), files: list8(component) },
31639
+ vars: { loop: [...loop, loop[0]].join(" \u2192 "), files: list10(component) },
31179
31640
  meta: { kind: "cycle", files: component, cycle: loop }
31180
31641
  });
31181
31642
  }
@@ -31192,11 +31653,11 @@ var reuse = {
31192
31653
  locations: locations2(original ? [copy, original] : [copy]),
31193
31654
  vars: {
31194
31655
  copy,
31195
- copyName: path36.posix.basename(copy),
31196
- copyImporters: list8(importersOf(copy), 5),
31656
+ copyName: path39.posix.basename(copy),
31657
+ copyImporters: list10(importersOf(copy), 5),
31197
31658
  original: original ?? "",
31198
- originalName: original ? path36.posix.basename(original) : "",
31199
- originalImporters: original ? list8(importersOf(original), 5) : ""
31659
+ originalName: original ? path39.posix.basename(original) : "",
31660
+ originalImporters: original ? list10(importersOf(original), 5) : ""
31200
31661
  },
31201
31662
  meta: {
31202
31663
  kind: "spare-copy",
@@ -31211,11 +31672,11 @@ var reuse = {
31211
31672
  const localhost = [];
31212
31673
  for (const [file2, text] of texts) {
31213
31674
  if (CONFIG.test(file2) || DEV_ONLY.test(file2)) continue;
31214
- const code = stripComments2(text);
31675
+ const code2 = stripComments2(text);
31215
31676
  let local = null;
31216
31677
  for (const hit of findUrls(text)) {
31217
31678
  if (LOCAL_HOSTS.has(hit.host)) {
31218
- if (!local && !RUNS_LOCAL_SERVER.test(code) && !isDevOnlyUse(code, hit.index)) local = hit.url;
31679
+ if (!local && !RUNS_LOCAL_SERVER.test(code2) && !isDevOnlyUse(code2, hit.index)) local = hit.url;
31219
31680
  continue;
31220
31681
  }
31221
31682
  if (isIgnoredHost(hit.host)) continue;
@@ -31231,7 +31692,7 @@ var reuse = {
31231
31692
  facts: { files: files.length },
31232
31693
  key: `host:${host}`,
31233
31694
  locations: locations2(files),
31234
- vars: { host, files: list8(files) },
31695
+ vars: { host, files: list10(files) },
31235
31696
  meta: { kind: "hardcoded-host", host, files }
31236
31697
  });
31237
31698
  }
@@ -31247,7 +31708,7 @@ var reuse = {
31247
31708
  key: "scattered-fetch",
31248
31709
  locations: locations2(files),
31249
31710
  vars: {
31250
- targets: scattered.slice(0, 5).map((s) => `${s.target} (${list8(s.files, 5)})`).join("; ")
31711
+ targets: scattered.slice(0, 5).map((s) => `${s.target} (${list10(s.files, 5)})`).join("; ")
31251
31712
  },
31252
31713
  meta: { kind: "scattered-fetch", targets: scattered.map((s) => s.target), files }
31253
31714
  });
@@ -31257,7 +31718,7 @@ var reuse = {
31257
31718
  };
31258
31719
 
31259
31720
  // src/checks/theme.ts
31260
- import path37 from "path";
31721
+ import path40 from "path";
31261
31722
 
31262
31723
  // src/checks/colour.ts
31263
31724
  var clamp01 = (n) => Math.min(1, Math.max(0, n));
@@ -31639,7 +32100,7 @@ function contrastProblems(tokens, theme2) {
31639
32100
  // src/checks/theme.ts
31640
32101
  var MAX_LISTED9 = 20;
31641
32102
  function topFolder2(file2) {
31642
- const dir = path37.posix.dirname(file2);
32103
+ const dir = path40.posix.dirname(file2);
31643
32104
  return dir === "." ? "(top level)" : dir;
31644
32105
  }
31645
32106
  var theme = {
@@ -32048,13 +32509,13 @@ var deadFiles = {
32048
32509
  const facts = { sourceFiles: appSource2(ctx).length };
32049
32510
  const engine = "builtin import graph";
32050
32511
  if (dead.length === 0) return { facts, items: [], engine };
32051
- const list9 = `${dead.slice(0, 15).join(", ")}${dead.length > 15 ? ` and ${dead.length - 15} more` : ""}`;
32512
+ const list11 = `${dead.slice(0, 15).join(", ")}${dead.length > 15 ? ` and ${dead.length - 15} more` : ""}`;
32052
32513
  const item = {
32053
32514
  kind: "unused-files",
32054
32515
  facts: { files: dead.length },
32055
32516
  key: "dead",
32056
32517
  locations: dead.map((file2) => ({ file: file2 })),
32057
- vars: { list: list9 },
32518
+ vars: { list: list11 },
32058
32519
  meta: { files: dead }
32059
32520
  };
32060
32521
  return { facts, items: [item], engine };
@@ -32115,7 +32576,7 @@ var duplicates = {
32115
32576
  facts: { similarity: Math.round(sim * 100) },
32116
32577
  key: [a, b].sort().join("|"),
32117
32578
  locations: [{ file: a }, { file: b }],
32118
- vars: { nameA: path38.basename(a), nameB: path38.basename(b), fileA: a, fileB: b },
32579
+ vars: { nameA: path41.basename(a), nameB: path41.basename(b), fileA: a, fileB: b },
32119
32580
  meta: { similarity: Math.round(sim * 100) }
32120
32581
  }));
32121
32582
  return { facts: {}, items };
@@ -32123,7 +32584,7 @@ var duplicates = {
32123
32584
  };
32124
32585
  var ROOT_ALLOWED = /^(?:[^/]+\.config\.[cm]?[jt]s|middleware\.[cm]?[jt]s|proxy\.[cm]?[jt]s|instrumentation(?:-client)?\.[cm]?[jt]s|next-env\.d\.ts|global\.d\.ts|env\.d\.ts|sentry\.[\w.]+\.[cm]?[jt]s)$/;
32125
32586
  function suggestTarget(file2, srcDir) {
32126
- const base2 = path38.posix.basename(file2);
32587
+ const base2 = path41.posix.basename(file2);
32127
32588
  const prefix = srcDir ? "src/" : "";
32128
32589
  const isComponent = /^[A-Z][A-Za-z0-9]*\.(?:tsx|jsx)$/.test(base2);
32129
32590
  if (isComponent) return `${prefix}components/${base2}`;
@@ -32279,10 +32740,10 @@ async function observeCheck(check2, ctx, at) {
32279
32740
  }
32280
32741
 
32281
32742
  // src/api/fill.ts
32282
- var PLACEHOLDER2 = /\{([a-zA-Z]+)\}/g;
32743
+ var PLACEHOLDER3 = /\{([a-zA-Z]+)\}/g;
32283
32744
  function fill2(template, vars) {
32284
32745
  let missing = false;
32285
- const text = template.replace(PLACEHOLDER2, (_, name) => {
32746
+ const text = template.replace(PLACEHOLDER3, (_, name) => {
32286
32747
  const value = Object.hasOwn(vars, name) ? vars[name] : void 0;
32287
32748
  if (value !== void 0) return value;
32288
32749
  missing = true;
@@ -32410,7 +32871,7 @@ function unscored(scan) {
32410
32871
  }
32411
32872
  function loadCache2(root) {
32412
32873
  try {
32413
- return JSON.parse(readFileSync18(auraPath(root, CACHE), "utf8"));
32874
+ return JSON.parse(readFileSync19(auraPath(root, CACHE), "utf8"));
32414
32875
  } catch {
32415
32876
  return {};
32416
32877
  }
@@ -32541,7 +33002,7 @@ async function buildContext(root, options, config2) {
32541
33002
  const allTracked = await listTrackedFiles(root);
32542
33003
  const tracked = allTracked ? new Set([...allTracked].filter((f) => !ignores.some((re) => re.test(f)))) : null;
32543
33004
  const cache3 = /* @__PURE__ */ new Map();
32544
- const read2 = (f) => {
33005
+ const read3 = (f) => {
32545
33006
  const cached2 = cache3.get(f);
32546
33007
  if (cached2 !== void 0) return cached2;
32547
33008
  const text = readText(root, f);
@@ -32549,10 +33010,10 @@ async function buildContext(root, options, config2) {
32549
33010
  return text;
32550
33011
  };
32551
33012
  const pkg2 = readJson(root, "package.json");
32552
- const stack = detectStack(root, files, pkg2, read2);
32553
- const projects = detectProjects(root, files, read2, cfg.projects);
33013
+ const stack = detectStack(root, files, pkg2, read3);
33014
+ const projects = detectProjects(root, files, read3, cfg.projects);
32554
33015
  const project = combineProfiles(projects.map((p2) => p2.profile));
32555
- return { root, files, tracked, read: read2, pkg: pkg2, stack, projects, profile: repoProfile(projects), project, config: cfg, options };
33016
+ return { root, files, tracked, read: read3, pkg: pkg2, stack, projects, profile: repoProfile(projects), project, config: cfg, options };
32556
33017
  }
32557
33018
  function applyIgnores(result, config2) {
32558
33019
  if (config2.ignore.includes(result.checkId)) {
@@ -32614,9 +33075,9 @@ async function runScan(root, options, onProgress) {
32614
33075
  }
32615
33076
  async function latestScan(root) {
32616
33077
  const file2 = auraPath(root, "scan.json");
32617
- if (existsSync21(file2)) {
33078
+ if (existsSync23(file2)) {
32618
33079
  try {
32619
- const scan2 = JSON.parse(readFileSync19(file2, "utf8"));
33080
+ const scan2 = JSON.parse(readFileSync20(file2, "utf8"));
32620
33081
  const head = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
32621
33082
  const dirty = execFileSync("git", ["status", "--porcelain"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
32622
33083
  if (scan2.commit === head && !dirty && Date.now() - Date.parse(scan2.scannedAt) < 24 * 36e5) return scan2;
@@ -32629,21 +33090,21 @@ async function latestScan(root) {
32629
33090
  }
32630
33091
 
32631
33092
  // src/commands/fix.ts
32632
- import { existsSync as existsSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync5 } from "fs";
32633
- import path40 from "path";
33093
+ import { existsSync as existsSync26, readFileSync as readFileSync23, writeFileSync as writeFileSync5 } from "fs";
33094
+ import path43 from "path";
32634
33095
 
32635
33096
  // src/agent/tools.ts
32636
- import { existsSync as existsSync22, mkdirSync as mkdirSync4, readFileSync as readFileSync20, rmSync as rmSync4, statSync as statSync5, writeFileSync as writeFileSync4 } from "fs";
32637
- import path39 from "path";
33097
+ import { existsSync as existsSync24, mkdirSync as mkdirSync4, readFileSync as readFileSync21, rmSync as rmSync4, statSync as statSync5, writeFileSync as writeFileSync4 } from "fs";
33098
+ import path42 from "path";
32638
33099
  var MAX_OUTPUT = 12e3;
32639
33100
  var MAX_FILE_LINES = 800;
32640
33101
  var FORBIDDEN = /^(?:\.git|node_modules|\.next|\.vercel)(?:\/|$)|(?:^|\/)\.env(?:\.(?!example$|sample$|template$)[^/]+)?$/;
32641
33102
  function safePath(root, rel) {
32642
33103
  if (typeof rel !== "string" || !rel.trim()) throw new Error("A file path is required.");
32643
- const normalized = path39.posix.normalize(rel.replace(/\\/g, "/").replace(/^\.\//, ""));
32644
- if (normalized.startsWith("../") || normalized === ".." || path39.isAbsolute(normalized)) throw new Error(`Path is outside the repo: ${rel}`);
33104
+ const normalized = path42.posix.normalize(rel.replace(/\\/g, "/").replace(/^\.\//, ""));
33105
+ if (normalized.startsWith("../") || normalized === ".." || path42.isAbsolute(normalized)) throw new Error(`Path is outside the repo: ${rel}`);
32645
33106
  if (FORBIDDEN.test(normalized)) throw new Error(`Aura doesn\u2019t let the agent touch ${normalized}.`);
32646
- const full = path39.join(root, normalized);
33107
+ const full = path42.join(root, normalized);
32647
33108
  if (!isInside(root, full)) throw new Error(`Path is outside the repo: ${rel}`);
32648
33109
  return normalized;
32649
33110
  }
@@ -32664,10 +33125,10 @@ async function runTool(root, call, allowedCommands2) {
32664
33125
  }
32665
33126
  case "read_file": {
32666
33127
  const rel = safePath(root, input2.path);
32667
- const full = path39.join(root, rel);
32668
- if (!existsSync22(full)) return { output: `File not found: ${rel}` };
33128
+ const full = path42.join(root, rel);
33129
+ if (!existsSync24(full)) return { output: `File not found: ${rel}` };
32669
33130
  if (statSync5(full).size > 1e6) return { output: `${rel} is too large to read.` };
32670
- const lines = readFileSync20(full, "utf8").split("\n");
33131
+ const lines = readFileSync21(full, "utf8").split("\n");
32671
33132
  const start = Math.max(1, Number(input2.startLine) || 1);
32672
33133
  const end = Math.min(lines.length, Number(input2.endLine) || start + MAX_FILE_LINES - 1);
32673
33134
  const body = lines.slice(start - 1, end).map((l, i) => `${start + i}: ${l}`).join("\n");
@@ -32684,7 +33145,7 @@ async function runTool(root, call, allowedCommands2) {
32684
33145
  if (FORBIDDEN.test(f) || glob && !glob.test(f)) continue;
32685
33146
  let text;
32686
33147
  try {
32687
- text = readFileSync20(path39.join(root, f), "utf8");
33148
+ text = readFileSync21(path42.join(root, f), "utf8");
32688
33149
  } catch {
32689
33150
  continue;
32690
33151
  }
@@ -32703,14 +33164,14 @@ async function runTool(root, call, allowedCommands2) {
32703
33164
  output: "Refused: the content contains a [REDACTED:\u2026] placeholder. Secrets are hidden from you on purpose. Read the value from an environment variable (process.env.NAME) instead of writing it."
32704
33165
  };
32705
33166
  }
32706
- mkdirSync4(path39.dirname(path39.join(root, rel)), { recursive: true });
32707
- writeFileSync4(path39.join(root, rel), content2);
33167
+ mkdirSync4(path42.dirname(path42.join(root, rel)), { recursive: true });
33168
+ writeFileSync4(path42.join(root, rel), content2);
32708
33169
  return { output: `Wrote ${rel} (${content2.split("\n").length} lines).`, touched: rel };
32709
33170
  }
32710
33171
  case "delete_file": {
32711
33172
  const rel = safePath(root, input2.path);
32712
- if (!existsSync22(path39.join(root, rel))) return { output: `File not found: ${rel}` };
32713
- rmSync4(path39.join(root, rel));
33173
+ if (!existsSync24(path42.join(root, rel))) return { output: `File not found: ${rel}` };
33174
+ rmSync4(path42.join(root, rel));
32714
33175
  return { output: `Deleted ${rel}.`, touched: rel };
32715
33176
  }
32716
33177
  case "run_command": {
@@ -32726,15 +33187,15 @@ ${r.stderr}`.trim().slice(-6e3)}`;
32726
33187
  }
32727
33188
  case "finish": {
32728
33189
  const summary = String(input2.summary ?? "");
32729
- const list9 = (v) => Array.isArray(v) ? v.map(String) : [];
32730
- return { output: "Finished.", finish: { summary, testsRun: list9(input2.testsRun), risks: list9(input2.risks) } };
33190
+ const list11 = (v) => Array.isArray(v) ? v.map(String) : [];
33191
+ return { output: "Finished.", finish: { summary, testsRun: list11(input2.testsRun), risks: list11(input2.risks) } };
32731
33192
  }
32732
33193
  }
32733
33194
  }
32734
33195
 
32735
33196
  // src/api/explain.ts
32736
33197
  import { createHash as createHash5 } from "crypto";
32737
- import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
33198
+ import { existsSync as existsSync25, readFileSync as readFileSync22 } from "fs";
32738
33199
 
32739
33200
  // src/report.ts
32740
33201
  import pc3 from "picocolors";
@@ -33019,7 +33480,7 @@ var CACHE_FILE2 = "cache/explanations.json";
33019
33480
  var MAX_EXPLAIN = 25;
33020
33481
  function loadCache3(root) {
33021
33482
  try {
33022
- return JSON.parse(readFileSync21(auraPath(root, CACHE_FILE2), "utf8"));
33483
+ return JSON.parse(readFileSync22(auraPath(root, CACHE_FILE2), "utf8"));
33023
33484
  } catch {
33024
33485
  return {};
33025
33486
  }
@@ -33040,8 +33501,8 @@ function excerptFor(root, f, context = 4) {
33040
33501
  }
33041
33502
  function productContext(root) {
33042
33503
  const file2 = auraPath(root, "brief.md");
33043
- if (!existsSync23(file2)) return void 0;
33044
- return redact(readFileSync21(file2, "utf8")).slice(0, 1800);
33504
+ if (!existsSync25(file2)) return void 0;
33505
+ return redact(readFileSync22(file2, "utf8")).slice(0, 1800);
33045
33506
  }
33046
33507
  async function addExplanations(root, scan, client) {
33047
33508
  const cache3 = loadCache3(root);
@@ -33098,7 +33559,7 @@ function allowedCommands(root, pm, typescript) {
33098
33559
  const pkg2 = readJson(root, "package.json");
33099
33560
  const run2 = pmCommands(pm).run;
33100
33561
  const cmds = ["test", "lint", "typecheck", "build"].filter((s) => pkg2?.scripts?.[s]).map((s) => `${run2} ${s}`);
33101
- if (typescript && !pkg2?.scripts?.typecheck && existsSync24(path40.join(root, "node_modules/.bin/tsc"))) cmds.push(`${pmCommands(pm).exec} tsc --noEmit`);
33562
+ if (typescript && !pkg2?.scripts?.typecheck && existsSync26(path43.join(root, "node_modules/.bin/tsc"))) cmds.push(`${pmCommands(pm).exec} tsc --noEmit`);
33102
33563
  return cmds;
33103
33564
  }
33104
33565
  function taskFor(root, scan, action2, findings) {
@@ -33169,8 +33630,8 @@ async function untrackEnvFiles(root, findings) {
33169
33630
  const files = findings.filter((f) => f.checkId === "security.env-files").flatMap((f) => f.locations.slice(0, 1).map((l) => l.file));
33170
33631
  for (const f of files) await git(root, "rm", "-q", "--cached", "--", f);
33171
33632
  if (files.length) {
33172
- const gi = path40.join(root, ".gitignore");
33173
- const current2 = existsSync24(gi) ? readFileSync22(gi, "utf8") : "";
33633
+ const gi = path43.join(root, ".gitignore");
33634
+ const current2 = existsSync26(gi) ? readFileSync23(gi, "utf8") : "";
33174
33635
  if (!/^\.env\*?$/m.test(current2)) writeFileSync5(gi, `${current2.trimEnd()}
33175
33636
 
33176
33637
  # Environment files hold secrets
@@ -33185,10 +33646,10 @@ async function fixCommand(target, flags) {
33185
33646
  const started = Date.now();
33186
33647
  const report = { delivery: flags.agent ? "agent-task" : "agent-fix" };
33187
33648
  const actionId = { value: target };
33188
- const code = await fixAction(target, flags, report, actionId);
33649
+ const code2 = await fixAction(target, flags, report, actionId);
33189
33650
  const root = await findRepoRoot();
33190
33651
  await recordAction(root, loadConfig(root), actionId.value, report, started, flags.via ?? "command");
33191
- return code;
33652
+ return code2;
33192
33653
  }
33193
33654
  async function fixAction(target, flags, report, resolvedId) {
33194
33655
  const root = await findRepoRoot();
@@ -33402,25 +33863,25 @@ The plan is in ${large.file}.`);
33402
33863
  }
33403
33864
 
33404
33865
  // src/commands/inventory.ts
33405
- import { readFileSync as readFileSync23 } from "fs";
33866
+ import { readFileSync as readFileSync24 } from "fs";
33406
33867
  function accountState(root, services) {
33407
33868
  const inv = loadInventory(root);
33408
- const read2 = Object.fromEntries(
33869
+ const read3 = Object.fromEntries(
33409
33870
  Object.entries(loadSnapshots(root)).map(([id, snap]) => [id, Object.values(snap.settings).every((state) => state !== "off")])
33410
33871
  );
33411
33872
  return {
33412
33873
  inventoryComplete: services.every((s) => inv.services[s]?.owner) && Boolean(inv.githubOrg),
33413
33874
  secured: Object.fromEntries(
33414
- [.../* @__PURE__ */ new Set([...Object.keys(read2), ...Object.keys(inv.secured ?? {}), "github"])].map((id) => [
33875
+ [.../* @__PURE__ */ new Set([...Object.keys(read3), ...Object.keys(inv.secured ?? {}), "github"])].map((id) => [
33415
33876
  id,
33416
- Boolean(read2[id] || inv.secured?.[id] || id === "github" && inv.githubSecurity)
33877
+ Boolean(read3[id] || inv.secured?.[id] || id === "github" && inv.githubSecurity)
33417
33878
  ])
33418
33879
  )
33419
33880
  };
33420
33881
  }
33421
33882
  function loadInventory(root) {
33422
33883
  try {
33423
- return JSON.parse(readFileSync23(auraPath(root, "inventory.json"), "utf8"));
33884
+ return JSON.parse(readFileSync24(auraPath(root, "inventory.json"), "utf8"));
33424
33885
  } catch {
33425
33886
  return { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), services: {} };
33426
33887
  }
@@ -33655,13 +34116,13 @@ ${pc.bold(" Left for you")}`);
33655
34116
  }
33656
34117
 
33657
34118
  // src/commands/guide.ts
33658
- import { readdirSync as readdirSync6, readFileSync as readFileSync24 } from "fs";
33659
- import path41 from "path";
34119
+ import { readdirSync as readdirSync6, readFileSync as readFileSync25 } from "fs";
34120
+ import path44 from "path";
33660
34121
  import YAML6 from "yaml";
33661
34122
  function loadGuides() {
33662
- const dir = path41.join(templatesDir(), "guides");
34123
+ const dir = path44.join(templatesDir(), "guides");
33663
34124
  return readdirSync6(dir).filter((f) => f.endsWith(".md")).map((f) => {
33664
- const text = readFileSync24(path41.join(dir, f), "utf8");
34125
+ const text = readFileSync25(path44.join(dir, f), "utf8");
33665
34126
  const fm = text.match(/^---\n([\s\S]*?)\n---\n?/);
33666
34127
  const yaml = fm?.[1];
33667
34128
  const meta3 = yaml !== void 0 ? YAML6.parse(yaml) : {};
@@ -33694,10 +34155,10 @@ var LOCKFILES2 = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock"
33694
34155
  async function applyCommand(actionId, flags) {
33695
34156
  const started = Date.now();
33696
34157
  const report = { delivery: "pr" };
33697
- const code = await applyAction(actionId, flags, report);
34158
+ const code2 = await applyAction(actionId, flags, report);
33698
34159
  const root = await findRepoRoot();
33699
34160
  await recordAction(root, loadConfig(root), actionId, report, started, flags.via ?? "command");
33700
- return code;
34161
+ return code2;
33701
34162
  }
33702
34163
  async function applyAction(actionId, flags, report) {
33703
34164
  const root = await findRepoRoot();
@@ -33714,7 +34175,9 @@ async function applyAction(actionId, flags, report) {
33714
34175
  if (delivery === "agent-fix") {
33715
34176
  if (fellBack)
33716
34177
  say.info(
33717
- pc.dim(`Aura only opens template PRs for stacks it recognizes (a Next.js app at the repo root). Handing "${actionId}" to Aura\u2019s agent instead.`)
34178
+ pc.dim(
34179
+ `Aura\u2019s template for "${actionId}" doesn\u2019t fit this project (templates cover a Next.js app, or a Vite + React app, at the repo root). Handing it to Aura\u2019s agent instead.`
34180
+ )
33718
34181
  );
33719
34182
  return fixCommand(actionId, { yes: flags.yes, dryRun: flags.dryRun, pr: flags.pr, approved: flags.approved, via: flags.via });
33720
34183
  }
@@ -33764,7 +34227,7 @@ async function applyAction(actionId, flags, report) {
33764
34227
  console.log(`
33765
34228
  ${pc.bold(title2)}`);
33766
34229
  for (const c of changes.changes)
33767
- console.log(` ${c.content === null ? pc.red("delete") : existsSync25(path42.join(root, c.path)) ? pc.yellow("update") : pc.green("add ")} ${c.path}`);
34230
+ console.log(` ${c.content === null ? pc.red("delete") : existsSync27(path45.join(root, c.path)) ? pc.yellow("update") : pc.green("add ")} ${c.path}`);
33768
34231
  if (changes.devDeps?.length) console.log(` ${pc.cyan("install")} ${changes.devDeps.join(", ")} ${pc.dim(`(${pm.addDev})`)}`);
33769
34232
  for (const cmd of changes.commands ?? []) console.log(` ${pc.cyan("run ")} ${cmd.join(" ")}`);
33770
34233
  };
@@ -33820,8 +34283,8 @@ The plan is in ${large.file}.`);
33820
34283
  return 0;
33821
34284
  }
33822
34285
  const chunkPlan = plan;
33823
- const tsc = path42.join(root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
33824
- const checkTypes = plan.verifyTypes && scan.stack.typescript && existsSync25(tsc);
34286
+ const tsc = path45.join(root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
34287
+ const checkTypes = plan.verifyTypes && scan.stack.typescript && existsSync27(tsc);
33825
34288
  const typeErrors = async () => {
33826
34289
  const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: root, timeoutMs: 3e5 });
33827
34290
  return (r.stdout.match(/error TS\d+/g) ?? []).length;
@@ -33936,17 +34399,17 @@ async function undoPlan(root, plan) {
33936
34399
  for (const file2 of touched) {
33937
34400
  const inHead = (await git(root, "cat-file", "-e", `HEAD:${file2}`)).code === 0;
33938
34401
  if (inHead) await git(root, "checkout", "HEAD", "--", file2);
33939
- else if (plan.changes.some((c) => c.path === file2)) rmSync5(path42.join(root, file2), { force: true });
34402
+ else if (plan.changes.some((c) => c.path === file2)) rmSync5(path45.join(root, file2), { force: true });
33940
34403
  }
33941
34404
  }
33942
34405
 
33943
34406
  // src/commands/init.ts
33944
- import { existsSync as existsSync27, readFileSync as readFileSync28 } from "fs";
34407
+ import { existsSync as existsSync29, readFileSync as readFileSync29 } from "fs";
33945
34408
  import { spawnSync as spawnSync3 } from "child_process";
33946
- import path47 from "path";
34409
+ import path50 from "path";
33947
34410
 
33948
34411
  // src/brief.ts
33949
- import path43 from "path";
34412
+ import path46 from "path";
33950
34413
  function repoSummary(ctx) {
33951
34414
  const routes2 = ctx.files.filter((f) => /^(?:src\/)?app\/(?:.+\/)?(?:page|route)\.[cm]?[jt]sx?$/.test(f) || /^(?:src\/)?pages\/.+\.[cm]?[jt]sx?$/.test(f)).map(
33952
34415
  (f) => "/" + f.replace(/^(?:src\/)?(?:app|pages)\//, "").replace(/(?:^|\/)(?:page|route|index)\.[cm]?[jt]sx?$|\.[cm]?[jt]sx?$/, "").replace(/\([^)]+\)\//g, "")
@@ -33963,7 +34426,7 @@ function repoSummary(ctx) {
33963
34426
  const readme2 = ctx.files.find((f) => /^readme\.mdx?$/i.test(f));
33964
34427
  const readmeText = readme2 ? ctx.read(readme2) ?? "" : "";
33965
34428
  return {
33966
- name: ctx.pkg?.name ?? path43.basename(ctx.root),
34429
+ name: ctx.pkg?.name ?? path46.basename(ctx.root),
33967
34430
  packageJson: ctx.pkg ? {
33968
34431
  name: ctx.pkg.name,
33969
34432
  description: ctx.pkg.description,
@@ -34062,10 +34525,10 @@ function banner(colors = pc4.isColorSupported) {
34062
34525
  // src/api/connect.ts
34063
34526
  import { spawn as spawn2 } from "child_process";
34064
34527
  import { constants, generateKeyPairSync, privateDecrypt } from "crypto";
34065
- import { existsSync as existsSync26, mkdirSync as mkdirSync5, readFileSync as readFileSync25, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "fs";
34528
+ import { existsSync as existsSync28, mkdirSync as mkdirSync5, readFileSync as readFileSync26, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "fs";
34066
34529
  import os5 from "os";
34067
- import path44 from "path";
34068
- var pendingFile = () => path44.join(os5.homedir(), ".aura", "pending-connect.json");
34530
+ import path47 from "path";
34531
+ var pendingFile = () => path47.join(os5.homedir(), ".aura", "pending-connect.json");
34069
34532
  async function postJson(apiUrl, route, body) {
34070
34533
  let res;
34071
34534
  try {
@@ -34084,14 +34547,14 @@ async function postJson(apiUrl, route, body) {
34084
34547
  }
34085
34548
  function loadPending(apiUrl, now) {
34086
34549
  try {
34087
- const p2 = JSON.parse(readFileSync25(pendingFile(), "utf8"));
34550
+ const p2 = JSON.parse(readFileSync26(pendingFile(), "utf8"));
34088
34551
  if (p2.apiUrl === apiUrl && Date.parse(p2.expiresAt) - now > 6e4) return p2;
34089
34552
  } catch {
34090
34553
  }
34091
34554
  return null;
34092
34555
  }
34093
34556
  function clearPendingConnect() {
34094
- if (existsSync26(pendingFile())) rmSync6(pendingFile(), { force: true });
34557
+ if (existsSync28(pendingFile())) rmSync6(pendingFile(), { force: true });
34095
34558
  }
34096
34559
  async function startOrResumeConnect(apiUrl, now = Date.now()) {
34097
34560
  const existing = loadPending(apiUrl, now);
@@ -34103,7 +34566,7 @@ async function startOrResumeConnect(apiUrl, now = Date.now()) {
34103
34566
  });
34104
34567
  const res = ConnectStartResponse.parse(await postJson(apiUrl, "connect", { publicKey }));
34105
34568
  const pending2 = { apiUrl, privateKey, ...res };
34106
- mkdirSync5(path44.dirname(pendingFile()), { recursive: true });
34569
+ mkdirSync5(path47.dirname(pendingFile()), { recursive: true });
34107
34570
  writeFileSync6(pendingFile(), `${JSON.stringify(pending2, null, 2)}
34108
34571
  `, { mode: 384 });
34109
34572
  return { ...pending2, resumed: false };
@@ -34256,9 +34719,9 @@ function installFallback(result, platform = process.platform) {
34256
34719
  // src/core/claude-plugin.ts
34257
34720
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
34258
34721
  import os6 from "os";
34259
- import path45 from "path";
34722
+ import path48 from "path";
34260
34723
  var CLAUDE_PLUGIN_ID = "aura@aura";
34261
- var marketplaceDir = () => path45.join(os6.homedir(), ".aura", "claude-marketplace");
34724
+ var marketplaceDir = () => path48.join(os6.homedir(), ".aura", "claude-marketplace");
34262
34725
  var MARKETPLACE = {
34263
34726
  name: "aura",
34264
34727
  description: "Aura for Claude Code",
@@ -34273,8 +34736,8 @@ var MARKETPLACE = {
34273
34736
  };
34274
34737
  function writeMarketplace() {
34275
34738
  const dir = marketplaceDir();
34276
- mkdirSync6(path45.join(dir, ".claude-plugin"), { recursive: true });
34277
- writeFileSync7(path45.join(dir, ".claude-plugin", "marketplace.json"), `${JSON.stringify(MARKETPLACE, null, 2)}
34739
+ mkdirSync6(path48.join(dir, ".claude-plugin"), { recursive: true });
34740
+ writeFileSync7(path48.join(dir, ".claude-plugin", "marketplace.json"), `${JSON.stringify(MARKETPLACE, null, 2)}
34278
34741
  `);
34279
34742
  return dir;
34280
34743
  }
@@ -34330,7 +34793,7 @@ var AUTH_PACKAGES = [
34330
34793
  [/^@kinde-oss\//, "Kinde"],
34331
34794
  [/^@auth0\//, "Auth0"]
34332
34795
  ];
34333
- var words = (list9) => new RegExp(`(?<![a-z])(?:${list9})(?![a-z])`, "i");
34796
+ var words = (list11) => new RegExp(`(?<![a-z])(?:${list11})(?![a-z])`, "i");
34334
34797
  var SENSITIVE = [
34335
34798
  ["health", words("patients?|diagnos[a-z]*|medical|medications?|prescriptions?|symptoms?|health_records?"), "health"],
34336
34799
  ["legal", words("case_number|lawsuits?|attorneys?|legal_matters?|court[a-z]*"), "legal"],
@@ -34340,7 +34803,7 @@ function schemaText(ctx) {
34340
34803
  const files = ctx.files.filter((f) => /\.sql$|schema\.prisma$|(?:^|\/)schema\.[cm]?[jt]s$|(?:^|\/)db\/schema\//.test(f) && !/node_modules\//.test(f));
34341
34804
  return files.slice(0, 200).map((f) => ctx.read(f) ?? "").join("\n");
34342
34805
  }
34343
- var matchPackage = (deps2, list9) => list9.find(([re]) => deps2.some((d) => re.test(d)))?.[1];
34806
+ var matchPackage = (deps2, list11) => list11.find(([re]) => deps2.some((d) => re.test(d)))?.[1];
34344
34807
  function inferAnswers(ctx) {
34345
34808
  const deps2 = Object.keys(allDeps(ctx.pkg));
34346
34809
  const payment = matchPackage(deps2, PAYMENT_PACKAGES);
@@ -34391,13 +34854,13 @@ function describeInferred(inferred) {
34391
34854
  // src/commands/scan.ts
34392
34855
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "fs";
34393
34856
  import os7 from "os";
34394
- import path46 from "path";
34857
+ import path49 from "path";
34395
34858
 
34396
34859
  // src/history.ts
34397
- import { readFileSync as readFileSync26 } from "fs";
34860
+ import { readFileSync as readFileSync27 } from "fs";
34398
34861
  function loadHistory(root) {
34399
34862
  try {
34400
- const data = JSON.parse(readFileSync26(auraPath(root, "history.json"), "utf8"));
34863
+ const data = JSON.parse(readFileSync27(auraPath(root, "history.json"), "utf8"));
34401
34864
  return Array.isArray(data) ? data : [];
34402
34865
  } catch {
34403
34866
  return [];
@@ -34459,7 +34922,7 @@ async function pickAndRunSteps(steps, limit = 10) {
34459
34922
  console.log(`
34460
34923
  ${pc.magenta("\u2726")} ${pc.bold(step.title)}`);
34461
34924
  const run2 = stepRun(step, hasKey);
34462
- const code = run2.kind === "apply" ? (
34925
+ const code2 = run2.kind === "apply" ? (
34463
34926
  // Settings changes always ask first, even when picked here; pull requests don't need to.
34464
34927
  await applyCommand(run2.actionId, { yes: !connectorForAction(run2.actionId), via: "checklist" })
34465
34928
  ) : run2.kind === "fix" ? await fixCommand(run2.actionId, run2.withOwnAgent ? { agent: true, via: "checklist" } : { yes: true, via: "checklist" }) : run2.kind === "guide" ? await guideCommand(run2.topic, {}) : await inventoryCommand({});
@@ -34473,7 +34936,7 @@ ${pc.magenta("\u2726")} ${pc.bold(step.title)}`);
34473
34936
  outcome: "shown"
34474
34937
  });
34475
34938
  }
34476
- results.push({ title: step.title, ok: code === 0 });
34939
+ results.push({ title: step.title, ok: code2 === 0 });
34477
34940
  }
34478
34941
  if (results.length > 1) {
34479
34942
  console.log(`
@@ -34484,12 +34947,12 @@ ${pc.bold(" Done")}`);
34484
34947
  }
34485
34948
 
34486
34949
  // src/connectors/refresh.ts
34487
- import { readFileSync as readFileSync27 } from "fs";
34950
+ import { readFileSync as readFileSync28 } from "fs";
34488
34951
  async function refreshToolSettings(root) {
34489
34952
  if (process.env.AURA_NO_CONNECTORS) return;
34490
34953
  let previous = null;
34491
34954
  try {
34492
- previous = JSON.parse(readFileSync27(auraPath(root, "scan.json"), "utf8"));
34955
+ previous = JSON.parse(readFileSync28(auraPath(root, "scan.json"), "utf8"));
34493
34956
  } catch {
34494
34957
  }
34495
34958
  const snapshots = loadSnapshots(root);
@@ -34513,8 +34976,8 @@ function reportScoring(outcome, quiet, ci) {
34513
34976
  if (!quiet) say.warn(text);
34514
34977
  }
34515
34978
  async function scanRef(root, ref, opts) {
34516
- const dir = mkdtempSync3(path46.join(os7.tmpdir(), "aura-base-"));
34517
- const wt = path46.join(dir, "tree");
34979
+ const dir = mkdtempSync3(path49.join(os7.tmpdir(), "aura-base-"));
34980
+ const wt = path49.join(dir, "tree");
34518
34981
  const add = await run("git", ["worktree", "add", "--detach", wt, ref], { cwd: root });
34519
34982
  if (add.code !== 0) {
34520
34983
  rmSync7(dir, { recursive: true, force: true });
@@ -34617,7 +35080,7 @@ async function scanCommand(flags) {
34617
35080
  // src/commands/init.ts
34618
35081
  var AGENT_IDS2 = ["claude", "codex", "cursor", "pi"];
34619
35082
  function detectAgents(root) {
34620
- return detectAgentsFrom((rel) => existsSync27(path47.join(root, rel)));
35083
+ return detectAgentsFrom((rel) => existsSync29(path50.join(root, rel)));
34621
35084
  }
34622
35085
  function openEditor(file2) {
34623
35086
  const fallback = process.platform === "win32" ? "notepad" : spawnSync3("sh", ["-c", "command -v nano"]).status === 0 ? "nano" : "vi";
@@ -34733,13 +35196,13 @@ async function initCommand(flags) {
34733
35196
  let brief2 = null;
34734
35197
  let keptBrief = false;
34735
35198
  if (flags.briefFile) {
34736
- brief2 = readFileSync28(flags.briefFile, "utf8");
34737
- } else if (!flags.answers && existsSync27(briefPath) && !(tty && unwrap(await p.confirm({ message: "A product brief already exists. Draft a new one?", initialValue: false })))) {
35199
+ brief2 = readFileSync29(flags.briefFile, "utf8");
35200
+ } else if (!flags.answers && existsSync29(briefPath) && !(tty && unwrap(await p.confirm({ message: "A product brief already exists. Draft a new one?", initialValue: false })))) {
34738
35201
  keptBrief = true;
34739
35202
  } else {
34740
35203
  let answers = null;
34741
35204
  if (flags.answers) {
34742
- answers = OnboardingAnswersSchema.parse(JSON.parse(readFileSync28(flags.answers, "utf8")));
35205
+ answers = OnboardingAnswersSchema.parse(JSON.parse(readFileSync29(flags.answers, "utf8")));
34743
35206
  } else if (tty) {
34744
35207
  const inferred = inferAnswers(ctx);
34745
35208
  p.note(describeInferred(inferred).join("\n"), "Here\u2019s what Aura found about your app");
@@ -34891,7 +35354,7 @@ ${pc.magenta("\u2726")} ${pc.bold("Give the user the aura command")}`);
34891
35354
  }
34892
35355
 
34893
35356
  // src/commands/summary.ts
34894
- import path48 from "path";
35357
+ import path51 from "path";
34895
35358
  async function activity(root, since) {
34896
35359
  const log = await git(root, "log", `--since=${since.toISOString()}`, "--no-merges", "--format=%H%x09%an%x09%s");
34897
35360
  const commits = log.stdout.split("\n").filter(Boolean).slice(0, 300).map((l) => {
@@ -34953,7 +35416,7 @@ async function summaryCommand(flags) {
34953
35416
  const beforeIds = new Set(before ? allFindings(before).map((f) => f.id) : []);
34954
35417
  const nowIds = new Set(allFindings(now).map((f) => f.id));
34955
35418
  const req = {
34956
- repo: path48.basename(root),
35419
+ repo: path51.basename(root),
34957
35420
  since: since.toISOString(),
34958
35421
  until: until.toISOString(),
34959
35422
  product: productContext(root),
@@ -34998,15 +35461,15 @@ ${body}`);
34998
35461
  }
34999
35462
 
35000
35463
  // src/commands/update.ts
35001
- import { existsSync as existsSync28, readFileSync as readFileSync30 } from "fs";
35002
- import path50 from "path";
35464
+ import { existsSync as existsSync30, readFileSync as readFileSync31 } from "fs";
35465
+ import path53 from "path";
35003
35466
 
35004
35467
  // src/core/update-check.ts
35005
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync29, writeFileSync as writeFileSync9 } from "fs";
35468
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync30, writeFileSync as writeFileSync9 } from "fs";
35006
35469
  import os8 from "os";
35007
- import path49 from "path";
35470
+ import path52 from "path";
35008
35471
  var DAY_MS = 24 * 60 * 60 * 1e3;
35009
- var cacheFile = () => path49.join(os8.homedir(), ".aura", "update-check.json");
35472
+ var cacheFile = () => path52.join(os8.homedir(), ".aura", "update-check.json");
35010
35473
  function isNewer(a, b) {
35011
35474
  if (!/^\d+\.\d+\.\d+$/.test(a) || !/^\d+\.\d+\.\d+$/.test(b)) return false;
35012
35475
  const x = a.split(".").map(Number);
@@ -35031,13 +35494,13 @@ async function newerVersion(opts = {}) {
35031
35494
  const now = opts.now ?? Date.now();
35032
35495
  let latest = null;
35033
35496
  try {
35034
- const cached2 = JSON.parse(readFileSync29(cacheFile(), "utf8"));
35497
+ const cached2 = JSON.parse(readFileSync30(cacheFile(), "utf8"));
35035
35498
  if (now - cached2.checkedAt < DAY_MS) latest = cached2.latest;
35036
35499
  else throw new Error("stale");
35037
35500
  } catch {
35038
35501
  latest = await (opts.fetchLatest ?? fetchLatest)();
35039
35502
  try {
35040
- mkdirSync7(path49.dirname(cacheFile()), { recursive: true });
35503
+ mkdirSync7(path52.dirname(cacheFile()), { recursive: true });
35041
35504
  writeFileSync9(cacheFile(), JSON.stringify({ checkedAt: now, latest }));
35042
35505
  } catch {
35043
35506
  }
@@ -35055,7 +35518,7 @@ function updateNotice(latest, argv1 = process.argv[1] ?? "") {
35055
35518
  }
35056
35519
 
35057
35520
  // src/commands/update.ts
35058
- function howAuraRuns(argv1, root, paths = path50) {
35521
+ function howAuraRuns(argv1, root, paths = path53) {
35059
35522
  if (/[\\/]_npx[\\/]/.test(argv1)) return "npx";
35060
35523
  const rel = root && argv1 ? paths.relative(paths.join(root, "node_modules"), argv1) : "..";
35061
35524
  return rel.startsWith("..") || paths.isAbsolute(rel) ? "global" : "project";
@@ -35073,9 +35536,9 @@ function newAura(how, version2) {
35073
35536
  return ["aura"];
35074
35537
  }
35075
35538
  function workflowVersion(root) {
35076
- const file2 = path50.join(root, ".github", "workflows", "aura.yml");
35077
- if (!existsSync28(file2)) return null;
35078
- return readFileSync30(file2, "utf8").match(/getaura@(\d+\.\d+\.\d+)/)?.[1] ?? null;
35539
+ const file2 = path53.join(root, ".github", "workflows", "aura.yml");
35540
+ if (!existsSync30(file2)) return null;
35541
+ return readFileSync31(file2, "utf8").match(/getaura@(\d+\.\d+\.\d+)/)?.[1] ?? null;
35079
35542
  }
35080
35543
  function mentionWorkflow(root, version2) {
35081
35544
  const pinned = root ? workflowVersion(root) : null;
@@ -35083,7 +35546,7 @@ function mentionWorkflow(root, version2) {
35083
35546
  say.info(`Your GitHub check still runs Aura ${pinned}. To move it to ${version2}, run: aura apply add-aura-workflow (it opens a pull request).`);
35084
35547
  }
35085
35548
  async function refreshRepoFiles(root) {
35086
- if (!existsSync28(auraPath(root, "config.json"))) {
35549
+ if (!existsSync30(auraPath(root, "config.json"))) {
35087
35550
  say.dim("Aura isn\u2019t set up in this repo, so there\u2019s nothing here to refresh.");
35088
35551
  return 0;
35089
35552
  }
@@ -35150,8 +35613,8 @@ async function updateCommand(flags, deps2 = { fetchLatest, run, argv1: process.a
35150
35613
  }
35151
35614
 
35152
35615
  // src/commands/pr.ts
35153
- import { existsSync as existsSync29, readFileSync as readFileSync31, statSync as statSync6 } from "fs";
35154
- import path51 from "path";
35616
+ import { existsSync as existsSync31, readFileSync as readFileSync32, statSync as statSync6 } from "fs";
35617
+ import path54 from "path";
35155
35618
 
35156
35619
  // src/actions/pr-plan.ts
35157
35620
  var PR_MAX_LINES = 400;
@@ -35348,7 +35811,7 @@ function parseStatus(out) {
35348
35811
  }
35349
35812
  return entries;
35350
35813
  }
35351
- function projectOf(root, file2, exists2 = (rel) => existsSync29(path51.join(root, rel))) {
35814
+ function projectOf(root, file2, exists2 = (rel) => existsSync31(path54.join(root, rel))) {
35352
35815
  const parts = file2.split("/").slice(0, -1);
35353
35816
  for (let n = parts.length; n > 0; n--) {
35354
35817
  const dir = parts.slice(0, n).join("/");
@@ -35365,11 +35828,11 @@ async function changedFiles(root) {
35365
35828
  if (file2) lines.set(file2, (Number(added) || 0) + (Number(removed) || 0));
35366
35829
  }
35367
35830
  return parseStatus(status.stdout).map(({ path: file2, from }) => {
35368
- const full = path51.join(root, file2);
35369
- const bytes = existsSync29(full) ? statSync6(full).size : 0;
35831
+ const full = path54.join(root, file2);
35832
+ const bytes = existsSync31(full) ? statSync6(full).size : 0;
35370
35833
  let count3 = lines.get(file2);
35371
35834
  if (count3 === void 0 && bytes && bytes < 2e6) {
35372
- const text = readFileSync31(full, "utf8");
35835
+ const text = readFileSync32(full, "utf8");
35373
35836
  count3 = text.includes("\0") ? 0 : text.split("\n").length;
35374
35837
  }
35375
35838
  return { path: file2, ...from ? { from } : {}, lines: count3 ?? 0, bytes, project: projectOf(root, file2) };
@@ -35379,20 +35842,20 @@ async function filesWithSecrets(root, files, ignores) {
35379
35842
  const hits = [];
35380
35843
  for (const file2 of files) {
35381
35844
  if (ignores.some((re) => re.test(file2))) continue;
35382
- const full = path51.join(root, file2);
35383
- if (!existsSync29(full) || statSync6(full).size > 2e6) continue;
35845
+ const full = path54.join(root, file2);
35846
+ if (!existsSync31(full) || statSync6(full).size > 2e6) continue;
35384
35847
  const tracked = (await git(root, "cat-file", "-e", `HEAD:${file2}`)).code === 0;
35385
- const added = tracked ? (await git(root, "diff", "-U0", "--no-color", "HEAD", "--", file2)).stdout.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).map((l) => l.slice(1)) : readFileSync31(full, "utf8").split("\n");
35386
- if (added.some((l) => findSecrets(l).some((m) => m.confidence === "high"))) hits.push(file2);
35848
+ const added = tracked ? (await git(root, "diff", "-U0", "--no-color", "HEAD", "--", file2)).stdout.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).map((l) => l.slice(1)) : readFileSync32(full, "utf8").split("\n");
35849
+ if (added.some((l) => findLeakedSecrets(l).some((m) => m.confidence === "high"))) hits.push(file2);
35387
35850
  }
35388
35851
  return hits;
35389
35852
  }
35390
35853
  function loadNotes(root, file2) {
35391
35854
  if (!file2) return {};
35392
- const full = path51.resolve(root, file2);
35393
- if (!isInside(root, full) || !existsSync29(full)) return `Couldn\u2019t find the notes file ${file2}.`;
35855
+ const full = path54.resolve(root, file2);
35856
+ if (!isInside(root, full) || !existsSync31(full)) return `Couldn\u2019t find the notes file ${file2}.`;
35394
35857
  try {
35395
- const parsed = Notes.safeParse(JSON.parse(readFileSync31(full, "utf8")));
35858
+ const parsed = Notes.safeParse(JSON.parse(readFileSync32(full, "utf8")));
35396
35859
  return parsed.success ? parsed.data : `The notes file ${file2} isn\u2019t in the expected shape: { "<group id>": { "title", "summary", "check": [] } }.`;
35397
35860
  } catch {
35398
35861
  return `The notes file ${file2} isn\u2019t valid JSON.`;
@@ -35548,7 +36011,7 @@ Finish by hand with these two commands:
35548
36011
 
35549
36012
  // src/commands/simple.ts
35550
36013
  import { execFileSync as execFileSync2 } from "child_process";
35551
- import { existsSync as existsSync30 } from "fs";
36014
+ import { existsSync as existsSync32 } from "fs";
35552
36015
  async function nextCommand(flags) {
35553
36016
  const root = await findRepoRoot();
35554
36017
  const scan = await latestScan(root);
@@ -35607,7 +36070,7 @@ async function kindSetting(root, config2, value, project) {
35607
36070
  const paths = profile.projects.map((p2) => p2.path);
35608
36071
  const at = project ?? (paths.length === 1 ? paths[0] : void 0);
35609
36072
  if (!at) return `This repo has ${paths.length} projects, so say which one, for example: aura config kind ${value} ${paths.find((p2) => p2 !== ".")}`;
35610
- if (!existsSync30(packageFolder(root, normDir(at)))) return `There is no folder "${at}" in this repo.`;
36073
+ if (!existsSync32(packageFolder(root, normDir(at)))) return `There is no folder "${at}" in this repo.`;
35611
36074
  config2.projects = setKindOverride(config2.projects, at, kind);
35612
36075
  return "saved";
35613
36076
  }
@@ -35680,7 +36143,7 @@ async function checkStagedCommand() {
35680
36143
  for (const line of diff.split("\n")) {
35681
36144
  if (line.startsWith("+++ ")) file2 = line.replace(/^\+\+\+ (?:b\/)?/, "");
35682
36145
  else if (line.startsWith("+") && !line.startsWith("+++") && !ignored(file2)) {
35683
- for (const m of findSecrets(line.slice(1))) if (m.confidence === "high") hits.push(`${file2}: ${m.label} (${maskSecret(m.value)})`);
36146
+ for (const m of findLeakedSecrets(line.slice(1))) if (m.confidence === "high") hits.push(`${file2}: ${m.label} (${maskSecret(m.value)})`);
35684
36147
  }
35685
36148
  }
35686
36149
  const envFiles2 = execFileSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=A"], { cwd: root, encoding: "utf8" }).split("\n").filter((f) => /(?:^|\/)\.env(?:\.[^/]+)?$/.test(f) && !/\.(?:example|sample|template)$/.test(f) && !ignored(f));