@superatomai/sdk-node 0.0.88-dsp → 0.0.89-dsp
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/README.md +942 -942
- package/dist/index.d.mts +206 -812
- package/dist/index.d.ts +206 -812
- package/dist/index.js +196 -34
- package/dist/index.mjs +195 -34
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62,6 +62,7 @@ __export(index_exports, {
|
|
|
62
62
|
bridgePreamble: () => bridgePreamble,
|
|
63
63
|
captureContext: () => captureContext,
|
|
64
64
|
clearContextTokens: () => clearContextTokens,
|
|
65
|
+
constraintPoliciesFromConfig: () => constraintPoliciesFromConfig,
|
|
65
66
|
contextTokenCount: () => contextTokenCount,
|
|
66
67
|
createCodexSession: () => createCodexSession,
|
|
67
68
|
createCodingAgentSession: () => createCodingAgentSession,
|
|
@@ -1952,7 +1953,17 @@ function runWithContext(ctx, fn) {
|
|
|
1952
1953
|
return getStorage().run(ctx, fn);
|
|
1953
1954
|
}
|
|
1954
1955
|
function runAsUser(user, fn) {
|
|
1955
|
-
const {
|
|
1956
|
+
const {
|
|
1957
|
+
userId,
|
|
1958
|
+
orgId,
|
|
1959
|
+
role,
|
|
1960
|
+
projectId,
|
|
1961
|
+
requestId,
|
|
1962
|
+
policies,
|
|
1963
|
+
constraintPolicies,
|
|
1964
|
+
allowedSources,
|
|
1965
|
+
graphVersion
|
|
1966
|
+
} = user;
|
|
1956
1967
|
return runWithContext(
|
|
1957
1968
|
// `allowedSources` is spread in only when given, so an absent restriction
|
|
1958
1969
|
// stays absent rather than becoming an explicit `undefined` that a later
|
|
@@ -1962,6 +1973,7 @@ function runAsUser(user, fn) {
|
|
|
1962
1973
|
projectId,
|
|
1963
1974
|
requestId,
|
|
1964
1975
|
policies,
|
|
1976
|
+
...constraintPolicies?.length ? { constraintPolicies } : {},
|
|
1965
1977
|
...allowedSources !== void 0 ? { allowedSources } : {},
|
|
1966
1978
|
...graphVersion !== void 0 ? { graphVersion } : {}
|
|
1967
1979
|
},
|
|
@@ -2642,42 +2654,148 @@ function buildDirectToolCacheKey(toolId, parameters) {
|
|
|
2642
2654
|
return `et-direct:${toolId}:${stableStringify(parameters || {})}`;
|
|
2643
2655
|
}
|
|
2644
2656
|
|
|
2645
|
-
// src/handlers/
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2657
|
+
// src/handlers/data-access/v2/policies.ts
|
|
2658
|
+
var CONSTRAINT_OPS = [
|
|
2659
|
+
"in",
|
|
2660
|
+
"not_in",
|
|
2661
|
+
"eq",
|
|
2662
|
+
"ne",
|
|
2663
|
+
"gt",
|
|
2664
|
+
"gte",
|
|
2665
|
+
"lt",
|
|
2666
|
+
"lte",
|
|
2667
|
+
"like",
|
|
2668
|
+
"not_like",
|
|
2669
|
+
"is_null",
|
|
2670
|
+
"is_not_null"
|
|
2671
|
+
];
|
|
2672
|
+
var NO_VALUE_OPS = /* @__PURE__ */ new Set(["is_null", "is_not_null"]);
|
|
2673
|
+
function constraintPoliciesFromConfig(raw) {
|
|
2674
|
+
const entries = Array.isArray(raw) ? raw : [raw];
|
|
2675
|
+
const out = [];
|
|
2676
|
+
for (const entry of entries) {
|
|
2677
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
2678
|
+
const e = entry;
|
|
2679
|
+
const sourceId = typeof e.sourceId === "string" && e.sourceId ? e.sourceId : null;
|
|
2680
|
+
const label = typeof e.label === "string" && e.label.trim() ? e.label.trim() : void 0;
|
|
2681
|
+
const bad = (why) => ({ sourceId, label, invalid: why });
|
|
2682
|
+
let schema2;
|
|
2683
|
+
if (e.schema !== void 0 && e.schema !== null) {
|
|
2684
|
+
if (typeof e.schema !== "string" || !e.schema.trim()) {
|
|
2685
|
+
out.push(bad("schema must be a schema name"));
|
|
2686
|
+
continue;
|
|
2687
|
+
}
|
|
2688
|
+
schema2 = e.schema.trim();
|
|
2689
|
+
}
|
|
2690
|
+
let readableTables;
|
|
2691
|
+
let tablesBad;
|
|
2692
|
+
if (e.readableTables !== void 0 && e.readableTables !== null) {
|
|
2693
|
+
if (typeof e.readableTables !== "object" || Array.isArray(e.readableTables)) {
|
|
2694
|
+
out.push(bad("readableTables must be an object of table to columns"));
|
|
2695
|
+
continue;
|
|
2696
|
+
}
|
|
2697
|
+
readableTables = {};
|
|
2698
|
+
for (const [table, cols] of Object.entries(e.readableTables)) {
|
|
2699
|
+
if (cols === null) {
|
|
2700
|
+
readableTables[table] = null;
|
|
2701
|
+
continue;
|
|
2702
|
+
}
|
|
2703
|
+
if (!Array.isArray(cols) || cols.length === 0) {
|
|
2704
|
+
tablesBad = `readableTables["${table}"] must be a non-empty list or null`;
|
|
2705
|
+
break;
|
|
2706
|
+
}
|
|
2707
|
+
const columns = cols.filter((c) => typeof c === "string" && !!c.trim()).map((c) => c.trim());
|
|
2708
|
+
if (columns.length !== cols.length) {
|
|
2709
|
+
tablesBad = `readableTables["${table}"] has an entry that is not a column name`;
|
|
2710
|
+
break;
|
|
2711
|
+
}
|
|
2712
|
+
readableTables[table] = columns;
|
|
2713
|
+
}
|
|
2714
|
+
if (!tablesBad && Object.keys(readableTables).length === 0) {
|
|
2715
|
+
tablesBad = "readableTables names no tables \u2014 omit it to allow every table";
|
|
2716
|
+
}
|
|
2717
|
+
if (tablesBad) {
|
|
2718
|
+
out.push(bad(tablesBad));
|
|
2719
|
+
continue;
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
const rowFilters = {};
|
|
2723
|
+
let filtersBad;
|
|
2724
|
+
const rawFilters = e.rowFilters;
|
|
2725
|
+
if (rawFilters !== void 0 && rawFilters !== null) {
|
|
2726
|
+
if (typeof rawFilters !== "object" || Array.isArray(rawFilters)) {
|
|
2727
|
+
filtersBad = "rowFilters must be an object keyed by table";
|
|
2728
|
+
} else {
|
|
2729
|
+
for (const [table, items] of Object.entries(rawFilters)) {
|
|
2730
|
+
if (readableTables && !(table in readableTables)) {
|
|
2731
|
+
filtersBad = `rowFilters names "${table}", which readableTables does not allow`;
|
|
2732
|
+
break;
|
|
2733
|
+
}
|
|
2734
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
2735
|
+
filtersBad = `rowFilters["${table}"] must be a non-empty list`;
|
|
2736
|
+
break;
|
|
2737
|
+
}
|
|
2738
|
+
const parsed = [];
|
|
2739
|
+
for (const item of items) {
|
|
2740
|
+
if (!item || typeof item !== "object") {
|
|
2741
|
+
filtersBad = `rowFilters["${table}"] has an entry that is not an object`;
|
|
2742
|
+
break;
|
|
2743
|
+
}
|
|
2744
|
+
const f = item;
|
|
2745
|
+
if (typeof f.column !== "string" || !f.column.trim()) {
|
|
2746
|
+
filtersBad = `a filter on "${table}" has no column`;
|
|
2747
|
+
break;
|
|
2748
|
+
}
|
|
2749
|
+
const op = typeof f.op === "string" ? f.op.toLowerCase() : "in";
|
|
2750
|
+
if (!CONSTRAINT_OPS.includes(op)) {
|
|
2751
|
+
filtersBad = `unsupported operator "${f.op}" on "${table}.${f.column}"`;
|
|
2752
|
+
break;
|
|
2753
|
+
}
|
|
2754
|
+
const values = Array.isArray(f.values) ? f.values : [];
|
|
2755
|
+
if (!NO_VALUE_OPS.has(op) && values.length === 0) {
|
|
2756
|
+
filtersBad = `the filter on "${table}.${f.column}" has no values`;
|
|
2757
|
+
break;
|
|
2758
|
+
}
|
|
2759
|
+
parsed.push({
|
|
2760
|
+
column: f.column.trim(),
|
|
2761
|
+
op,
|
|
2762
|
+
...NO_VALUE_OPS.has(op) ? {} : { values },
|
|
2763
|
+
label: typeof f.label === "string" && f.label.trim() ? f.label.trim() : void 0
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2766
|
+
if (filtersBad) break;
|
|
2767
|
+
rowFilters[table] = parsed;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
if (filtersBad) {
|
|
2772
|
+
out.push(bad(filtersBad));
|
|
2773
|
+
continue;
|
|
2774
|
+
}
|
|
2775
|
+
const rawLimits = e.limits;
|
|
2776
|
+
const num = (v) => typeof v === "number" && Number.isInteger(v) && v > 0 ? v : void 0;
|
|
2777
|
+
const limits = rawLimits ? { maxRows: num(rawLimits.maxRows), maxExecutionSeconds: num(rawLimits.maxExecutionSeconds) } : void 0;
|
|
2778
|
+
out.push({
|
|
2779
|
+
sourceId,
|
|
2780
|
+
label,
|
|
2781
|
+
...schema2 ? { schema: schema2 } : {},
|
|
2782
|
+
...readableTables ? { readableTables } : {},
|
|
2783
|
+
...Object.keys(rowFilters).length ? { rowFilters } : {},
|
|
2784
|
+
...limits ? { limits } : {}
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
return out;
|
|
2649
2788
|
}
|
|
2789
|
+
|
|
2790
|
+
// src/handlers/data-access/shape.ts
|
|
2650
2791
|
function scopedForm(config2) {
|
|
2651
2792
|
if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) return void 0;
|
|
2652
2793
|
const c = config2;
|
|
2653
2794
|
return "allowedSources" in c || "policies" in c ? c : void 0;
|
|
2654
2795
|
}
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
if (!warnedBadSources) {
|
|
2659
|
-
warnedBadSources = true;
|
|
2660
|
-
logger.warn(
|
|
2661
|
-
`[RequestIdentity] "allowedSources" is ${typeof value}, not a list of source ids \u2014 treating it as NO sources rather than all of them. Fix the config: "allowedSources": ["mssql-1a2b"].`
|
|
2662
|
-
);
|
|
2663
|
-
}
|
|
2664
|
-
return [];
|
|
2665
|
-
}
|
|
2666
|
-
const out = [];
|
|
2667
|
-
for (const item of value) {
|
|
2668
|
-
if (typeof item !== "string" || !item.trim()) continue;
|
|
2669
|
-
const id = item.trim();
|
|
2670
|
-
if (!out.includes(id)) out.push(id);
|
|
2671
|
-
}
|
|
2672
|
-
return out;
|
|
2673
|
-
}
|
|
2674
|
-
function accessFromConfig(config2) {
|
|
2675
|
-
const scoped = scopedForm(config2);
|
|
2676
|
-
return {
|
|
2677
|
-
policies: policiesFromConfig(config2),
|
|
2678
|
-
allowedSources: scoped ? allowedSourcesFrom(scoped.allowedSources) : null
|
|
2679
|
-
};
|
|
2680
|
-
}
|
|
2796
|
+
|
|
2797
|
+
// src/handlers/data-access/v1/policies.ts
|
|
2798
|
+
var warnedUnlabelled = false;
|
|
2681
2799
|
function exceptionsFromConfig(raw) {
|
|
2682
2800
|
if (!Array.isArray(raw)) return [];
|
|
2683
2801
|
const str = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
@@ -2756,16 +2874,55 @@ function policiesFromConfig(config2) {
|
|
|
2756
2874
|
}
|
|
2757
2875
|
return out;
|
|
2758
2876
|
}
|
|
2877
|
+
|
|
2878
|
+
// src/handlers/request-identity.ts
|
|
2879
|
+
function graphVersionOf(v) {
|
|
2880
|
+
if (v === "draft") return "draft";
|
|
2881
|
+
return Number.isInteger(v) && v > 0 ? v : void 0;
|
|
2882
|
+
}
|
|
2883
|
+
function allowedSourcesFrom(value) {
|
|
2884
|
+
if (value === void 0 || value === null) return null;
|
|
2885
|
+
if (!Array.isArray(value)) {
|
|
2886
|
+
if (!warnedBadSources) {
|
|
2887
|
+
warnedBadSources = true;
|
|
2888
|
+
logger.warn(
|
|
2889
|
+
`[RequestIdentity] "allowedSources" is ${typeof value}, not a list of source ids \u2014 treating it as NO sources rather than all of them. Fix the config: "allowedSources": ["mssql-1a2b"].`
|
|
2890
|
+
);
|
|
2891
|
+
}
|
|
2892
|
+
return [];
|
|
2893
|
+
}
|
|
2894
|
+
const out = [];
|
|
2895
|
+
for (const item of value) {
|
|
2896
|
+
if (typeof item !== "string" || !item.trim()) continue;
|
|
2897
|
+
const id = item.trim();
|
|
2898
|
+
if (!out.includes(id)) out.push(id);
|
|
2899
|
+
}
|
|
2900
|
+
return out;
|
|
2901
|
+
}
|
|
2902
|
+
function accessFromConfig(config2) {
|
|
2903
|
+
const scoped = scopedForm(config2);
|
|
2904
|
+
const allowedSources = scoped ? allowedSourcesFrom(scoped.allowedSources) : null;
|
|
2905
|
+
if (scoped?.version === 2) {
|
|
2906
|
+
return {
|
|
2907
|
+
policies: [],
|
|
2908
|
+
constraintPolicies: constraintPoliciesFromConfig(scoped.policies),
|
|
2909
|
+
allowedSources
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
return { policies: policiesFromConfig(config2), constraintPolicies: [], allowedSources };
|
|
2913
|
+
}
|
|
2759
2914
|
var warnedUntrusted = false;
|
|
2760
2915
|
var warnedAnonymous = false;
|
|
2761
|
-
var warnedUnlabelled = false;
|
|
2762
2916
|
var warnedBadSources = false;
|
|
2763
2917
|
var announcedVerified = false;
|
|
2764
2918
|
var reportedIdMismatches = /* @__PURE__ */ new Set();
|
|
2765
2919
|
function runWithMessageIdentity(message, fn) {
|
|
2766
2920
|
const verified = message?.authContext;
|
|
2767
2921
|
const requestId = message?.id;
|
|
2768
|
-
const { policies, allowedSources } = verified ? accessFromConfig(verified.config) : accessFromConfig(message?.payload?.config);
|
|
2922
|
+
const { policies, constraintPolicies, allowedSources } = verified ? accessFromConfig(verified.config) : accessFromConfig(message?.payload?.config);
|
|
2923
|
+
logger.info(
|
|
2924
|
+
`[RequestIdentity] ${message?.type ?? "?"}: access from ${verified ? "authContext" : "payload"}${verified && verified.config == null ? " (none on it)" : ""}, allowedSources=${allowedSources === null ? "every source" : `[${allowedSources.join(", ") || "NONE"}]`}, ${constraintPolicies.length} constraint policy(ies), ${policies.length} row policy(ies)`
|
|
2925
|
+
);
|
|
2769
2926
|
if (verified?.userId) {
|
|
2770
2927
|
const claimedId = message?.payload?.userId;
|
|
2771
2928
|
if (!announcedVerified) {
|
|
@@ -2790,6 +2947,7 @@ function runWithMessageIdentity(message, fn) {
|
|
|
2790
2947
|
role: verified.role,
|
|
2791
2948
|
requestId,
|
|
2792
2949
|
policies,
|
|
2950
|
+
constraintPolicies,
|
|
2793
2951
|
// `?? undefined` so "no restriction" leaves the key off the context
|
|
2794
2952
|
// entirely; `[]` is passed through as the deny-everything it means.
|
|
2795
2953
|
allowedSources: allowedSources ?? void 0,
|
|
@@ -2807,7 +2965,10 @@ function runWithMessageIdentity(message, fn) {
|
|
|
2807
2965
|
"[RequestIdentity] Using the client-supplied payload.userId as the principal \u2014 the relay is not stamping a verified authContext yet, so this identity is NOT trustworthy. Fine for plumbing; do not enable policy enforcement until the relay change lands."
|
|
2808
2966
|
);
|
|
2809
2967
|
}
|
|
2810
|
-
return runAsUser(
|
|
2968
|
+
return runAsUser(
|
|
2969
|
+
{ userId: claimed, requestId, policies, constraintPolicies, allowedSources: allowedSources ?? void 0 },
|
|
2970
|
+
fn
|
|
2971
|
+
);
|
|
2811
2972
|
}
|
|
2812
2973
|
if (!warnedAnonymous) {
|
|
2813
2974
|
warnedAnonymous = true;
|
|
@@ -30209,6 +30370,7 @@ var SuperatomSDK = class {
|
|
|
30209
30370
|
bridgePreamble,
|
|
30210
30371
|
captureContext,
|
|
30211
30372
|
clearContextTokens,
|
|
30373
|
+
constraintPoliciesFromConfig,
|
|
30212
30374
|
contextTokenCount,
|
|
30213
30375
|
createCodexSession,
|
|
30214
30376
|
createCodingAgentSession,
|
package/dist/index.mjs
CHANGED
|
@@ -1840,7 +1840,17 @@ function runWithContext(ctx, fn) {
|
|
|
1840
1840
|
return getStorage().run(ctx, fn);
|
|
1841
1841
|
}
|
|
1842
1842
|
function runAsUser(user, fn) {
|
|
1843
|
-
const {
|
|
1843
|
+
const {
|
|
1844
|
+
userId,
|
|
1845
|
+
orgId,
|
|
1846
|
+
role,
|
|
1847
|
+
projectId,
|
|
1848
|
+
requestId,
|
|
1849
|
+
policies,
|
|
1850
|
+
constraintPolicies,
|
|
1851
|
+
allowedSources,
|
|
1852
|
+
graphVersion
|
|
1853
|
+
} = user;
|
|
1844
1854
|
return runWithContext(
|
|
1845
1855
|
// `allowedSources` is spread in only when given, so an absent restriction
|
|
1846
1856
|
// stays absent rather than becoming an explicit `undefined` that a later
|
|
@@ -1850,6 +1860,7 @@ function runAsUser(user, fn) {
|
|
|
1850
1860
|
projectId,
|
|
1851
1861
|
requestId,
|
|
1852
1862
|
policies,
|
|
1863
|
+
...constraintPolicies?.length ? { constraintPolicies } : {},
|
|
1853
1864
|
...allowedSources !== void 0 ? { allowedSources } : {},
|
|
1854
1865
|
...graphVersion !== void 0 ? { graphVersion } : {}
|
|
1855
1866
|
},
|
|
@@ -2530,42 +2541,148 @@ function buildDirectToolCacheKey(toolId, parameters) {
|
|
|
2530
2541
|
return `et-direct:${toolId}:${stableStringify(parameters || {})}`;
|
|
2531
2542
|
}
|
|
2532
2543
|
|
|
2533
|
-
// src/handlers/
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2544
|
+
// src/handlers/data-access/v2/policies.ts
|
|
2545
|
+
var CONSTRAINT_OPS = [
|
|
2546
|
+
"in",
|
|
2547
|
+
"not_in",
|
|
2548
|
+
"eq",
|
|
2549
|
+
"ne",
|
|
2550
|
+
"gt",
|
|
2551
|
+
"gte",
|
|
2552
|
+
"lt",
|
|
2553
|
+
"lte",
|
|
2554
|
+
"like",
|
|
2555
|
+
"not_like",
|
|
2556
|
+
"is_null",
|
|
2557
|
+
"is_not_null"
|
|
2558
|
+
];
|
|
2559
|
+
var NO_VALUE_OPS = /* @__PURE__ */ new Set(["is_null", "is_not_null"]);
|
|
2560
|
+
function constraintPoliciesFromConfig(raw) {
|
|
2561
|
+
const entries = Array.isArray(raw) ? raw : [raw];
|
|
2562
|
+
const out = [];
|
|
2563
|
+
for (const entry of entries) {
|
|
2564
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
2565
|
+
const e = entry;
|
|
2566
|
+
const sourceId = typeof e.sourceId === "string" && e.sourceId ? e.sourceId : null;
|
|
2567
|
+
const label = typeof e.label === "string" && e.label.trim() ? e.label.trim() : void 0;
|
|
2568
|
+
const bad = (why) => ({ sourceId, label, invalid: why });
|
|
2569
|
+
let schema2;
|
|
2570
|
+
if (e.schema !== void 0 && e.schema !== null) {
|
|
2571
|
+
if (typeof e.schema !== "string" || !e.schema.trim()) {
|
|
2572
|
+
out.push(bad("schema must be a schema name"));
|
|
2573
|
+
continue;
|
|
2574
|
+
}
|
|
2575
|
+
schema2 = e.schema.trim();
|
|
2576
|
+
}
|
|
2577
|
+
let readableTables;
|
|
2578
|
+
let tablesBad;
|
|
2579
|
+
if (e.readableTables !== void 0 && e.readableTables !== null) {
|
|
2580
|
+
if (typeof e.readableTables !== "object" || Array.isArray(e.readableTables)) {
|
|
2581
|
+
out.push(bad("readableTables must be an object of table to columns"));
|
|
2582
|
+
continue;
|
|
2583
|
+
}
|
|
2584
|
+
readableTables = {};
|
|
2585
|
+
for (const [table, cols] of Object.entries(e.readableTables)) {
|
|
2586
|
+
if (cols === null) {
|
|
2587
|
+
readableTables[table] = null;
|
|
2588
|
+
continue;
|
|
2589
|
+
}
|
|
2590
|
+
if (!Array.isArray(cols) || cols.length === 0) {
|
|
2591
|
+
tablesBad = `readableTables["${table}"] must be a non-empty list or null`;
|
|
2592
|
+
break;
|
|
2593
|
+
}
|
|
2594
|
+
const columns = cols.filter((c) => typeof c === "string" && !!c.trim()).map((c) => c.trim());
|
|
2595
|
+
if (columns.length !== cols.length) {
|
|
2596
|
+
tablesBad = `readableTables["${table}"] has an entry that is not a column name`;
|
|
2597
|
+
break;
|
|
2598
|
+
}
|
|
2599
|
+
readableTables[table] = columns;
|
|
2600
|
+
}
|
|
2601
|
+
if (!tablesBad && Object.keys(readableTables).length === 0) {
|
|
2602
|
+
tablesBad = "readableTables names no tables \u2014 omit it to allow every table";
|
|
2603
|
+
}
|
|
2604
|
+
if (tablesBad) {
|
|
2605
|
+
out.push(bad(tablesBad));
|
|
2606
|
+
continue;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
const rowFilters = {};
|
|
2610
|
+
let filtersBad;
|
|
2611
|
+
const rawFilters = e.rowFilters;
|
|
2612
|
+
if (rawFilters !== void 0 && rawFilters !== null) {
|
|
2613
|
+
if (typeof rawFilters !== "object" || Array.isArray(rawFilters)) {
|
|
2614
|
+
filtersBad = "rowFilters must be an object keyed by table";
|
|
2615
|
+
} else {
|
|
2616
|
+
for (const [table, items] of Object.entries(rawFilters)) {
|
|
2617
|
+
if (readableTables && !(table in readableTables)) {
|
|
2618
|
+
filtersBad = `rowFilters names "${table}", which readableTables does not allow`;
|
|
2619
|
+
break;
|
|
2620
|
+
}
|
|
2621
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
2622
|
+
filtersBad = `rowFilters["${table}"] must be a non-empty list`;
|
|
2623
|
+
break;
|
|
2624
|
+
}
|
|
2625
|
+
const parsed = [];
|
|
2626
|
+
for (const item of items) {
|
|
2627
|
+
if (!item || typeof item !== "object") {
|
|
2628
|
+
filtersBad = `rowFilters["${table}"] has an entry that is not an object`;
|
|
2629
|
+
break;
|
|
2630
|
+
}
|
|
2631
|
+
const f = item;
|
|
2632
|
+
if (typeof f.column !== "string" || !f.column.trim()) {
|
|
2633
|
+
filtersBad = `a filter on "${table}" has no column`;
|
|
2634
|
+
break;
|
|
2635
|
+
}
|
|
2636
|
+
const op = typeof f.op === "string" ? f.op.toLowerCase() : "in";
|
|
2637
|
+
if (!CONSTRAINT_OPS.includes(op)) {
|
|
2638
|
+
filtersBad = `unsupported operator "${f.op}" on "${table}.${f.column}"`;
|
|
2639
|
+
break;
|
|
2640
|
+
}
|
|
2641
|
+
const values = Array.isArray(f.values) ? f.values : [];
|
|
2642
|
+
if (!NO_VALUE_OPS.has(op) && values.length === 0) {
|
|
2643
|
+
filtersBad = `the filter on "${table}.${f.column}" has no values`;
|
|
2644
|
+
break;
|
|
2645
|
+
}
|
|
2646
|
+
parsed.push({
|
|
2647
|
+
column: f.column.trim(),
|
|
2648
|
+
op,
|
|
2649
|
+
...NO_VALUE_OPS.has(op) ? {} : { values },
|
|
2650
|
+
label: typeof f.label === "string" && f.label.trim() ? f.label.trim() : void 0
|
|
2651
|
+
});
|
|
2652
|
+
}
|
|
2653
|
+
if (filtersBad) break;
|
|
2654
|
+
rowFilters[table] = parsed;
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
if (filtersBad) {
|
|
2659
|
+
out.push(bad(filtersBad));
|
|
2660
|
+
continue;
|
|
2661
|
+
}
|
|
2662
|
+
const rawLimits = e.limits;
|
|
2663
|
+
const num = (v) => typeof v === "number" && Number.isInteger(v) && v > 0 ? v : void 0;
|
|
2664
|
+
const limits = rawLimits ? { maxRows: num(rawLimits.maxRows), maxExecutionSeconds: num(rawLimits.maxExecutionSeconds) } : void 0;
|
|
2665
|
+
out.push({
|
|
2666
|
+
sourceId,
|
|
2667
|
+
label,
|
|
2668
|
+
...schema2 ? { schema: schema2 } : {},
|
|
2669
|
+
...readableTables ? { readableTables } : {},
|
|
2670
|
+
...Object.keys(rowFilters).length ? { rowFilters } : {},
|
|
2671
|
+
...limits ? { limits } : {}
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
return out;
|
|
2537
2675
|
}
|
|
2676
|
+
|
|
2677
|
+
// src/handlers/data-access/shape.ts
|
|
2538
2678
|
function scopedForm(config2) {
|
|
2539
2679
|
if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) return void 0;
|
|
2540
2680
|
const c = config2;
|
|
2541
2681
|
return "allowedSources" in c || "policies" in c ? c : void 0;
|
|
2542
2682
|
}
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
if (!warnedBadSources) {
|
|
2547
|
-
warnedBadSources = true;
|
|
2548
|
-
logger.warn(
|
|
2549
|
-
`[RequestIdentity] "allowedSources" is ${typeof value}, not a list of source ids \u2014 treating it as NO sources rather than all of them. Fix the config: "allowedSources": ["mssql-1a2b"].`
|
|
2550
|
-
);
|
|
2551
|
-
}
|
|
2552
|
-
return [];
|
|
2553
|
-
}
|
|
2554
|
-
const out = [];
|
|
2555
|
-
for (const item of value) {
|
|
2556
|
-
if (typeof item !== "string" || !item.trim()) continue;
|
|
2557
|
-
const id = item.trim();
|
|
2558
|
-
if (!out.includes(id)) out.push(id);
|
|
2559
|
-
}
|
|
2560
|
-
return out;
|
|
2561
|
-
}
|
|
2562
|
-
function accessFromConfig(config2) {
|
|
2563
|
-
const scoped = scopedForm(config2);
|
|
2564
|
-
return {
|
|
2565
|
-
policies: policiesFromConfig(config2),
|
|
2566
|
-
allowedSources: scoped ? allowedSourcesFrom(scoped.allowedSources) : null
|
|
2567
|
-
};
|
|
2568
|
-
}
|
|
2683
|
+
|
|
2684
|
+
// src/handlers/data-access/v1/policies.ts
|
|
2685
|
+
var warnedUnlabelled = false;
|
|
2569
2686
|
function exceptionsFromConfig(raw) {
|
|
2570
2687
|
if (!Array.isArray(raw)) return [];
|
|
2571
2688
|
const str = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
@@ -2644,16 +2761,55 @@ function policiesFromConfig(config2) {
|
|
|
2644
2761
|
}
|
|
2645
2762
|
return out;
|
|
2646
2763
|
}
|
|
2764
|
+
|
|
2765
|
+
// src/handlers/request-identity.ts
|
|
2766
|
+
function graphVersionOf(v) {
|
|
2767
|
+
if (v === "draft") return "draft";
|
|
2768
|
+
return Number.isInteger(v) && v > 0 ? v : void 0;
|
|
2769
|
+
}
|
|
2770
|
+
function allowedSourcesFrom(value) {
|
|
2771
|
+
if (value === void 0 || value === null) return null;
|
|
2772
|
+
if (!Array.isArray(value)) {
|
|
2773
|
+
if (!warnedBadSources) {
|
|
2774
|
+
warnedBadSources = true;
|
|
2775
|
+
logger.warn(
|
|
2776
|
+
`[RequestIdentity] "allowedSources" is ${typeof value}, not a list of source ids \u2014 treating it as NO sources rather than all of them. Fix the config: "allowedSources": ["mssql-1a2b"].`
|
|
2777
|
+
);
|
|
2778
|
+
}
|
|
2779
|
+
return [];
|
|
2780
|
+
}
|
|
2781
|
+
const out = [];
|
|
2782
|
+
for (const item of value) {
|
|
2783
|
+
if (typeof item !== "string" || !item.trim()) continue;
|
|
2784
|
+
const id = item.trim();
|
|
2785
|
+
if (!out.includes(id)) out.push(id);
|
|
2786
|
+
}
|
|
2787
|
+
return out;
|
|
2788
|
+
}
|
|
2789
|
+
function accessFromConfig(config2) {
|
|
2790
|
+
const scoped = scopedForm(config2);
|
|
2791
|
+
const allowedSources = scoped ? allowedSourcesFrom(scoped.allowedSources) : null;
|
|
2792
|
+
if (scoped?.version === 2) {
|
|
2793
|
+
return {
|
|
2794
|
+
policies: [],
|
|
2795
|
+
constraintPolicies: constraintPoliciesFromConfig(scoped.policies),
|
|
2796
|
+
allowedSources
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
return { policies: policiesFromConfig(config2), constraintPolicies: [], allowedSources };
|
|
2800
|
+
}
|
|
2647
2801
|
var warnedUntrusted = false;
|
|
2648
2802
|
var warnedAnonymous = false;
|
|
2649
|
-
var warnedUnlabelled = false;
|
|
2650
2803
|
var warnedBadSources = false;
|
|
2651
2804
|
var announcedVerified = false;
|
|
2652
2805
|
var reportedIdMismatches = /* @__PURE__ */ new Set();
|
|
2653
2806
|
function runWithMessageIdentity(message, fn) {
|
|
2654
2807
|
const verified = message?.authContext;
|
|
2655
2808
|
const requestId = message?.id;
|
|
2656
|
-
const { policies, allowedSources } = verified ? accessFromConfig(verified.config) : accessFromConfig(message?.payload?.config);
|
|
2809
|
+
const { policies, constraintPolicies, allowedSources } = verified ? accessFromConfig(verified.config) : accessFromConfig(message?.payload?.config);
|
|
2810
|
+
logger.info(
|
|
2811
|
+
`[RequestIdentity] ${message?.type ?? "?"}: access from ${verified ? "authContext" : "payload"}${verified && verified.config == null ? " (none on it)" : ""}, allowedSources=${allowedSources === null ? "every source" : `[${allowedSources.join(", ") || "NONE"}]`}, ${constraintPolicies.length} constraint policy(ies), ${policies.length} row policy(ies)`
|
|
2812
|
+
);
|
|
2657
2813
|
if (verified?.userId) {
|
|
2658
2814
|
const claimedId = message?.payload?.userId;
|
|
2659
2815
|
if (!announcedVerified) {
|
|
@@ -2678,6 +2834,7 @@ function runWithMessageIdentity(message, fn) {
|
|
|
2678
2834
|
role: verified.role,
|
|
2679
2835
|
requestId,
|
|
2680
2836
|
policies,
|
|
2837
|
+
constraintPolicies,
|
|
2681
2838
|
// `?? undefined` so "no restriction" leaves the key off the context
|
|
2682
2839
|
// entirely; `[]` is passed through as the deny-everything it means.
|
|
2683
2840
|
allowedSources: allowedSources ?? void 0,
|
|
@@ -2695,7 +2852,10 @@ function runWithMessageIdentity(message, fn) {
|
|
|
2695
2852
|
"[RequestIdentity] Using the client-supplied payload.userId as the principal \u2014 the relay is not stamping a verified authContext yet, so this identity is NOT trustworthy. Fine for plumbing; do not enable policy enforcement until the relay change lands."
|
|
2696
2853
|
);
|
|
2697
2854
|
}
|
|
2698
|
-
return runAsUser(
|
|
2855
|
+
return runAsUser(
|
|
2856
|
+
{ userId: claimed, requestId, policies, constraintPolicies, allowedSources: allowedSources ?? void 0 },
|
|
2857
|
+
fn
|
|
2858
|
+
);
|
|
2699
2859
|
}
|
|
2700
2860
|
if (!warnedAnonymous) {
|
|
2701
2861
|
warnedAnonymous = true;
|
|
@@ -30096,6 +30256,7 @@ export {
|
|
|
30096
30256
|
bridgePreamble,
|
|
30097
30257
|
captureContext,
|
|
30098
30258
|
clearContextTokens,
|
|
30259
|
+
constraintPoliciesFromConfig,
|
|
30099
30260
|
contextTokenCount,
|
|
30100
30261
|
createCodexSession,
|
|
30101
30262
|
createCodingAgentSession,
|