@zackbart/connecta 0.37.0 → 0.38.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/CHANGELOG.md +80 -0
- package/README.md +2 -2
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/cloudflare-access.d.ts +3 -1
- package/dist/auth/cloudflare-access.js +22 -11
- package/dist/auth/downstream-oauth.js +195 -270
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/oauth-grant.js +11 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +2 -2
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-admission.js +5 -0
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +26 -7
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-revision.js +9 -0
- package/dist/catalog-service.js +51 -20
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +41 -14
- package/dist/config.js +21 -71
- package/dist/connector-caller.js +1 -1
- package/dist/connectors/api-connector.js +4 -3
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/remote-mcp.js +93 -69
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/credentials.js +3 -0
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/downstream-input.js +2 -2
- package/dist/execute.js +161 -116
- package/dist/executor-result.js +2 -1
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-runtime.js +5 -3
- package/dist/guest-types.d.ts +5 -5
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +2 -2
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +1 -1
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +1 -1
- package/dist/http/safety.js +1 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +1 -7
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.js +1 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +8 -2
- package/dist/meta-tools.js +27 -27
- package/dist/native-tools.js +2 -1
- package/dist/oauth-handoff.js +2 -2
- package/dist/operation-vocabulary.js +1 -1
- package/dist/operator-record.d.ts +8 -2
- package/dist/operator-record.js +18 -50
- package/dist/operator-ui/contract.d.ts +4 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +7 -2
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +1 -3
- package/dist/providers/_shared/rest/value-safety.js +26 -28
- package/dist/providers/breeze/api.js +2 -7
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/tools.js +3 -4
- package/dist/providers/ccb/api.js +1 -6
- package/dist/providers/ccb/index.js +2 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +2 -2
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/docs/tools.js +2 -10
- package/dist/providers/drive/tools.js +12 -25
- package/dist/providers/forms/tools.js +1 -9
- package/dist/providers/gmail/tools.js +5 -21
- package/dist/providers/overflow/api.js +1 -3
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +2 -1
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/tools.js +2 -1
- package/dist/providers/sheets/tools.js +1 -9
- package/dist/providers/slides/tools.js +4 -13
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/tithely/api.js +2 -7
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +1 -6
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/tools.js +2 -1
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +4 -1
- package/dist/registry.js +35 -22
- package/dist/request-principal.js +15 -2
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +1 -1
- package/dist/routes/dispatch.js +10 -0
- package/dist/routes/mcp.js +23 -21
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +53 -7
- package/dist/routes/shared.d.ts +8 -8
- package/dist/routes/shared.js +22 -15
- package/dist/routes/ui-config.js +6 -6
- package/dist/routes/ui-data.js +2 -2
- package/dist/routes/ui.js +9 -4
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +1 -1
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/sent-secrets.d.ts +14 -6
- package/dist/sent-secrets.js +231 -85
- package/dist/server.js +4 -4
- package/dist/skills.js +8 -35
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +29 -124
- package/dist/storage/sql-native.js +14 -111
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +1 -12
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +4 -3
- package/dist/ui.js +3 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/documentation/README.md +6 -42
- package/documentation/architecture.md +190 -507
- package/documentation/auth.md +172 -58
- package/documentation/code-mode.md +98 -66
- package/documentation/deploying.md +19 -40
- package/documentation/integrating.md +8 -19
- package/documentation/meta-tools.md +23 -6
- package/documentation/migration-0.33.md +5 -5
- package/documentation/migration-0.34.md +2 -4
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +19 -119
- package/documentation/operator-ui.md +46 -8
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/examples/worker/AGENTS.md +4 -6
- package/examples/worker/README.md +20 -115
- package/guides/connecting-agents.md +7 -30
- package/guides/how-it-works.md +2 -2
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
package/dist/sent-secrets.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Effect } from "effect";
|
|
|
5
5
|
import { classificationCode, ConnectorCallError } from "./errors.js";
|
|
6
6
|
import { credentialUrlViews } from "./credential-url.js";
|
|
7
7
|
import { RequestScope, requestOwner } from "./runtime/request-scope.js";
|
|
8
|
-
import { viewSecrets
|
|
8
|
+
import { viewSecrets } from "./runtime/call.js";
|
|
9
9
|
import { visitPrivateCallArguments } from "./argument-redaction.js";
|
|
10
10
|
import { keptErrorName, redactedCopyOf } from "./redaction-identity.js";
|
|
11
11
|
import { REDACTED } from "./redaction-marker.js";
|
|
@@ -127,6 +127,12 @@ const TEXT = 0;
|
|
|
127
127
|
const NUMBER = 1;
|
|
128
128
|
const BINARY = 2;
|
|
129
129
|
const BYTES = 3;
|
|
130
|
+
// A typed binary's joined numbers are withheld together, including on an error.
|
|
131
|
+
const NUMBERS = 4;
|
|
132
|
+
// A changed numeric diagnostic is absent from the error's text projection.
|
|
133
|
+
const ERROR_NUMBER = 5;
|
|
134
|
+
const numeric = (kind) => kind === NUMBER || kind === NUMBERS || kind === ERROR_NUMBER;
|
|
135
|
+
const replacement = (kind) => (kind === BINARY || kind === ERROR_NUMBER ? "" : REDACTED);
|
|
130
136
|
/** An accessor met while looking up an inherited field: never invoked. */
|
|
131
137
|
const WITHHELD_ACCESSOR = Symbol("accessor");
|
|
132
138
|
/** The engine's own getters for what a typed array, DataView or ArrayBuffer holds, so no
|
|
@@ -682,7 +688,7 @@ function headerSpace(text, at) {
|
|
|
682
688
|
}
|
|
683
689
|
/**
|
|
684
690
|
* The echo's spans with the value of every sensitive header line (`headerLine`'s names) added,
|
|
685
|
-
* read on the text before the echo is replaced
|
|
691
|
+
* read on the text before the echo is replaced. An echoed private value can take part of a
|
|
686
692
|
* header's name, its `:`, or a credential prefix such as `Bearer `, so the line cannot be read
|
|
687
693
|
* after replacement; read here, the whole value goes, every cookie pair and any short tail with
|
|
688
694
|
* it. A line that is not a sensitive header keeps its text. One forward pass; nothing is kept.
|
|
@@ -1310,6 +1316,24 @@ export class SentSecrets {
|
|
|
1310
1316
|
text(value) {
|
|
1311
1317
|
return this.idle() ? value : this.project(value, this.scan(true)).value;
|
|
1312
1318
|
}
|
|
1319
|
+
/** An SDK response body, checked after it has been fully read so late registrations
|
|
1320
|
+
* still apply. An idle set leaves the original serialization untouched. */
|
|
1321
|
+
serializedAgentOutput(body, json) {
|
|
1322
|
+
if (this.idle())
|
|
1323
|
+
return body;
|
|
1324
|
+
if (json) {
|
|
1325
|
+
// Structured redaction joins text blocks, including secrets split across them.
|
|
1326
|
+
try {
|
|
1327
|
+
return JSON.stringify(redactAgentOutput(this, JSON.parse(body)));
|
|
1328
|
+
}
|
|
1329
|
+
catch {
|
|
1330
|
+
/* Non-JSON diagnostics still pass through the text boundary. */
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
// A second serialized pass over parsed JSON would mistake a blob's encoding
|
|
1334
|
+
// for credential bytes. Only non-JSON bodies take this text path.
|
|
1335
|
+
return redactAgentOutput(this, body);
|
|
1336
|
+
}
|
|
1313
1337
|
/** A plain-data snapshot of `value` with every match removed: own enumerable data properties
|
|
1314
1338
|
* (an error's own fields, non-enumerable ones included), read once each. Accessors are never
|
|
1315
1339
|
* invoked, except an error's stack and the accessors its class defines, read once into the
|
|
@@ -1566,10 +1590,8 @@ export class SentSecrets {
|
|
|
1566
1590
|
* charged to the call's budget, and a part it cannot pay for is withheld. A `bounded` lane is
|
|
1567
1591
|
* text cut short, which also loses an unfinished tail. Nothing else matches a string that is
|
|
1568
1592
|
* returned: the matching steps below are called only from here. */
|
|
1569
|
-
checked(parts, kinds, scan, bounded = false) {
|
|
1570
|
-
|
|
1571
|
-
this.encodedLeaves(parts, kinds, safe, scan);
|
|
1572
|
-
return safe;
|
|
1593
|
+
checked(parts, kinds, scan, bounded = false, normalize) {
|
|
1594
|
+
return this.safeParts(parts, kinds, scan, bounded, normalize);
|
|
1573
1595
|
}
|
|
1574
1596
|
/** Cursor claims: the whole text and its encoding under the boundary's rules, then every
|
|
1575
1597
|
* string in the JSON under payload rules. Keys and numbers are Connecta's own names and
|
|
@@ -1690,7 +1712,7 @@ export class SentSecrets {
|
|
|
1690
1712
|
// Binary and bytes any match changed are withheld whole already.
|
|
1691
1713
|
const kind = kinds[index];
|
|
1692
1714
|
const text = safe[index];
|
|
1693
|
-
if (kind
|
|
1715
|
+
if (numeric(kind) || text === REDACTED || this.memo.get(text) === true)
|
|
1694
1716
|
continue;
|
|
1695
1717
|
if ((!scan.payload && this.memo.has(text)) || (kind !== TEXT && text !== parts[index]))
|
|
1696
1718
|
continue;
|
|
@@ -1755,11 +1777,63 @@ export class SentSecrets {
|
|
|
1755
1777
|
safe[pairLeaves[pair]] = REDACTED;
|
|
1756
1778
|
}
|
|
1757
1779
|
}
|
|
1758
|
-
/**
|
|
1759
|
-
*
|
|
1760
|
-
*
|
|
1761
|
-
*
|
|
1762
|
-
|
|
1780
|
+
/** Check replacements as returned, including their placeholder seams. Numbers touched by
|
|
1781
|
+
* the first scan get one chance beside the replaced strings; every later hit withholds its
|
|
1782
|
+
* entire part. Each changing round therefore removes at least one remaining part. Unchanged
|
|
1783
|
+
* lanes pay for no retry. Large lanes recheck changed parts and the seams touching them; an
|
|
1784
|
+
* unaffordable check withholds every part it read and all original retention candidates. */
|
|
1785
|
+
settleParts(parts, safe, scan, numbers = [], leafByLeaf = false, kinds, normalize) {
|
|
1786
|
+
let touched = new Set(parts.flatMap((part, index) => (part !== safe[index] ? [index] : [])));
|
|
1787
|
+
if (!touched.size)
|
|
1788
|
+
return safe;
|
|
1789
|
+
const lane = [...safe];
|
|
1790
|
+
for (const index of numbers)
|
|
1791
|
+
lane[index] = parts[index];
|
|
1792
|
+
normalize?.(lane);
|
|
1793
|
+
while (touched.size) {
|
|
1794
|
+
// Pay for round bookkeeping even when no registered matcher could scan (for
|
|
1795
|
+
// example, an oversized private registration governed only by withholdRuns).
|
|
1796
|
+
if (!spend(scan, lane.length)) {
|
|
1797
|
+
const withheld = lane.map((part, index) => part && replacement(kinds?.[index]));
|
|
1798
|
+
normalize?.(withheld);
|
|
1799
|
+
return withheld;
|
|
1800
|
+
}
|
|
1801
|
+
let checked;
|
|
1802
|
+
const recheck = { parts: touched, complete: true };
|
|
1803
|
+
const matches = leafByLeaf ? undefined : this.spansOf(lane.join(""), scan, scan.payload);
|
|
1804
|
+
if (matches && !matches.deep)
|
|
1805
|
+
checked = replaceParts(lane, matches.spans);
|
|
1806
|
+
else
|
|
1807
|
+
checked = this.leafByLeaf(lane, scan, undefined, recheck);
|
|
1808
|
+
const limit = this.limit(scan);
|
|
1809
|
+
if (limit < Infinity)
|
|
1810
|
+
checked = withholdRuns(lane, checked, limit);
|
|
1811
|
+
if (!recheck.complete)
|
|
1812
|
+
for (const index of numbers)
|
|
1813
|
+
if (checked[index] === parts[index])
|
|
1814
|
+
checked[index] = replacement(kinds?.[index]);
|
|
1815
|
+
normalize?.(checked);
|
|
1816
|
+
const next = new Set();
|
|
1817
|
+
for (let index = 0; index < lane.length; index++) {
|
|
1818
|
+
if (checked[index] === lane[index] || (lane[index] === REDACTED && checked[index] !== ""))
|
|
1819
|
+
continue;
|
|
1820
|
+
const withheld = replacement(kinds?.[index]);
|
|
1821
|
+
// Whole-field groups project one marker and empty remaining parts.
|
|
1822
|
+
const value = checked[index] === "" ? "" : withheld;
|
|
1823
|
+
if (lane[index] === value)
|
|
1824
|
+
continue;
|
|
1825
|
+
lane[index] = value;
|
|
1826
|
+
next.add(index);
|
|
1827
|
+
}
|
|
1828
|
+
touched = next;
|
|
1829
|
+
}
|
|
1830
|
+
return lane;
|
|
1831
|
+
}
|
|
1832
|
+
/** The parts' joined text, with each part's replacement. A touched number is checked again
|
|
1833
|
+
* after string replacement before withholding it whole. Tiny or short whole private values,
|
|
1834
|
+
* deep matches and unchecked runs still withhold changed numbers whole. A bounded lane also
|
|
1835
|
+
* loses an unfinished tail, and withholds every touched number without a second check. */
|
|
1836
|
+
safeParts(parts, kinds, scan, bounded, normalize) {
|
|
1763
1837
|
if (!parts.length)
|
|
1764
1838
|
return parts;
|
|
1765
1839
|
const text = parts.join("");
|
|
@@ -1769,29 +1843,63 @@ export class SentSecrets {
|
|
|
1769
1843
|
return parts.map((part) => part && REDACTED);
|
|
1770
1844
|
if (bounded)
|
|
1771
1845
|
this.openSpans(text, matches.spans);
|
|
1772
|
-
|
|
1846
|
+
const numbers = [];
|
|
1847
|
+
let safe = matches ? replaceParts(parts, matches.spans) : this.leafByLeaf(parts, scan, kinds, undefined, numbers);
|
|
1848
|
+
if (matches && !bounded && !matches.deep)
|
|
1849
|
+
for (let index = 0; index < parts.length; index++)
|
|
1850
|
+
if (numeric(kinds[index]) && safe[index] !== parts[index])
|
|
1851
|
+
numbers.push(index);
|
|
1773
1852
|
if (matches?.deep)
|
|
1774
1853
|
safe = safe.map((next, index) => (parts[index].includes("\\") ? REDACTED : next));
|
|
1854
|
+
const whole = new Set();
|
|
1775
1855
|
if (scan.payload && this.smallForms)
|
|
1776
|
-
safe = safe.map((next, index) =>
|
|
1856
|
+
safe = safe.map((next, index) => {
|
|
1857
|
+
// Typed numeric groups checked their individual whole values while walking.
|
|
1858
|
+
if (kinds[index] === NUMBERS || !this.whole(parts[index]))
|
|
1859
|
+
return next;
|
|
1860
|
+
whole.add(index);
|
|
1861
|
+
return REDACTED;
|
|
1862
|
+
});
|
|
1777
1863
|
const limit = this.limit(scan);
|
|
1778
1864
|
if (limit < Infinity)
|
|
1779
1865
|
safe = withholdRuns(parts, safe, limit);
|
|
1780
|
-
|
|
1866
|
+
safe = safe.map((next, index) => (next !== parts[index] && numeric(kinds[index]) ? REDACTED : next));
|
|
1867
|
+
this.encodedLeaves(parts, kinds, safe, scan);
|
|
1868
|
+
// Declared binary leaves as the checked encoding of the marker, not its partial text.
|
|
1869
|
+
let marker;
|
|
1870
|
+
safe = safe.map((next, index) => {
|
|
1871
|
+
if (next === parts[index] || kinds[index] !== BINARY)
|
|
1872
|
+
return next;
|
|
1873
|
+
const encoded = btoa(REDACTED);
|
|
1874
|
+
marker ??= this.checked([encoded], [TEXT], scan)[0] === encoded ? encoded : "";
|
|
1875
|
+
return marker;
|
|
1876
|
+
});
|
|
1877
|
+
safe = safe.map((next, index) => (next !== parts[index] && kinds[index] === ERROR_NUMBER ? "" : next));
|
|
1878
|
+
normalize?.(safe);
|
|
1879
|
+
return this.settleParts(parts, safe, scan, numbers.filter((index) => !whole.has(index) && limit === Infinity), !matches, kinds, normalize);
|
|
1781
1880
|
}
|
|
1782
1881
|
/** Leaves too large to match joined within the work budget: each is matched alone, in order,
|
|
1783
1882
|
* and one that does not fit is withheld without spending what a smaller one needs. A run
|
|
1784
1883
|
* crossing from one kept leaf into the next, through any short leaves between them, is then
|
|
1785
1884
|
* matched SEAM_UNITS deep on each side of the seam. */
|
|
1786
|
-
leafByLeaf(parts, scan) {
|
|
1787
|
-
const spans = parts.map((part) => {
|
|
1885
|
+
leafByLeaf(parts, scan, kinds, recheck, candidates) {
|
|
1886
|
+
const spans = parts.map((part, index) => {
|
|
1887
|
+
// Only changed parts and their seams need checking on a retry. Kept neighbours
|
|
1888
|
+
// remain exactly as returned, even when their full text would not fit again.
|
|
1889
|
+
if (recheck && !recheck.parts.has(index))
|
|
1890
|
+
return [];
|
|
1788
1891
|
const matches = this.spansOf(part, scan, scan.payload);
|
|
1892
|
+
if (recheck && (!matches || matches.deep))
|
|
1893
|
+
recheck.complete = false;
|
|
1789
1894
|
return !matches || (matches.deep && part.includes("\\")) ? undefined : matches.spans;
|
|
1790
1895
|
});
|
|
1791
1896
|
let pieces = [];
|
|
1792
1897
|
const seam = () => {
|
|
1793
1898
|
const text = pieces.map((piece) => piece.text).join("");
|
|
1794
|
-
const
|
|
1899
|
+
const needed = pieces.length > 1 && (!recheck || pieces.some((piece) => recheck.parts.has(piece.index)));
|
|
1900
|
+
const matches = needed ? this.spansOf(text, scan, scan.payload) : { spans: [], deep: false };
|
|
1901
|
+
if (recheck && (!matches || matches.deep))
|
|
1902
|
+
recheck.complete = false;
|
|
1795
1903
|
const ranges = matches ? merged(matches.spans) : [];
|
|
1796
1904
|
let offset = 0;
|
|
1797
1905
|
for (const piece of pieces) {
|
|
@@ -1806,7 +1914,9 @@ export class SentSecrets {
|
|
|
1806
1914
|
pieces = [];
|
|
1807
1915
|
};
|
|
1808
1916
|
parts.forEach((part, index) => {
|
|
1809
|
-
|
|
1917
|
+
// A failed retry, or a marker already introduced while walking, still
|
|
1918
|
+
// contains this part in the returned lane. Keep its unchecked seams.
|
|
1919
|
+
if (!spans[index] && !recheck && part !== REDACTED)
|
|
1810
1920
|
return seam();
|
|
1811
1921
|
if (part.length <= 2 * SEAM_UNITS)
|
|
1812
1922
|
return void pieces.push({ index, start: 0, text: part });
|
|
@@ -1815,10 +1925,21 @@ export class SentSecrets {
|
|
|
1815
1925
|
pieces.push({ index, start: part.length - SEAM_UNITS, text: part.slice(-SEAM_UNITS) });
|
|
1816
1926
|
});
|
|
1817
1927
|
seam();
|
|
1818
|
-
|
|
1928
|
+
const safe = parts.map((part, index) => {
|
|
1819
1929
|
const own = spans[index];
|
|
1820
1930
|
return !own ? part && REDACTED : own.length ? replace(part, own) : part;
|
|
1821
1931
|
});
|
|
1932
|
+
const numbers = kinds
|
|
1933
|
+
? kinds.flatMap((kind, index) => numeric(kind) && spans[index]?.length && safe[index] !== parts[index] ? [index] : [])
|
|
1934
|
+
: [];
|
|
1935
|
+
if (candidates) {
|
|
1936
|
+
for (const index of numbers)
|
|
1937
|
+
candidates.push(index);
|
|
1938
|
+
return safe;
|
|
1939
|
+
}
|
|
1940
|
+
return kinds
|
|
1941
|
+
? this.settleParts(parts, safe.map((next, index) => (next !== parts[index] && numeric(kinds[index]) ? REDACTED : next)), scan, numbers, true, kinds)
|
|
1942
|
+
: safe;
|
|
1822
1943
|
}
|
|
1823
1944
|
/** Serialized JSON whose keys, syntax and leaves together hold a K-unit run of a form: one
|
|
1824
1945
|
* spanning tokens, which no leaf holds alone, withholds the whole text. A run inside one
|
|
@@ -1891,10 +2012,12 @@ export class SentSecrets {
|
|
|
1891
2012
|
const active = new Set();
|
|
1892
2013
|
// Keys and scalars repeat across rows; each is checked once per call.
|
|
1893
2014
|
const seen = { keys: new Map(), scalars: new Map() };
|
|
1894
|
-
//
|
|
2015
|
+
// An error name may contain parsed JSON. Any change collapses all of its
|
|
2016
|
+
// projected leaves to one placeholder before the surrounding lane settles.
|
|
1895
2017
|
const named = [];
|
|
1896
|
-
//
|
|
1897
|
-
|
|
2018
|
+
// Non-byte typed arrays have an alternate numeric projection, in the same slots as
|
|
2019
|
+
// their returned bytes. Neither representation can interrupt the other's neighbours.
|
|
2020
|
+
const elements = new Map();
|
|
1898
2021
|
// The accessors each prototype's chain defines, found once per call.
|
|
1899
2022
|
const chains = new Map();
|
|
1900
2023
|
let changed = false;
|
|
@@ -1903,7 +2026,8 @@ export class SentSecrets {
|
|
|
1903
2026
|
if (owner >= 0)
|
|
1904
2027
|
nested[owner].dirty = true;
|
|
1905
2028
|
};
|
|
1906
|
-
const withheld = (owner) => {
|
|
2029
|
+
const withheld = (frame, slot, owner) => {
|
|
2030
|
+
leaf(REDACTED, TEXT, frame, slot, owner);
|
|
1907
2031
|
touch(owner);
|
|
1908
2032
|
return REDACTED;
|
|
1909
2033
|
};
|
|
@@ -2004,8 +2128,9 @@ export class SentSecrets {
|
|
|
2004
2128
|
if (level < MAX_DECODE_LEVELS && /^\s*["[{]/.test(item) && spend(scan, item.length)) {
|
|
2005
2129
|
const parsed = parse(item);
|
|
2006
2130
|
if (parsed) {
|
|
2007
|
-
|
|
2008
|
-
|
|
2131
|
+
if (this.spansTokens(item, scan))
|
|
2132
|
+
return withheld(frame, slot, owner);
|
|
2133
|
+
const entry = { frame, slot, json: undefined, owner, dirty: false };
|
|
2009
2134
|
const index = nested.push(entry) - 1;
|
|
2010
2135
|
// Parsed JSON is this walk's own, so it is written in place.
|
|
2011
2136
|
const holder = { target: entry, shared: false, parent: undefined, slot: "json" };
|
|
@@ -2017,25 +2142,25 @@ export class SentSecrets {
|
|
|
2017
2142
|
return item;
|
|
2018
2143
|
}
|
|
2019
2144
|
if (typeof item === "number" || typeof item === "bigint") {
|
|
2020
|
-
leaf(String(item), NUMBER, frame, slot, owner);
|
|
2145
|
+
leaf(String(item), frame.target instanceof Error ? ERROR_NUMBER : NUMBER, frame, slot, owner);
|
|
2021
2146
|
return item;
|
|
2022
2147
|
}
|
|
2023
2148
|
if (typeof item === "boolean" || item === null)
|
|
2024
|
-
return check(seen.scalars, String(item), NUMBER) === String(item) ? item : withheld(owner);
|
|
2149
|
+
return check(seen.scalars, String(item), NUMBER) === String(item) ? item : withheld(frame, slot, owner);
|
|
2025
2150
|
// A function carries no data, and every exit's serializer would call a toJSON it holds; a
|
|
2026
2151
|
// symbol carries none either. Both are withheld.
|
|
2027
2152
|
if (typeof item === "function" || typeof item === "symbol")
|
|
2028
|
-
return withheld(owner);
|
|
2153
|
+
return withheld(frame, slot, owner);
|
|
2029
2154
|
if (typeof item !== "object")
|
|
2030
2155
|
return item;
|
|
2031
2156
|
if (active.has(item) || depth > MAX_WALK_DEPTH)
|
|
2032
|
-
return withheld(owner);
|
|
2157
|
+
return withheld(frame, slot, owner);
|
|
2033
2158
|
active.add(item);
|
|
2034
2159
|
try {
|
|
2035
2160
|
return container(item, frame, slot, depth, level, owner, owned);
|
|
2036
2161
|
}
|
|
2037
2162
|
catch {
|
|
2038
|
-
return withheld(owner); // A proxy trap or a toJSON that threw.
|
|
2163
|
+
return withheld(frame, slot, owner); // A proxy trap or a toJSON that threw.
|
|
2039
2164
|
}
|
|
2040
2165
|
finally {
|
|
2041
2166
|
active.delete(item);
|
|
@@ -2063,24 +2188,36 @@ export class SentSecrets {
|
|
|
2063
2188
|
leaf(text, BINARY, frame, slot, owner);
|
|
2064
2189
|
return text;
|
|
2065
2190
|
};
|
|
2066
|
-
/** Typed binary
|
|
2067
|
-
*
|
|
2068
|
-
*
|
|
2069
|
-
const typed = ({ bytes, elements }, frame, slot, owner) => {
|
|
2070
|
-
const numbers =
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2191
|
+
/** Typed binary is one whole-withheld numeric part: its byte numbers are adjacent to
|
|
2192
|
+
* neighbours in the returned lane, and original elements substitute in a separate lane.
|
|
2193
|
+
* Individual numeric whole-value matches and the raw byte view are also checked. */
|
|
2194
|
+
const typed = ({ bytes, elements: constructor }, frame, slot, owner) => {
|
|
2195
|
+
const numbers = constructor ? new constructor(bytes.buffer) : undefined;
|
|
2196
|
+
let clean = fits(bytes.length + (numbers?.length ?? 0) + 1) && spend(scan, Math.ceil(bytes.length / 8));
|
|
2197
|
+
const decimal = (values) => {
|
|
2198
|
+
const text = [];
|
|
2199
|
+
for (let index = 0; index < values.length; index++) {
|
|
2200
|
+
const number = values === bytes ? DECIMAL[bytes[index]] : String(values[index]);
|
|
2201
|
+
if (scan.payload && this.smallForms && this.whole(number))
|
|
2202
|
+
clean = false;
|
|
2203
|
+
text.push(number);
|
|
2204
|
+
}
|
|
2205
|
+
return text.join("");
|
|
2206
|
+
};
|
|
2207
|
+
// Refuse oversize arrays before building their numeric projection.
|
|
2208
|
+
if (!clean) {
|
|
2209
|
+
return withheld(frame, slot, owner);
|
|
2210
|
+
}
|
|
2211
|
+
const text = decimal(bytes);
|
|
2212
|
+
const original = numbers ? decimal(numbers) : undefined;
|
|
2213
|
+
const binary = latin1(bytes);
|
|
2214
|
+
clean &&= this.checked([binary], [BYTES], scan)[0] === binary;
|
|
2215
|
+
if (clean && original !== undefined)
|
|
2216
|
+
elements.set(parts.length, original);
|
|
2217
|
+
leaf(clean ? text : REDACTED, NUMBERS, frame, slot, owner);
|
|
2218
|
+
if (!clean)
|
|
2219
|
+
touch(owner);
|
|
2220
|
+
return clean ? bytes : REDACTED;
|
|
2084
2221
|
};
|
|
2085
2222
|
const container = (item, frame, slot, depth, level, owner, owned) => {
|
|
2086
2223
|
const error = item instanceof Error;
|
|
@@ -2094,7 +2231,7 @@ export class SentSecrets {
|
|
|
2094
2231
|
// What serialization would read: a toJSON result, checked in place of the container.
|
|
2095
2232
|
const toJSON = inherited(item, "toJSON", WITHHELD_ACCESSOR);
|
|
2096
2233
|
if (toJSON === WITHHELD_ACCESSOR)
|
|
2097
|
-
return withheld(owner);
|
|
2234
|
+
return withheld(frame, slot, owner);
|
|
2098
2235
|
if (typeof toJSON === "function") {
|
|
2099
2236
|
const result = toJSON.call(item, String(slot));
|
|
2100
2237
|
if (result !== null && typeof result === "object")
|
|
@@ -2111,7 +2248,7 @@ export class SentSecrets {
|
|
|
2111
2248
|
const list = (item, frame, slot, depth, level, owner, owned) => {
|
|
2112
2249
|
const length = item.length;
|
|
2113
2250
|
if (!fits(length))
|
|
2114
|
-
return withheld(owner);
|
|
2251
|
+
return withheld(frame, slot, owner);
|
|
2115
2252
|
const own = walked(frame, item, slot, owned);
|
|
2116
2253
|
for (let index = 0; index < length; index++) {
|
|
2117
2254
|
const descriptor = Object.getOwnPropertyDescriptor(item, index);
|
|
@@ -2120,7 +2257,7 @@ export class SentSecrets {
|
|
|
2120
2257
|
// and copied in, an accessor is withheld unread, and nothing leaves the hole as it is.
|
|
2121
2258
|
const found = owned ? undefined : inherited(item, String(index), WITHHELD_ACCESSOR);
|
|
2122
2259
|
if (found === WITHHELD_ACCESSOR)
|
|
2123
|
-
replace(own, index,
|
|
2260
|
+
replace(own, index, withheld(own, index, owner), owner);
|
|
2124
2261
|
else if (found !== undefined) {
|
|
2125
2262
|
replaced.push([own, index, found]);
|
|
2126
2263
|
const next = visit(found, own, index, depth + 1, level, owner, owned);
|
|
@@ -2130,7 +2267,7 @@ export class SentSecrets {
|
|
|
2130
2267
|
continue;
|
|
2131
2268
|
}
|
|
2132
2269
|
if (!("value" in descriptor)) {
|
|
2133
|
-
replace(own, index,
|
|
2270
|
+
replace(own, index, withheld(own, index, owner), owner);
|
|
2134
2271
|
continue;
|
|
2135
2272
|
}
|
|
2136
2273
|
const next = visit(descriptor.value, own, index, depth + 1, level, owner, owned);
|
|
@@ -2147,7 +2284,7 @@ export class SentSecrets {
|
|
|
2147
2284
|
const record = (item, error, frame, slot, depth, level, owner, owned) => {
|
|
2148
2285
|
const names = Object.getOwnPropertyNames(item);
|
|
2149
2286
|
if (!fits(names.length))
|
|
2150
|
-
return withheld(owner);
|
|
2287
|
+
return withheld(frame, slot, owner);
|
|
2151
2288
|
const prototype = Object.getPrototypeOf(item);
|
|
2152
2289
|
let plain = owned ||
|
|
2153
2290
|
(!error &&
|
|
@@ -2244,9 +2381,20 @@ export class SentSecrets {
|
|
|
2244
2381
|
else if (typeof value === "string" && binaryField(field, key))
|
|
2245
2382
|
next = declared(value, target, name, owner);
|
|
2246
2383
|
else {
|
|
2384
|
+
const start = parts.length;
|
|
2385
|
+
const nestedStart = nested.length;
|
|
2247
2386
|
next = visit(value, target, name, depth + 1, level, owner, owned);
|
|
2248
|
-
if (error && key === "name" && typeof value === "string" && target === own)
|
|
2249
|
-
|
|
2387
|
+
if (error && key === "name" && typeof value === "string" && target === own) {
|
|
2388
|
+
if (parts.length === start)
|
|
2389
|
+
leaf("", TEXT, target, name, owner);
|
|
2390
|
+
named.push({
|
|
2391
|
+
copy: out,
|
|
2392
|
+
slot: name,
|
|
2393
|
+
start,
|
|
2394
|
+
end: parts.length,
|
|
2395
|
+
withheld: next !== value || nested.slice(nestedStart).some((entry) => entry.dirty),
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2250
2398
|
}
|
|
2251
2399
|
if (target !== own)
|
|
2252
2400
|
continue;
|
|
@@ -2262,7 +2410,25 @@ export class SentSecrets {
|
|
|
2262
2410
|
};
|
|
2263
2411
|
const root = { target: { value: undefined }, shared: false, parent: undefined, slot: "value" };
|
|
2264
2412
|
root.target["value"] = visit(value, root, "value", 0, 0, -1, false);
|
|
2265
|
-
const
|
|
2413
|
+
const normalize = named.length
|
|
2414
|
+
? (lane) => {
|
|
2415
|
+
for (const entry of named) {
|
|
2416
|
+
for (let index = entry.start; !entry.withheld && index < entry.end; index++)
|
|
2417
|
+
if (lane[index] !== parts[index])
|
|
2418
|
+
entry.withheld = true;
|
|
2419
|
+
if (entry.withheld)
|
|
2420
|
+
for (let index = entry.start; index < entry.end; index++)
|
|
2421
|
+
lane[index] = index === entry.start ? REDACTED : "";
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
: undefined;
|
|
2425
|
+
let lane = parts;
|
|
2426
|
+
if (elements.size) {
|
|
2427
|
+
const original = parts.map((part, index) => elements.get(index) ?? part);
|
|
2428
|
+
const checked = this.checked(original, kinds, scan, false, normalize);
|
|
2429
|
+
lane = checked.map((part, index) => (part === original[index] ? parts[index] : part));
|
|
2430
|
+
}
|
|
2431
|
+
const safe = this.checked(lane, kinds, scan, false, normalize);
|
|
2266
2432
|
// Structure first, then the leaves inside it and typed binary, then nested JSON from the
|
|
2267
2433
|
// innermost out.
|
|
2268
2434
|
// A slot replaced by a toJSON result walked there takes the copy that result already got,
|
|
@@ -2273,7 +2439,6 @@ export class SentSecrets {
|
|
|
2273
2439
|
}
|
|
2274
2440
|
for (const frame of copied)
|
|
2275
2441
|
writable(frame);
|
|
2276
|
-
let marker;
|
|
2277
2442
|
for (let index = 0; index < parts.length; index++) {
|
|
2278
2443
|
const next = safe[index];
|
|
2279
2444
|
const kind = kinds[index];
|
|
@@ -2283,34 +2448,20 @@ export class SentSecrets {
|
|
|
2283
2448
|
continue;
|
|
2284
2449
|
}
|
|
2285
2450
|
const holder = writable(frames[index]);
|
|
2286
|
-
//
|
|
2287
|
-
|
|
2288
|
-
if (kind === BINARY) {
|
|
2289
|
-
const encoded = btoa(REDACTED);
|
|
2290
|
-
marker ??= this.checked([encoded], [BINARY], scan)[0] === encoded ? encoded : "";
|
|
2291
|
-
}
|
|
2292
|
-
put(holder, slots[index], kind === BINARY ? marker : kind === NUMBER && holder instanceof Error ? undefined : next);
|
|
2451
|
+
// A typed number on an error is dropped rather than turned into text.
|
|
2452
|
+
put(holder, slots[index], kind === ERROR_NUMBER ? undefined : next);
|
|
2293
2453
|
touch(owners[index]);
|
|
2294
2454
|
}
|
|
2295
|
-
for (const [frame, slot, owner, start, end] of binaries) {
|
|
2296
|
-
let clean = true;
|
|
2297
|
-
for (let index = start; clean && index < end; index++)
|
|
2298
|
-
clean = safe[index] === parts[index];
|
|
2299
|
-
if (clean)
|
|
2300
|
-
continue;
|
|
2301
|
-
put(writable(frame), slot, REDACTED);
|
|
2302
|
-
touch(owner);
|
|
2303
|
-
}
|
|
2304
2455
|
for (let index = nested.length - 1; index >= 0; index--) {
|
|
2305
2456
|
const entry = nested[index];
|
|
2306
|
-
if (!entry.
|
|
2457
|
+
if (!entry.dirty)
|
|
2307
2458
|
continue;
|
|
2308
|
-
put(writable(entry.frame), entry.slot,
|
|
2459
|
+
put(writable(entry.frame), entry.slot, JSON.stringify(entry.json));
|
|
2309
2460
|
touch(entry.owner);
|
|
2310
2461
|
}
|
|
2311
|
-
for (const
|
|
2312
|
-
if (
|
|
2313
|
-
put(copy, slot, REDACTED);
|
|
2462
|
+
for (const entry of named)
|
|
2463
|
+
if (entry.withheld)
|
|
2464
|
+
put(entry.copy, entry.slot, REDACTED);
|
|
2314
2465
|
return { value: root.target["value"], changed };
|
|
2315
2466
|
}
|
|
2316
2467
|
}
|
|
@@ -2361,9 +2512,6 @@ export function registerCredentialValues(secrets, values) {
|
|
|
2361
2512
|
secrets.add(value);
|
|
2362
2513
|
}
|
|
2363
2514
|
}
|
|
2364
|
-
export function trackCredentialReads(ctx) {
|
|
2365
|
-
wrapViewCredentials(ctx);
|
|
2366
|
-
}
|
|
2367
2515
|
/** A connector's result or error as it enters: every connector's credentials and the
|
|
2368
2516
|
* request's private arguments, under payload rules. */
|
|
2369
2517
|
export function redactSentSecrets(ctx, value) {
|
|
@@ -2379,9 +2527,7 @@ export function requestSentSecrets(ctx) {
|
|
|
2379
2527
|
* extracted from. Redact it whole first: a cut through an echoed form can leave a remnant
|
|
2380
2528
|
* shorter than any fragment the output boundary matches, and a normalized number or
|
|
2381
2529
|
* extracted substring may no longer hold the form at all. */
|
|
2382
|
-
export
|
|
2383
|
-
return requestSentSecrets(ctx).redact(value);
|
|
2384
|
-
}
|
|
2530
|
+
export const redactBeforeCut = redactSentSecrets;
|
|
2385
2531
|
/** Base64, or base64url with `url`, of bytes or UTF-8 text Connecta returns to an agent: a
|
|
2386
2532
|
* download, an attachment, a REST body, a page cursor. Every such encoding goes through here,
|
|
2387
2533
|
* since no later pass reads through an encoding the output does not declare: the content is
|
package/dist/server.js
CHANGED
|
@@ -35,7 +35,7 @@ export function createFetchHandler(opts) {
|
|
|
35
35
|
// Which sandbox, not how it is tuned: `connecta doctor` reports the
|
|
36
36
|
// executor it just exercised rather than assuming one, and a
|
|
37
37
|
// deployment whose executor identifies as nothing omits the key
|
|
38
|
-
// instead of inviting a guess
|
|
38
|
+
// instead of inviting a guess.
|
|
39
39
|
...(opts.executorName !== undefined ? { executor: { name: opts.executorName } } : {}),
|
|
40
40
|
admission: {
|
|
41
41
|
policy: "global-fifo",
|
|
@@ -64,7 +64,7 @@ export function createFetchHandler(opts) {
|
|
|
64
64
|
const defer = runtimeContext ? runtimeContext.waitUntil.bind(runtimeContext) : undefined;
|
|
65
65
|
const originRefusal = routeMcp.rejectOrigin(request);
|
|
66
66
|
if (originRefusal) {
|
|
67
|
-
return Effect.succeed(withSecurityHeaders(originRefusal, url
|
|
67
|
+
return Effect.succeed(withSecurityHeaders(originRefusal, url));
|
|
68
68
|
}
|
|
69
69
|
// Container and orchestrator probes reach /health over plain HTTP on
|
|
70
70
|
// loopback, where no proxy has set X-Forwarded-Proto. Redirecting them to
|
|
@@ -87,7 +87,7 @@ export function createFetchHandler(opts) {
|
|
|
87
87
|
return Effect.succeed(withSecurityHeaders(new Response(null, {
|
|
88
88
|
status: 308,
|
|
89
89
|
headers: { Location: target.toString() },
|
|
90
|
-
}), url
|
|
90
|
+
}), url));
|
|
91
91
|
}
|
|
92
92
|
const context = {
|
|
93
93
|
request,
|
|
@@ -98,7 +98,7 @@ export function createFetchHandler(opts) {
|
|
|
98
98
|
defer,
|
|
99
99
|
runtimeContext,
|
|
100
100
|
};
|
|
101
|
-
return Effect.map(route(context), (response) => withSecurityHeaders(response, url
|
|
101
|
+
return Effect.map(route(context), (response) => withSecurityHeaders(response, url));
|
|
102
102
|
});
|
|
103
103
|
return function fetch(request, runtimeContext) {
|
|
104
104
|
return runEdge(serve(request, runtimeContext), { signal: request.signal });
|