kryptheon-night 0.1.32 → 0.1.33
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/attack.js +135 -3
- package/collision.js +6 -1
- package/engine.sql +154 -1
- package/package.json +4 -3
package/attack.js
CHANGED
|
@@ -204,6 +204,132 @@ function allowedByCheck(table, columnName) {
|
|
|
204
204
|
return found;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/**
|
|
208
|
+
* The CHECK constraints on a table that name this column, as the expression
|
|
209
|
+
* inside CHECK (...). A NOT VALID one still applies to every new row.
|
|
210
|
+
*/
|
|
211
|
+
function checksOn(table, columnName) {
|
|
212
|
+
const escaped = String(columnName).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
213
|
+
const named = new RegExp('(^|[^A-Za-z0-9_$"])("' + escaped.split('"').join('""') + '"|' + escaped + ')($|[^A-Za-z0-9_$"])');
|
|
214
|
+
const found = [];
|
|
215
|
+
for (const constraint of table.constraints || []) {
|
|
216
|
+
if (constraint.kind !== 'c') continue;
|
|
217
|
+
const definition = String(constraint.definition || '');
|
|
218
|
+
if (!named.test(definition)) continue;
|
|
219
|
+
found.push(definition.replace(/^CHECK\s*/i, '').replace(/\s+NOT VALID\s*$/i, ''));
|
|
220
|
+
}
|
|
221
|
+
return found;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const NUMERIC_KIND = /^(integer|bigint|smallint|numeric|decimal|real|double)/;
|
|
225
|
+
const TEXT_KIND = /^(text|character|varchar|citext)/;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Values to offer a column whose CHECK could not be read, best first.
|
|
229
|
+
*
|
|
230
|
+
* Found on a real app (a clinic's, 2026-10-11): patient_waitlist and
|
|
231
|
+
* whatsapp_contacts each had a phone number column with a CHECK on its
|
|
232
|
+
* format, compliance_requests a type column with a CHECK no reading here
|
|
233
|
+
* understood - and none of the made-up values fitted, so three tables, and a
|
|
234
|
+
* fourth that pointed at one of them, were never tested. Nothing here reads
|
|
235
|
+
* the rule. Each value is put to the rule itself, in the database
|
|
236
|
+
* (fittingValue), and the first it accepts is used.
|
|
237
|
+
*
|
|
238
|
+
* Mirrored by __KN__.check_candidates in engine.sql, value for value and in
|
|
239
|
+
* the same order: both engines must seed the same rows.
|
|
240
|
+
*/
|
|
241
|
+
function checkCandidates(column, definitions, distinct) {
|
|
242
|
+
const kind = String(column.base_type || column.type).toLowerCase();
|
|
243
|
+
const tag = distinct === undefined || distinct === null ? '' : String(distinct);
|
|
244
|
+
const step = Number(tag) || 0;
|
|
245
|
+
const out = [];
|
|
246
|
+
const add = (value) => { if (out.indexOf(value) === -1) out.push(value); };
|
|
247
|
+
if (NUMERIC_KIND.test(kind)) {
|
|
248
|
+
[1 + step, 18 + step, 100 + step, 1000 + step, 0, 5].forEach((n) => add(String(n)));
|
|
249
|
+
for (const definition of definitions) {
|
|
250
|
+
const numbers = definition.match(/-?\d+(?:\.\d+)?/g) || [];
|
|
251
|
+
numbers.forEach(add);
|
|
252
|
+
}
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
if (!TEXT_KIND.test(kind)) return out;
|
|
256
|
+
const d = String(step % 10);
|
|
257
|
+
const phones = ['+1415555012' + d, '+919876543' + (210 + step), '9876543' + (210 + step), '415555012' + d,
|
|
258
|
+
'+1 415-555-012' + d, '(415) 555-012' + d, '415-555-012' + d, '+44 7700 90012' + d, '07700 90012' + d];
|
|
259
|
+
const emails = ['kryptheon' + tag + '@example.com'];
|
|
260
|
+
const links = ['https://example.com/kryptheon' + tag];
|
|
261
|
+
// The shapes valueFor offers, in its order, so a rule they already passed
|
|
262
|
+
// still gets the value it always got.
|
|
263
|
+
const words = [tag ? tag + ' kryptheon test' : 'kryptheon test', 'kryptheon' + tag + '@example.com', 'KN' + (tag || '1'),
|
|
264
|
+
String(step + 1), 'https://example.com/kryptheon', 'kryptheon' + tag, 'Kryptheon' + tag, 'kn-' + (tag || '1')];
|
|
265
|
+
const literals = [];
|
|
266
|
+
for (const definition of definitions) {
|
|
267
|
+
const quoted = /'((?:[^']|'')*)'/g;
|
|
268
|
+
let one;
|
|
269
|
+
while ((one = quoted.exec(definition)) !== null) literals.push(one[1].split("''").join("'"));
|
|
270
|
+
}
|
|
271
|
+
const codes = ['US', 'IN', 'USD', 'INR', 'EUR', 'en', 'en-US', '#00000' + d, '2026-01-0' + (1 + (step % 9)), '1234' + d, '56000' + d,
|
|
272
|
+
'kryptheon test value long enough for a rule on a minimum length ' + (tag || '0')];
|
|
273
|
+
const name = String(column.name).toLowerCase();
|
|
274
|
+
const first = /phone|mobile|whatsapp|tel|msisdn/.test(name) ? phones
|
|
275
|
+
: /mail/.test(name) ? emails
|
|
276
|
+
: /url|link|website|site|href/.test(name) ? links : [];
|
|
277
|
+
for (const value of first.concat(words, literals, phones, emails, links, codes)) add(fitTo(value, kind));
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* The first values a column's CHECK constraints accept, asked of the
|
|
283
|
+
* database itself: each rule is run on the value alone, the column given as
|
|
284
|
+
* the value by a one-row subquery. A rule that also names another column
|
|
285
|
+
* cannot be run alone and is left out; when no rule can be, nothing is
|
|
286
|
+
* offered and the seeder goes on as before. Remembered per table, column and
|
|
287
|
+
* row, so the attacks that insert later ask nothing again.
|
|
288
|
+
*
|
|
289
|
+
* Returns the accepted values, best first - possibly none.
|
|
290
|
+
*/
|
|
291
|
+
const FITTED = new WeakMap();
|
|
292
|
+
async function fittingValues(client, table, column, distinct) {
|
|
293
|
+
const definitions = checksOn(table, column.name);
|
|
294
|
+
if (!definitions.length) return [];
|
|
295
|
+
const candidates = checkCandidates(column, definitions, distinct);
|
|
296
|
+
if (!candidates.length) return [];
|
|
297
|
+
let byTable = FITTED.get(table);
|
|
298
|
+
if (!byTable) FITTED.set(table, (byTable = new Map()));
|
|
299
|
+
const key = column.name + '\u0000' + String(distinct);
|
|
300
|
+
if (byTable.has(key)) return byTable.get(key);
|
|
301
|
+
|
|
302
|
+
const type = String(column.type);
|
|
303
|
+
const alone = (expression, value) =>
|
|
304
|
+
'SELECT (' + expression + ') IS NOT FALSE AS ok FROM (SELECT ' + value + '::' + type + ' AS ' + quote(column.name) + ') AS kn_row';
|
|
305
|
+
const runnable = [];
|
|
306
|
+
for (const expression of definitions) {
|
|
307
|
+
try {
|
|
308
|
+
await client.query(alone(expression, 'NULL'));
|
|
309
|
+
runnable.push(expression);
|
|
310
|
+
} catch (err) {
|
|
311
|
+
// Names another column, or something only the table can supply.
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
let accepted = [];
|
|
315
|
+
if (runnable.length) {
|
|
316
|
+
const all = runnable.map((e) => '(' + e + ') IS NOT FALSE').join(' AND ');
|
|
317
|
+
for (const value of candidates) {
|
|
318
|
+
try {
|
|
319
|
+
const { rows } = await client.query(
|
|
320
|
+
'SELECT ' + all + ' AS ok FROM (SELECT $1::' + type + ' AS ' + quote(column.name) + ') AS kn_row', [value]);
|
|
321
|
+
if (rows[0] && rows[0].ok === true) accepted.push(value);
|
|
322
|
+
// As many as the seeder will ever try; asking further changes nothing.
|
|
323
|
+
if (accepted.length >= SHAPES_TO_TRY) break;
|
|
324
|
+
} catch (err) {
|
|
325
|
+
// A value its own type, or the rule, refuses outright.
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
byTable.set(key, accepted);
|
|
330
|
+
return accepted;
|
|
331
|
+
}
|
|
332
|
+
|
|
207
333
|
/**
|
|
208
334
|
* Sets of columns where exactly one may be filled in.
|
|
209
335
|
*
|
|
@@ -500,11 +626,14 @@ async function rowFor(client, schema, table, person, distinct, overrides, attemp
|
|
|
500
626
|
if (column.default_expr) continue;
|
|
501
627
|
if (!column.not_null && !mustFill) continue;
|
|
502
628
|
columns.push(column.name);
|
|
503
|
-
// A CHECK that lists what it will accept beats anything invented here
|
|
629
|
+
// A CHECK that lists what it will accept beats anything invented here;
|
|
630
|
+
// after that, a value one it could not read was asked about and accepts.
|
|
504
631
|
const allowed = allowedByCheck(table, column.name);
|
|
632
|
+
const fitting = allowed.length ? [] : await fittingValues(client, table, column, distinct);
|
|
633
|
+
const pick = (list) => list[Math.min(Math.max(attempt || 0, 0), list.length - 1)];
|
|
505
634
|
values.push(
|
|
506
|
-
allowed.length
|
|
507
|
-
|
|
635
|
+
allowed.length ? pick(allowed)
|
|
636
|
+
: fitting.length ? pick(fitting)
|
|
508
637
|
: valueFor(column, person, distinct, attempt),
|
|
509
638
|
);
|
|
510
639
|
}
|
|
@@ -882,6 +1011,9 @@ module.exports = {
|
|
|
882
1011
|
dependencyOrder: dependencyOrder,
|
|
883
1012
|
valueFor: valueFor,
|
|
884
1013
|
allowedByCheck: allowedByCheck,
|
|
1014
|
+
checksOn: checksOn,
|
|
1015
|
+
checkCandidates: checkCandidates,
|
|
1016
|
+
fittingValues: fittingValues,
|
|
885
1017
|
exactlyOne: exactlyOne,
|
|
886
1018
|
rowFor: rowFor,
|
|
887
1019
|
existingRow: existingRow,
|
package/collision.js
CHANGED
|
@@ -214,7 +214,12 @@ async function collide(client, one, two, schema, tables, indexes) {
|
|
|
214
214
|
|
|
215
215
|
for (const target of candidates(tables, indexes)) {
|
|
216
216
|
const table = byName.get(target.table);
|
|
217
|
-
|
|
217
|
+
// A column with a CHECK of its own - an email that has to look like one -
|
|
218
|
+
// refused the made-up value, and the race never started. A value the
|
|
219
|
+
// rule was asked about and accepts is raced instead.
|
|
220
|
+
const column = table && (table.columns || []).find((c) => c.name === target.column);
|
|
221
|
+
const fitting = column ? await attack.fittingValues(client, table, column, 'collision') : [];
|
|
222
|
+
const value = fitting.length ? fitting[0] : collidingValue(target);
|
|
218
223
|
const override = {};
|
|
219
224
|
override[target.column] = value;
|
|
220
225
|
|
package/engine.sql
CHANGED
|
@@ -1464,6 +1464,155 @@ BEGIN
|
|
|
1464
1464
|
RETURN found;
|
|
1465
1465
|
END $$;
|
|
1466
1466
|
|
|
1467
|
+
/*
|
|
1468
|
+
* The CHECK constraints on a table that name this column, as the expression
|
|
1469
|
+
* inside CHECK (...). A NOT VALID one still applies to every new row.
|
|
1470
|
+
* Mirrors checksOn in attack.js.
|
|
1471
|
+
*/
|
|
1472
|
+
CREATE OR REPLACE FUNCTION __KN__.checks_on(tab jsonb, column_name text)
|
|
1473
|
+
RETURNS text[] LANGUAGE plpgsql IMMUTABLE AS $$
|
|
1474
|
+
DECLARE
|
|
1475
|
+
escaped text := regexp_replace(column_name, '([.*+?^${}()|\[\]\\])', '\\\1', 'g');
|
|
1476
|
+
named text;
|
|
1477
|
+
found text[] := ARRAY[]::text[];
|
|
1478
|
+
con jsonb;
|
|
1479
|
+
definition text;
|
|
1480
|
+
BEGIN
|
|
1481
|
+
named := '(^|[^A-Za-z0-9_$"])("' || replace(escaped, '"', '""') || '"|' || escaped || ')($|[^A-Za-z0-9_$"])';
|
|
1482
|
+
FOR con IN SELECT * FROM jsonb_array_elements(coalesce(tab->'constraints', '[]'::jsonb)) LOOP
|
|
1483
|
+
CONTINUE WHEN con->>'kind' <> 'c';
|
|
1484
|
+
definition := coalesce(con->>'definition', '');
|
|
1485
|
+
CONTINUE WHEN definition !~ named;
|
|
1486
|
+
found := found || regexp_replace(regexp_replace(definition, '^CHECK\s*', '', 'i'), '\s+NOT VALID\s*$', '', 'i');
|
|
1487
|
+
END LOOP;
|
|
1488
|
+
RETURN found;
|
|
1489
|
+
END $$;
|
|
1490
|
+
|
|
1491
|
+
/*
|
|
1492
|
+
* Values to offer a column whose CHECK could not be read, best first.
|
|
1493
|
+
*
|
|
1494
|
+
* Found on a real app (a clinic's, 2026-10-11): phone number columns with a
|
|
1495
|
+
* CHECK on their format, and a type column with a CHECK nothing here read -
|
|
1496
|
+
* three tables, and a fourth that pointed at one of them, never tested.
|
|
1497
|
+
* Nothing here reads the rule: each value is put to the rule itself
|
|
1498
|
+
* (fitting_values), and the first it accepts is used.
|
|
1499
|
+
*
|
|
1500
|
+
* Mirrors checkCandidates in attack.js, value for value and in the same
|
|
1501
|
+
* order: both engines must seed the same rows.
|
|
1502
|
+
*/
|
|
1503
|
+
CREATE OR REPLACE FUNCTION __KN__.check_candidates(col jsonb, definitions text[], distinct_ text)
|
|
1504
|
+
RETURNS text[] LANGUAGE plpgsql IMMUTABLE AS $$
|
|
1505
|
+
DECLARE
|
|
1506
|
+
kind text := lower(coalesce(col->>'base_type', col->>'type'));
|
|
1507
|
+
tag text := coalesce(distinct_, '');
|
|
1508
|
+
step integer := CASE WHEN coalesce(distinct_, '') ~ '^[0-9]+$' THEN distinct_::integer ELSE 0 END;
|
|
1509
|
+
d text;
|
|
1510
|
+
name_ text := lower(col->>'name');
|
|
1511
|
+
out_ text[] := ARRAY[]::text[];
|
|
1512
|
+
phones text[];
|
|
1513
|
+
emails text[];
|
|
1514
|
+
links text[];
|
|
1515
|
+
words text[];
|
|
1516
|
+
literals text[] := ARRAY[]::text[];
|
|
1517
|
+
codes text[];
|
|
1518
|
+
first_ text[] := ARRAY[]::text[];
|
|
1519
|
+
definition text;
|
|
1520
|
+
one text;
|
|
1521
|
+
value_ text;
|
|
1522
|
+
BEGIN
|
|
1523
|
+
IF kind ~ '^(integer|bigint|smallint|numeric|decimal|real|double)' THEN
|
|
1524
|
+
FOREACH value_ IN ARRAY ARRAY[(1 + step)::text, (18 + step)::text, (100 + step)::text, (1000 + step)::text, '0', '5'] LOOP
|
|
1525
|
+
IF NOT (value_ = ANY (out_)) THEN out_ := out_ || value_; END IF;
|
|
1526
|
+
END LOOP;
|
|
1527
|
+
FOREACH definition IN ARRAY coalesce(definitions, ARRAY[]::text[]) LOOP
|
|
1528
|
+
FOR one IN SELECT (m)[1] FROM regexp_matches(definition, '(-?\d+(?:\.\d+)?)', 'g') m LOOP
|
|
1529
|
+
IF NOT (one = ANY (out_)) THEN out_ := out_ || one; END IF;
|
|
1530
|
+
END LOOP;
|
|
1531
|
+
END LOOP;
|
|
1532
|
+
RETURN out_;
|
|
1533
|
+
END IF;
|
|
1534
|
+
IF kind !~ '^(text|character|varchar|citext)' THEN RETURN out_; END IF;
|
|
1535
|
+
d := (step % 10)::text;
|
|
1536
|
+
phones := ARRAY['+1415555012' || d, '+919876543' || (210 + step)::text, '9876543' || (210 + step)::text, '415555012' || d,
|
|
1537
|
+
'+1 415-555-012' || d, '(415) 555-012' || d, '415-555-012' || d, '+44 7700 90012' || d, '07700 90012' || d];
|
|
1538
|
+
emails := ARRAY['kryptheon' || tag || '@example.com'];
|
|
1539
|
+
links := ARRAY['https://example.com/kryptheon' || tag];
|
|
1540
|
+
-- The shapes value_for offers, in its order, so a rule they already passed
|
|
1541
|
+
-- still gets the value it always got.
|
|
1542
|
+
words := ARRAY[CASE WHEN tag <> '' THEN tag || ' kryptheon test' ELSE 'kryptheon test' END,
|
|
1543
|
+
'kryptheon' || tag || '@example.com', 'KN' || CASE WHEN tag <> '' THEN tag ELSE '1' END,
|
|
1544
|
+
(step + 1)::text, 'https://example.com/kryptheon', 'kryptheon' || tag, 'Kryptheon' || tag,
|
|
1545
|
+
'kn-' || CASE WHEN tag <> '' THEN tag ELSE '1' END];
|
|
1546
|
+
FOREACH definition IN ARRAY coalesce(definitions, ARRAY[]::text[]) LOOP
|
|
1547
|
+
FOR one IN SELECT (m)[1] FROM regexp_matches(definition, '''((?:[^'']|'''')*)''', 'g') m LOOP
|
|
1548
|
+
literals := literals || replace(one, '''''', '''');
|
|
1549
|
+
END LOOP;
|
|
1550
|
+
END LOOP;
|
|
1551
|
+
codes := ARRAY['US', 'IN', 'USD', 'INR', 'EUR', 'en', 'en-US', '#00000' || d, '2026-01-0' || (1 + (step % 9))::text,
|
|
1552
|
+
'1234' || d, '56000' || d,
|
|
1553
|
+
'kryptheon test value long enough for a rule on a minimum length ' || CASE WHEN tag <> '' THEN tag ELSE '0' END];
|
|
1554
|
+
IF name_ ~ 'phone|mobile|whatsapp|tel|msisdn' THEN first_ := phones;
|
|
1555
|
+
ELSIF name_ ~ 'mail' THEN first_ := emails;
|
|
1556
|
+
ELSIF name_ ~ 'url|link|website|site|href' THEN first_ := links;
|
|
1557
|
+
END IF;
|
|
1558
|
+
FOREACH value_ IN ARRAY first_ || words || literals || phones || emails || links || codes LOOP
|
|
1559
|
+
value_ := __KN__.fit_to(value_, kind);
|
|
1560
|
+
IF NOT (value_ = ANY (out_)) THEN out_ := out_ || value_; END IF;
|
|
1561
|
+
END LOOP;
|
|
1562
|
+
RETURN out_;
|
|
1563
|
+
END $$;
|
|
1564
|
+
|
|
1565
|
+
/*
|
|
1566
|
+
* The first values a column's CHECK constraints accept, asked of the
|
|
1567
|
+
* database itself: each rule is run on the value alone, the column given as
|
|
1568
|
+
* the value by a one-row subquery. A rule that also names another column
|
|
1569
|
+
* cannot be run alone and is left out; when no rule can be, nothing is
|
|
1570
|
+
* offered and the seeder goes on as before. At most five - as many as the
|
|
1571
|
+
* seeder ever tries. Mirrors fittingValues in attack.js.
|
|
1572
|
+
*/
|
|
1573
|
+
CREATE OR REPLACE FUNCTION __KN__.fitting_values(tab jsonb, col jsonb, distinct_ text)
|
|
1574
|
+
RETURNS text[] LANGUAGE plpgsql AS $$
|
|
1575
|
+
DECLARE
|
|
1576
|
+
name_ text := col->>'name';
|
|
1577
|
+
type_ text := col->>'type';
|
|
1578
|
+
definitions text[] := __KN__.checks_on(tab, col->>'name');
|
|
1579
|
+
candidates text[];
|
|
1580
|
+
runnable text[] := ARRAY[]::text[];
|
|
1581
|
+
all_ text;
|
|
1582
|
+
accepted text[] := ARRAY[]::text[];
|
|
1583
|
+
expression text;
|
|
1584
|
+
value_ text;
|
|
1585
|
+
ok boolean;
|
|
1586
|
+
BEGIN
|
|
1587
|
+
IF coalesce(array_length(definitions, 1), 0) = 0 THEN RETURN accepted; END IF;
|
|
1588
|
+
candidates := __KN__.check_candidates(col, definitions, distinct_);
|
|
1589
|
+
IF coalesce(array_length(candidates, 1), 0) = 0 THEN RETURN accepted; END IF;
|
|
1590
|
+
FOREACH expression IN ARRAY definitions LOOP
|
|
1591
|
+
BEGIN
|
|
1592
|
+
EXECUTE format('SELECT (%s) IS NOT FALSE AS ok FROM (SELECT NULL::%s AS %I) AS kn_row', expression, type_, name_);
|
|
1593
|
+
runnable := runnable || expression;
|
|
1594
|
+
EXCEPTION WHEN OTHERS THEN
|
|
1595
|
+
-- Names another column, or something only the table can supply.
|
|
1596
|
+
NULL;
|
|
1597
|
+
END;
|
|
1598
|
+
END LOOP;
|
|
1599
|
+
IF coalesce(array_length(runnable, 1), 0) = 0 THEN RETURN accepted; END IF;
|
|
1600
|
+
SELECT string_agg('(' || e || ') IS NOT FALSE', ' AND ') INTO all_ FROM unnest(runnable) e;
|
|
1601
|
+
FOREACH value_ IN ARRAY candidates LOOP
|
|
1602
|
+
BEGIN
|
|
1603
|
+
EXECUTE format('SELECT %s AS ok FROM (SELECT $1::%s AS %I) AS kn_row', all_, type_, name_) INTO ok USING value_;
|
|
1604
|
+
IF ok IS TRUE THEN
|
|
1605
|
+
accepted := accepted || value_;
|
|
1606
|
+
EXIT WHEN array_length(accepted, 1) >= 5;
|
|
1607
|
+
END IF;
|
|
1608
|
+
EXCEPTION WHEN OTHERS THEN
|
|
1609
|
+
-- A value its own type, or the rule, refuses outright.
|
|
1610
|
+
NULL;
|
|
1611
|
+
END;
|
|
1612
|
+
END LOOP;
|
|
1613
|
+
RETURN accepted;
|
|
1614
|
+
END $$;
|
|
1615
|
+
|
|
1467
1616
|
/*
|
|
1468
1617
|
* Something valid to put in a column, so a row can exist at all.
|
|
1469
1618
|
*
|
|
@@ -1983,8 +2132,12 @@ BEGIN
|
|
|
1983
2132
|
CONTINUE WHEN NOT (col->>'not_null')::boolean AND NOT must_fill;
|
|
1984
2133
|
|
|
1985
2134
|
columns := columns || to_jsonb(name);
|
|
1986
|
-
-- A CHECK that lists what it will accept beats anything invented here
|
|
2135
|
+
-- A CHECK that lists what it will accept beats anything invented here;
|
|
2136
|
+
-- after that, a value one it could not read was asked about and accepts.
|
|
1987
2137
|
allowed := __KN__.allowed_by_check(tab, name);
|
|
2138
|
+
IF coalesce(array_length(allowed, 1), 0) = 0 THEN
|
|
2139
|
+
allowed := __KN__.fitting_values(tab, col, distinct_);
|
|
2140
|
+
END IF;
|
|
1988
2141
|
IF array_length(allowed, 1) > 0 THEN
|
|
1989
2142
|
nth := least(greatest(coalesce(attempt, 0), 0), array_length(allowed, 1) - 1);
|
|
1990
2143
|
values_ := values_ || jsonb_build_array(to_jsonb(allowed[nth + 1]));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kryptheon-night",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"description": "Attacks a copy of your Supabase database and tells you in plain English what got in.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"node": ">=18.0.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"check": "node package.check.js && node store.check.js && node trouble.check.js && node intro.check.js && node finding.check.js && node outside.check.js && node recheck.check.js && node untouched.check.js && node blocked.check.js && node orphans.check.js && node guests.check.js && node usable.check.js && node twin.check.js && node engines.check.js && node external.check.js && node schema.check.js && node collision.check.js && node tamper.check.js && node orphan.check.js && node shapes.check.js && node verdicts.check.js && node shop.check.js && node privileged.check.js && node recursion.check.js && node guarded.check.js && node nightly.check.js && node words.check.js && node helpers.check.js && node deeploop.check.js && node teams.check.js && node roles.check.js && node buckets.check.js && node cli.check.js && node loop.check.js && node report.check.js && node installscript.check.js && node tenants.check.js && node takeover.check.js",
|
|
34
|
+
"check": "node package.check.js && node store.check.js && node trouble.check.js && node intro.check.js && node finding.check.js && node outside.check.js && node recheck.check.js && node untouched.check.js && node blocked.check.js && node orphans.check.js && node guests.check.js && node usable.check.js && node twin.check.js && node engines.check.js && node external.check.js && node schema.check.js && node collision.check.js && node tamper.check.js && node orphan.check.js && node shapes.check.js && node verdicts.check.js && node shop.check.js && node privileged.check.js && node recursion.check.js && node guarded.check.js && node nightly.check.js && node words.check.js && node helpers.check.js && node deeploop.check.js && node teams.check.js && node roles.check.js && node buckets.check.js && node cli.check.js && node loop.check.js && node report.check.js && node installscript.check.js && node tenants.check.js && node takeover.check.js && node checkfit.check.js",
|
|
35
35
|
"check:dry": "node package.check.js && node store.check.js && node trouble.check.js && node intro.check.js && node finding.check.js && node recheck.check.js && node outside.check.js",
|
|
36
36
|
"check:package": "node package.check.js",
|
|
37
37
|
"check:store": "node store.check.js",
|
|
@@ -77,7 +77,8 @@
|
|
|
77
77
|
"check:buckets": "node buckets.check.js",
|
|
78
78
|
"check:installscript": "node installscript.check.js",
|
|
79
79
|
"check:tenants": "node tenants.check.js",
|
|
80
|
-
"check:takeover": "node takeover.check.js"
|
|
80
|
+
"check:takeover": "node takeover.check.js",
|
|
81
|
+
"check:checkfit": "node checkfit.check.js"
|
|
81
82
|
},
|
|
82
83
|
"keywords": [
|
|
83
84
|
"supabase",
|