auditai-scan 0.7.4 → 0.7.5
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/auditai-scan.mjs +32 -3
- package/package.json +1 -1
package/dist/auditai-scan.mjs
CHANGED
|
@@ -7916,6 +7916,31 @@ function readsOnlyPublicRows(fn2, tables, fromMigrationsOnly) {
|
|
|
7916
7916
|
});
|
|
7917
7917
|
}
|
|
7918
7918
|
var API_ROLES2 = /* @__PURE__ */ new Set(["anon", "authenticated", "public"]);
|
|
7919
|
+
var NAMES_A_PERSON = /(^|_)(user|uid|profile|member|person|owner|customer|employee|student|patient|email)(_?id)?$/i;
|
|
7920
|
+
var CARRIES_CREDENTIAL = /(^|_)(pin|password|passwd|secret|token|otp|code|hash)$/i;
|
|
7921
|
+
function isCatalogRelation(name) {
|
|
7922
|
+
return name.startsWith("information_schema.") || name.startsWith("pg_catalog.") || /^pg_[a-z_]+$/.test(name);
|
|
7923
|
+
}
|
|
7924
|
+
function writesNothing(fn2, byName, seen = /* @__PURE__ */ new Set()) {
|
|
7925
|
+
if ((fn2.writes?.length ?? 0) > 0) return false;
|
|
7926
|
+
if (seen.has(fn2.name)) return true;
|
|
7927
|
+
seen.add(fn2.name);
|
|
7928
|
+
return (fn2.calls ?? []).every((c) => {
|
|
7929
|
+
const callee = byName.get(c);
|
|
7930
|
+
return callee !== void 0 && writesNothing(callee, byName, seen);
|
|
7931
|
+
});
|
|
7932
|
+
}
|
|
7933
|
+
function narrowResult(fn2, byName) {
|
|
7934
|
+
if (!writesNothing(fn2, byName)) return null;
|
|
7935
|
+
if (fn2.returns === "boolean") {
|
|
7936
|
+
if (fn2.params === void 0 && fn2.args !== "") return null;
|
|
7937
|
+
const about = (fn2.params ?? []).map((p) => p.name);
|
|
7938
|
+
return about.some((n) => NAMES_A_PERSON.test(n) || CARRIES_CREDENTIAL.test(n)) ? null : "boolean";
|
|
7939
|
+
}
|
|
7940
|
+
const read = fn2.tables ?? [];
|
|
7941
|
+
if (fn2.returns !== "void" && read.length > 0 && read.every(isCatalogRelation)) return "catalog";
|
|
7942
|
+
return null;
|
|
7943
|
+
}
|
|
7919
7944
|
var securityDefinerFunctionWithoutCallerCheck = {
|
|
7920
7945
|
id: "supabase.security-definer-function-without-caller-check",
|
|
7921
7946
|
title: "SECURITY DEFINER function without a caller check",
|
|
@@ -7933,6 +7958,7 @@ var securityDefinerFunctionWithoutCallerCheck = {
|
|
|
7933
7958
|
}
|
|
7934
7959
|
const tables = new Map(ctx.model.tables.map((t) => [t.table, t]));
|
|
7935
7960
|
const hasMigrations = ctx.model.files.some((f) => MIGRATION_SQL.test(f));
|
|
7961
|
+
const byName = new Map(fns.map((f) => [f.name, f]));
|
|
7936
7962
|
const out = [];
|
|
7937
7963
|
for (const fn2 of fns) {
|
|
7938
7964
|
if (!fn2.securityDefiner || fn2.checksCaller) continue;
|
|
@@ -7941,6 +7967,8 @@ var securityDefinerFunctionWithoutCallerCheck = {
|
|
|
7941
7967
|
const roles = fn2.grantedTo.filter((r) => API_ROLES2.has(r));
|
|
7942
7968
|
if (roles.length === 0) continue;
|
|
7943
7969
|
if (readsOnlyPublicRows(fn2, tables, hasMigrations)) continue;
|
|
7970
|
+
const narrow = narrowResult(fn2, byName);
|
|
7971
|
+
const narrowNote = narrow === "boolean" ? " It changes nothing and returns only true or false, so it is reported as medium: a yes/no answer about ids the caller supplies can still confirm that a record exists or that a user holds a role." : narrow === "catalog" ? " It changes nothing and reads only the system catalog, so it is reported as medium: it describes the schema, not anyone's rows." : "";
|
|
7944
7972
|
const anonymous = roles.includes("anon") || roles.includes("public");
|
|
7945
7973
|
const sites = rpcByName.get(fn2.name) ?? [];
|
|
7946
7974
|
const entries = unique(sites.map((s) => s.handlerData.entry));
|
|
@@ -7969,7 +7997,7 @@ var securityDefinerFunctionWithoutCallerCheck = {
|
|
|
7969
7997
|
evidence: [
|
|
7970
7998
|
{
|
|
7971
7999
|
kind: "rule",
|
|
7972
|
-
summary: `public.${fn2.name}() is SECURITY DEFINER: it runs with the rights of its owner and Row Level Security does not apply inside it. Its body never reads the caller's identity (auth.uid(), auth.jwt(), auth.email() or the request JWT), so whatever it returns or changes is available to every role that can execute it: ${roles.join(", ")}. ${who} at ${endpoint}.${callNote} Filter by auth.uid() inside the function, make it SECURITY INVOKER, or revoke EXECUTE from public, anon and authenticated.`,
|
|
8000
|
+
summary: `public.${fn2.name}() is SECURITY DEFINER: it runs with the rights of its owner and Row Level Security does not apply inside it. Its body never reads the caller's identity (auth.uid(), auth.jwt(), auth.email() or the request JWT), so whatever it returns or changes is available to every role that can execute it: ${roles.join(", ")}. ${who} at ${endpoint}.${callNote}${narrowNote} Filter by auth.uid() inside the function, make it SECURITY INVOKER, or revoke EXECUTE from public, anon and authenticated.`,
|
|
7973
8001
|
locations: locations3(
|
|
7974
8002
|
fn2.location,
|
|
7975
8003
|
...sites.flatMap((s) => [s.handler.location, s.query.location])
|
|
@@ -7978,13 +8006,14 @@ var securityDefinerFunctionWithoutCallerCheck = {
|
|
|
7978
8006
|
deterministic: false,
|
|
7979
8007
|
ruleId: this.id,
|
|
7980
8008
|
function: fn2.name,
|
|
7981
|
-
grantedTo: [...fn2.grantedTo]
|
|
8009
|
+
grantedTo: [...fn2.grantedTo],
|
|
8010
|
+
...narrow ? { narrowResult: narrow } : {}
|
|
7982
8011
|
}
|
|
7983
8012
|
},
|
|
7984
8013
|
{ kind: "trace", summary: path.join(" -> ") }
|
|
7985
8014
|
]
|
|
7986
8015
|
},
|
|
7987
|
-
anonymous ? "critical" : "high"
|
|
8016
|
+
narrow ? "medium" : anonymous ? "critical" : "high"
|
|
7988
8017
|
)
|
|
7989
8018
|
);
|
|
7990
8019
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auditai-scan",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "Deterministic security scanner for Next.js + Supabase apps: cross-tenant reads, RLS gaps, service-role misuse, mass assignment. No account, no model, seconds.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|