@rubytech/create-maxy-code 0.1.351 → 0.1.352
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/package.json +2 -2
- package/payload/platform/config/brand.json +1 -1
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/hooks/lib/maxy-mcp-plugins.txt +1 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js +27 -10
- package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js.map +1 -1
- package/payload/platform/plugins/admin/skills/agent-builder/references/agent-pattern.md +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +40 -2
- package/payload/platform/plugins/connector/.claude-plugin/plugin.json +21 -0
- package/payload/platform/plugins/connector/PLUGIN.md +55 -0
- package/payload/platform/plugins/connector/lib/mcp-spawn-tee/index.js +193 -0
- package/payload/platform/plugins/connector/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/connector/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/connector/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/connector/mcp/dist/index.js +119 -0
- package/payload/platform/plugins/connector/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/call.d.ts +36 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/call.d.ts.map +1 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/call.js +84 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/call.js.map +1 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/redact.d.ts +10 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/redact.d.ts.map +1 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/redact.js +29 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/redact.js.map +1 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/ssrf.d.ts +18 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/ssrf.d.ts.map +1 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/ssrf.js +94 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/ssrf.js.map +1 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/store.d.ts +25 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/store.d.ts.map +1 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/store.js +63 -0
- package/payload/platform/plugins/connector/mcp/dist/lib/store.js.map +1 -0
- package/payload/platform/plugins/connector/mcp/dist/scripts/audit-connectors.d.ts +9 -0
- package/payload/platform/plugins/connector/mcp/dist/scripts/audit-connectors.d.ts.map +1 -0
- package/payload/platform/plugins/connector/mcp/dist/scripts/audit-connectors.js +63 -0
- package/payload/platform/plugins/connector/mcp/dist/scripts/audit-connectors.js.map +1 -0
- package/payload/platform/plugins/connector/mcp/package.json +10 -0
- package/payload/platform/plugins/docs/references/connector.md +33 -0
- package/payload/platform/plugins/docs/references/getting-started.md +1 -1
- package/payload/platform/scripts/check-agent-contract.mjs +97 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +5 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +4 -0
- package/payload/platform/templates/specialists/agents/citation-auditor.md +5 -1
- package/payload/platform/templates/specialists/agents/coding-assistant.md +8 -0
- package/payload/platform/templates/specialists/agents/compiled-truth-rewriter.md +4 -0
- package/payload/platform/templates/specialists/agents/content-producer.md +8 -0
- package/payload/platform/templates/specialists/agents/data-manager.md +9 -1
- package/payload/platform/templates/specialists/agents/database-operator.md +12 -0
- package/payload/platform/templates/specialists/agents/librarian.md +9 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +8 -0
- package/payload/platform/templates/specialists/agents/project-manager.md +8 -0
- package/payload/platform/templates/specialists/agents/public-session-reviewer.md +9 -1
- package/payload/platform/templates/specialists/agents/research-assistant.md +8 -0
- package/payload/platform/templates/specialists/agents/typed-edge-classifier.md +4 -2
- package/payload/premium-plugins/writer-craft/agents/writer-craft--manuscript-reviewer.md +12 -0
- package/payload/server/public/assets/{AdminLoginScreens-B1cvICYI.js → AdminLoginScreens-lt72mYrz.js} +1 -1
- package/payload/server/public/assets/{admin-DF9JDP6a.js → admin-DzLxv-nz.js} +1 -1
- package/payload/server/public/assets/admin-types-DJoj6VJv.js +1 -0
- package/payload/server/public/assets/chat-B9aNK1FG.js +1 -0
- package/payload/server/public/assets/{operator-CqKk6Twn.js → operator-MASl7IqX.js} +1 -1
- package/payload/server/public/assets/{page-Bfm1FN4n.js → page-CZy33qUH.js} +1 -1
- package/payload/server/public/assets/{public-D5lkOp3H.js → public-77Ubicd7.js} +1 -1
- package/payload/server/public/chat.html +3 -3
- package/payload/server/public/index.html +3 -3
- package/payload/server/public/operator.html +4 -4
- package/payload/server/public/public.html +3 -3
- package/payload/server/server.js +30 -1
- package/payload/server/public/assets/admin-types-Dg11L4MQ.js +0 -1
- package/payload/server/public/assets/chat-C4hHkcTu.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Refusal } from "./ssrf.js";
|
|
2
|
+
import type { ConnectorRecord } from "./store.js";
|
|
3
|
+
export interface CallParams {
|
|
4
|
+
method: string;
|
|
5
|
+
path: string;
|
|
6
|
+
query?: Record<string, string>;
|
|
7
|
+
body?: unknown;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export interface CallDeps {
|
|
11
|
+
/** Override for tests; defaults to global fetch. */
|
|
12
|
+
fetchImpl?: typeof fetch;
|
|
13
|
+
/** Override for tests so a loopback stub is reachable; defaults to the real SSRF guard. */
|
|
14
|
+
guard?: (targetHost: string, registeredHost: string, kind: "request" | "redirect") => Promise<Refusal | null>;
|
|
15
|
+
/** Sink for `[connector]` lifecycle lines; the secret is never passed to it. */
|
|
16
|
+
log?: (line: string) => void;
|
|
17
|
+
reqId?: string;
|
|
18
|
+
}
|
|
19
|
+
export type CallResult = {
|
|
20
|
+
ok: true;
|
|
21
|
+
status: number;
|
|
22
|
+
headers: Record<string, string>;
|
|
23
|
+
body: string;
|
|
24
|
+
truncated: boolean;
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
refused: Refusal;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Make one authenticated, host-bound request to a registered connector. The guard runs
|
|
31
|
+
* before the request and before every redirect hop; the credential is injected from the
|
|
32
|
+
* caller-supplied secret and never logged. Returns status + redacted headers + capped body,
|
|
33
|
+
* or a refusal when the guard rejects the target.
|
|
34
|
+
*/
|
|
35
|
+
export declare function performCall(rec: ConnectorRecord, secret: string, params: CallParams, deps?: CallDeps): Promise<CallResult>;
|
|
36
|
+
//# sourceMappingURL=call.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call.d.ts","sourceRoot":"","sources":["../../src/lib/call.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EAAc,eAAe,EAAE,MAAM,YAAY,CAAC;AAE9D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,QAAQ;IACvB,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,UAAU,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAC9G,gFAAgF;IAChF,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,GAC/F;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAapC;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,UAAU,EAClB,IAAI,GAAE,QAAa,GAClB,OAAO,CAAC,UAAU,CAAC,CA6DrB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { redactHeaders, scrubSecret } from "./redact.js";
|
|
2
|
+
import { guardTarget } from "./ssrf.js";
|
|
3
|
+
const BODY_CAP = 262144; // 256 KiB
|
|
4
|
+
const MAX_REDIRECTS = 5;
|
|
5
|
+
/** Inject the credential per scheme. Returns the scheme label for the auth-applied log line. */
|
|
6
|
+
function applyAuth(headers, rec, secret) {
|
|
7
|
+
if (rec.authScheme === "bearer")
|
|
8
|
+
headers["Authorization"] = `Bearer ${secret}`;
|
|
9
|
+
else if (rec.authScheme === "basic")
|
|
10
|
+
headers["Authorization"] = `Basic ${Buffer.from(secret).toString("base64")}`;
|
|
11
|
+
else
|
|
12
|
+
headers[rec.headerName] = secret; // api-key-header (headerName validated at register)
|
|
13
|
+
return rec.authScheme;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Make one authenticated, host-bound request to a registered connector. The guard runs
|
|
17
|
+
* before the request and before every redirect hop; the credential is injected from the
|
|
18
|
+
* caller-supplied secret and never logged. Returns status + redacted headers + capped body,
|
|
19
|
+
* or a refusal when the guard rejects the target.
|
|
20
|
+
*/
|
|
21
|
+
export async function performCall(rec, secret, params, deps = {}) {
|
|
22
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
23
|
+
const guard = deps.guard ?? guardTarget;
|
|
24
|
+
const log = deps.log ?? (() => { });
|
|
25
|
+
const reqId = deps.reqId ?? "—";
|
|
26
|
+
const url = new URL(params.path, rec.baseUrl);
|
|
27
|
+
// A caller-supplied path may carry userinfo (user:pass@host); strip it so the only credential
|
|
28
|
+
// sent is the one the connector injects, and so userinfo cannot smuggle alternate auth.
|
|
29
|
+
url.username = "";
|
|
30
|
+
url.password = "";
|
|
31
|
+
if (params.query)
|
|
32
|
+
for (const [k, v] of Object.entries(params.query))
|
|
33
|
+
url.searchParams.set(k, v);
|
|
34
|
+
log(`op=call reqId=${reqId} method=${params.method} host=${url.hostname} path=${url.pathname}`);
|
|
35
|
+
const reqRefusal = await guard(url.hostname, rec.host, "request");
|
|
36
|
+
if (reqRefusal) {
|
|
37
|
+
log(`op=call-refused reqId=${reqId} reason=${reqRefusal} target=${url.hostname}`);
|
|
38
|
+
return { ok: false, refused: reqRefusal };
|
|
39
|
+
}
|
|
40
|
+
// Caller headers may not set the auth header or Host — those are ours to control.
|
|
41
|
+
const headers = {};
|
|
42
|
+
if (params.headers) {
|
|
43
|
+
for (const [k, v] of Object.entries(params.headers)) {
|
|
44
|
+
const lk = k.toLowerCase();
|
|
45
|
+
if (lk === "authorization" || lk === "host")
|
|
46
|
+
continue;
|
|
47
|
+
if (rec.headerName && lk === rec.headerName.toLowerCase())
|
|
48
|
+
continue;
|
|
49
|
+
headers[k] = v;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let bodyStr;
|
|
53
|
+
if (params.body !== undefined) {
|
|
54
|
+
if (!Object.keys(headers).some((k) => k.toLowerCase() === "content-type"))
|
|
55
|
+
headers["Content-Type"] = "application/json";
|
|
56
|
+
bodyStr = typeof params.body === "string" ? params.body : JSON.stringify(params.body);
|
|
57
|
+
}
|
|
58
|
+
const scheme = applyAuth(headers, rec, secret);
|
|
59
|
+
log(`op=auth-applied reqId=${reqId} scheme=${scheme === "api-key-header" ? "api-key" : scheme}`);
|
|
60
|
+
const start = Date.now();
|
|
61
|
+
let current = url;
|
|
62
|
+
let hops = 0;
|
|
63
|
+
let res;
|
|
64
|
+
for (;;) {
|
|
65
|
+
res = await fetchImpl(current.toString(), { method: params.method, headers, body: bodyStr, redirect: "manual" });
|
|
66
|
+
const location = res.status >= 300 && res.status < 400 ? res.headers.get("location") : null;
|
|
67
|
+
if (!location || hops++ >= MAX_REDIRECTS)
|
|
68
|
+
break;
|
|
69
|
+
const next = new URL(location, current);
|
|
70
|
+
const redirectRefusal = await guard(next.hostname, rec.host, "redirect");
|
|
71
|
+
if (redirectRefusal) {
|
|
72
|
+
log(`op=call-refused reqId=${reqId} reason=${redirectRefusal} target=${next.hostname}`);
|
|
73
|
+
return { ok: false, refused: redirectRefusal };
|
|
74
|
+
}
|
|
75
|
+
current = next;
|
|
76
|
+
}
|
|
77
|
+
const raw = await res.text();
|
|
78
|
+
const truncated = raw.length > BODY_CAP;
|
|
79
|
+
// Scrub the credential from the body in case the upstream reflects it (debug/echo endpoints).
|
|
80
|
+
const body = scrubSecret(truncated ? raw.slice(0, BODY_CAP) : raw, secret);
|
|
81
|
+
log(`op=response reqId=${reqId} status=${res.status} bytes=${raw.length} ms=${Date.now() - start}`);
|
|
82
|
+
return { ok: true, status: res.status, headers: redactHeaders(res.headers, rec.headerName, secret), body, truncated };
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=call.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call.js","sourceRoot":"","sources":["../../src/lib/call.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Replace every occurrence of a credential (raw and base64 forms) with a marker. */
|
|
2
|
+
export declare function scrubSecret(text: string, secret: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Copy response headers into a plain object, masking the auth-bearing ones so a returned
|
|
5
|
+
* response never re-exposes a credential or session token. The connector's own api-key header
|
|
6
|
+
* (if any) is masked alongside Authorization and Set-Cookie, and the known secret value is
|
|
7
|
+
* scrubbed from every remaining header value in case the upstream reflects it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function redactHeaders(headers: Headers, apiKeyHeader?: string, secret?: string): Record<string, string>;
|
|
10
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/lib/redact.ts"],"names":[],"mappings":"AAGA,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQ9G"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Header names always redacted from a returned response (case-insensitive). */
|
|
2
|
+
const ALWAYS_DROP = ["authorization", "set-cookie"];
|
|
3
|
+
/** Replace every occurrence of a credential (raw and base64 forms) with a marker. */
|
|
4
|
+
export function scrubSecret(text, secret) {
|
|
5
|
+
if (!secret)
|
|
6
|
+
return text;
|
|
7
|
+
let out = text.split(secret).join("<redacted>");
|
|
8
|
+
const b64 = Buffer.from(secret).toString("base64");
|
|
9
|
+
if (b64)
|
|
10
|
+
out = out.split(b64).join("<redacted>");
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Copy response headers into a plain object, masking the auth-bearing ones so a returned
|
|
15
|
+
* response never re-exposes a credential or session token. The connector's own api-key header
|
|
16
|
+
* (if any) is masked alongside Authorization and Set-Cookie, and the known secret value is
|
|
17
|
+
* scrubbed from every remaining header value in case the upstream reflects it.
|
|
18
|
+
*/
|
|
19
|
+
export function redactHeaders(headers, apiKeyHeader, secret) {
|
|
20
|
+
const drop = new Set(ALWAYS_DROP);
|
|
21
|
+
if (apiKeyHeader)
|
|
22
|
+
drop.add(apiKeyHeader.toLowerCase());
|
|
23
|
+
const out = {};
|
|
24
|
+
headers.forEach((value, key) => {
|
|
25
|
+
out[key] = drop.has(key.toLowerCase()) ? "<redacted>" : secret ? scrubSecret(value, secret) : value;
|
|
26
|
+
});
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/lib/redact.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,WAAW,GAAG,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC;AAEpD,qFAAqF;AACrF,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,MAAc;IACtD,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAChD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,GAAG;QAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACjD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,OAAgB,EAAE,YAAqB,EAAE,MAAe;IACpF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IAClC,IAAI,YAAY;QAAE,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,CAAC;IACvD,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QAC7B,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACtG,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** True for any loopback, private, link-local, or unique-local address (v4 or v6). */
|
|
2
|
+
export declare function isPrivateIp(ip: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a host and judge whether any of its addresses is private. IP literals (incl.
|
|
5
|
+
* bracketed IPv6) skip DNS. Fails closed: an unresolvable or lookup-erroring host is treated
|
|
6
|
+
* as unsafe (returns true), so the caller refuses rather than throwing.
|
|
7
|
+
*/
|
|
8
|
+
export declare function hostResolvesPrivate(host: string): Promise<boolean>;
|
|
9
|
+
export type Refusal = "host-mismatch" | "private-ip" | "redirect-escape";
|
|
10
|
+
/**
|
|
11
|
+
* Confirm a request/redirect target is the registered host and not a private address.
|
|
12
|
+
* Returns a refusal reason, or null when the target is allowed. Hosts are normalised
|
|
13
|
+
* (lowercased, brackets and a trailing FQDN dot stripped) before comparison. `kind`
|
|
14
|
+
* distinguishes the mismatch label: an off-host initial request is "host-mismatch", an
|
|
15
|
+
* off-host redirect is "redirect-escape".
|
|
16
|
+
*/
|
|
17
|
+
export declare function guardTarget(targetHost: string, registeredHost: string, kind: "request" | "redirect"): Promise<Refusal | null>;
|
|
18
|
+
//# sourceMappingURL=ssrf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssrf.d.ts","sourceRoot":"","sources":["../../src/lib/ssrf.ts"],"names":[],"mappings":"AAwBA,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAqB/C;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWxE;AAED,MAAM,MAAM,OAAO,GAAG,eAAe,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAEzE;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,SAAS,GAAG,UAAU,GAC3B,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAMzB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { lookup } from "node:dns/promises";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
/** Strip the `[ ]` that URL.hostname keeps around an IPv6 literal, and a trailing FQDN dot. */
|
|
4
|
+
function normalizeHost(host) {
|
|
5
|
+
let s = host.toLowerCase();
|
|
6
|
+
if (s.startsWith("[") && s.endsWith("]"))
|
|
7
|
+
s = s.slice(1, -1);
|
|
8
|
+
if (s.endsWith("."))
|
|
9
|
+
s = s.slice(0, -1);
|
|
10
|
+
return s;
|
|
11
|
+
}
|
|
12
|
+
/** True for an IPv4 address in any loopback/private/link-local/CGNAT/"this-host" range. */
|
|
13
|
+
function v4Private(ip) {
|
|
14
|
+
const o = ip.split(".").map((s) => Number(s));
|
|
15
|
+
if (o.length !== 4 || o.some((n) => Number.isNaN(n) || n < 0 || n > 255))
|
|
16
|
+
return false;
|
|
17
|
+
const [a, b] = o;
|
|
18
|
+
if (a === 10 || a === 127 || a === 0)
|
|
19
|
+
return true; // private, loopback, "this host"
|
|
20
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
21
|
+
return true; // private
|
|
22
|
+
if (a === 192 && b === 168)
|
|
23
|
+
return true; // private
|
|
24
|
+
if (a === 169 && b === 254)
|
|
25
|
+
return true; // link-local
|
|
26
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
27
|
+
return true; // CGNAT (100.64/10)
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
/** True for any loopback, private, link-local, or unique-local address (v4 or v6). */
|
|
31
|
+
export function isPrivateIp(ip) {
|
|
32
|
+
const bare = normalizeHost(ip);
|
|
33
|
+
const fam = isIP(bare);
|
|
34
|
+
if (fam === 4)
|
|
35
|
+
return v4Private(bare);
|
|
36
|
+
if (fam === 6) {
|
|
37
|
+
const lower = bare;
|
|
38
|
+
if (lower === "::1" || lower === "::")
|
|
39
|
+
return true; // loopback, unspecified
|
|
40
|
+
const dotted = lower.match(/::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/); // IPv4-mapped, dotted
|
|
41
|
+
if (dotted)
|
|
42
|
+
return v4Private(dotted[1]);
|
|
43
|
+
const hex = lower.match(/::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/); // IPv4-mapped, hex
|
|
44
|
+
if (hex) {
|
|
45
|
+
const hi = parseInt(hex[1], 16);
|
|
46
|
+
const lo = parseInt(hex[2], 16);
|
|
47
|
+
return v4Private(`${(hi >> 8) & 255}.${hi & 255}.${(lo >> 8) & 255}.${lo & 255}`);
|
|
48
|
+
}
|
|
49
|
+
if (lower.startsWith("fc") || lower.startsWith("fd"))
|
|
50
|
+
return true; // fc00::/7 ULA
|
|
51
|
+
// fe80::/10 link-local spans fe80–febf, i.e. fe8/fe9/fea/feb prefixes.
|
|
52
|
+
if (/^fe[89ab]/.test(lower))
|
|
53
|
+
return true;
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolve a host and judge whether any of its addresses is private. IP literals (incl.
|
|
60
|
+
* bracketed IPv6) skip DNS. Fails closed: an unresolvable or lookup-erroring host is treated
|
|
61
|
+
* as unsafe (returns true), so the caller refuses rather than throwing.
|
|
62
|
+
*/
|
|
63
|
+
export async function hostResolvesPrivate(host) {
|
|
64
|
+
const bare = normalizeHost(host);
|
|
65
|
+
if (!bare)
|
|
66
|
+
return true; // empty/malformed host can never be a safe public target
|
|
67
|
+
if (isIP(bare))
|
|
68
|
+
return isPrivateIp(bare);
|
|
69
|
+
try {
|
|
70
|
+
const addrs = await lookup(bare, { all: true });
|
|
71
|
+
if (addrs.length === 0)
|
|
72
|
+
return true;
|
|
73
|
+
return addrs.some((a) => isPrivateIp(a.address));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return true; // DNS failure → fail closed
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Confirm a request/redirect target is the registered host and not a private address.
|
|
81
|
+
* Returns a refusal reason, or null when the target is allowed. Hosts are normalised
|
|
82
|
+
* (lowercased, brackets and a trailing FQDN dot stripped) before comparison. `kind`
|
|
83
|
+
* distinguishes the mismatch label: an off-host initial request is "host-mismatch", an
|
|
84
|
+
* off-host redirect is "redirect-escape".
|
|
85
|
+
*/
|
|
86
|
+
export async function guardTarget(targetHost, registeredHost, kind) {
|
|
87
|
+
if (normalizeHost(targetHost) !== normalizeHost(registeredHost)) {
|
|
88
|
+
return kind === "redirect" ? "redirect-escape" : "host-mismatch";
|
|
89
|
+
}
|
|
90
|
+
if (await hostResolvesPrivate(targetHost))
|
|
91
|
+
return "private-ip";
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=ssrf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssrf.js","sourceRoot":"","sources":["../../src/lib/ssrf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type AuthScheme = "bearer" | "api-key-header" | "basic";
|
|
2
|
+
export interface ConnectorRecord {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
host: string;
|
|
5
|
+
authScheme: AuthScheme;
|
|
6
|
+
/** Required when authScheme is "api-key-header"; the header the credential is sent in. */
|
|
7
|
+
headerName?: string;
|
|
8
|
+
}
|
|
9
|
+
type Registry = Record<string, ConnectorRecord>;
|
|
10
|
+
type Secrets = Record<string, string>;
|
|
11
|
+
export declare function readRegistry(platformRoot: string, accountId: string): Registry;
|
|
12
|
+
export declare function readSecrets(platformRoot: string, accountId: string): Secrets;
|
|
13
|
+
export declare function registerConnector(platformRoot: string, accountId: string, name: string, rec: ConnectorRecord, secret: string): void;
|
|
14
|
+
/** Remove a connector from both files. Returns whether it existed in either. */
|
|
15
|
+
export declare function deregisterConnector(platformRoot: string, accountId: string, name: string): boolean;
|
|
16
|
+
export declare function resolveConnector(platformRoot: string, accountId: string, name: string): {
|
|
17
|
+
rec: ConnectorRecord;
|
|
18
|
+
secret: string;
|
|
19
|
+
} | null;
|
|
20
|
+
export declare function listConnectors(platformRoot: string, accountId: string): {
|
|
21
|
+
name: string;
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
}[];
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/lib/store.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,gBAAgB,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,UAAU,CAAC;IACvB,0FAA0F;IAC1F,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAChD,KAAK,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAyBtC,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,QAAQ,CAE9E;AAED,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAE5E;AAED,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,eAAe,EACpB,MAAM,EAAE,MAAM,GACb,IAAI,CAON;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CASlG;AAED,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX;IAAE,GAAG,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKjD;AAED,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,CAK3G"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
/** Per-account secrets dir — the same location joblogic/quickbooks/cloudflare use. */
|
|
4
|
+
function secretsDir(platformRoot, accountId) {
|
|
5
|
+
return join(resolve(platformRoot, "..", "data/accounts"), accountId, "secrets");
|
|
6
|
+
}
|
|
7
|
+
const registryPath = (p, a) => join(secretsDir(p, a), "connectors.json");
|
|
8
|
+
const secretsPath = (p, a) => join(secretsDir(p, a), "connector-secrets.json");
|
|
9
|
+
/** Read a JSON file, returning null on a missing or unparseable file — never throws. */
|
|
10
|
+
function readJson(file) {
|
|
11
|
+
if (!existsSync(file))
|
|
12
|
+
return null;
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(readFileSync(file, "utf-8"));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Write a JSON file at mode 0600, creating the secrets dir if absent. */
|
|
21
|
+
function writeJson(file, value) {
|
|
22
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
23
|
+
writeFileSync(file, JSON.stringify(value, null, 2), { mode: 0o600 });
|
|
24
|
+
}
|
|
25
|
+
export function readRegistry(platformRoot, accountId) {
|
|
26
|
+
return readJson(registryPath(platformRoot, accountId)) ?? {};
|
|
27
|
+
}
|
|
28
|
+
export function readSecrets(platformRoot, accountId) {
|
|
29
|
+
return readJson(secretsPath(platformRoot, accountId)) ?? {};
|
|
30
|
+
}
|
|
31
|
+
export function registerConnector(platformRoot, accountId, name, rec, secret) {
|
|
32
|
+
const reg = readRegistry(platformRoot, accountId);
|
|
33
|
+
reg[name] = rec;
|
|
34
|
+
writeJson(registryPath(platformRoot, accountId), reg);
|
|
35
|
+
const sec = readSecrets(platformRoot, accountId);
|
|
36
|
+
sec[name] = secret;
|
|
37
|
+
writeJson(secretsPath(platformRoot, accountId), sec);
|
|
38
|
+
}
|
|
39
|
+
/** Remove a connector from both files. Returns whether it existed in either. */
|
|
40
|
+
export function deregisterConnector(platformRoot, accountId, name) {
|
|
41
|
+
const reg = readRegistry(platformRoot, accountId);
|
|
42
|
+
const sec = readSecrets(platformRoot, accountId);
|
|
43
|
+
const had = name in reg || name in sec;
|
|
44
|
+
delete reg[name];
|
|
45
|
+
delete sec[name];
|
|
46
|
+
writeJson(registryPath(platformRoot, accountId), reg);
|
|
47
|
+
writeJson(secretsPath(platformRoot, accountId), sec);
|
|
48
|
+
return had;
|
|
49
|
+
}
|
|
50
|
+
export function resolveConnector(platformRoot, accountId, name) {
|
|
51
|
+
const rec = readRegistry(platformRoot, accountId)[name];
|
|
52
|
+
const secret = readSecrets(platformRoot, accountId)[name];
|
|
53
|
+
if (!rec || secret === undefined)
|
|
54
|
+
return null;
|
|
55
|
+
return { rec, secret };
|
|
56
|
+
}
|
|
57
|
+
export function listConnectors(platformRoot, accountId) {
|
|
58
|
+
return Object.entries(readRegistry(platformRoot, accountId)).map(([name, r]) => ({
|
|
59
|
+
name,
|
|
60
|
+
baseUrl: r.baseUrl,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/lib/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit every account that has a connector registry. For each, emit:
|
|
3
|
+
* - `op=leak-scan outcome=clean|LEAK name=<n>` per stored secret (LEAK if its value
|
|
4
|
+
* is found in any connector log).
|
|
5
|
+
* - `op=reconcile name=<n> outcome=ok|missing-secret|orphan-secret` per registry/secret name.
|
|
6
|
+
* A secret value is never emitted.
|
|
7
|
+
*/
|
|
8
|
+
export declare function runAudit(platformRoot: string, logDir: string | null, emit: (line: string) => void): void;
|
|
9
|
+
//# sourceMappingURL=audit-connectors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-connectors.d.ts","sourceRoot":"","sources":["../../src/scripts/audit-connectors.ts"],"names":[],"mappings":"AAwBA;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAoBxG"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Standing connector audit. Covers two no-event failure modes that no per-call log
|
|
2
|
+
// surfaces: a stored secret reaching a log file (leak-scan), and a registry/secret
|
|
3
|
+
// desync (reconcile). Run on an interval by the UI server; one [connector-audit] line
|
|
4
|
+
// per finding, emitted to stderr so it lands in the brand journal.
|
|
5
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
|
+
import { readRegistry, readSecrets } from "../lib/store.js";
|
|
9
|
+
/** Concatenate every connector log file in LOG_DIR (per-session + per-date sinks). */
|
|
10
|
+
function readConnectorLogs(logDir) {
|
|
11
|
+
if (!existsSync(logDir))
|
|
12
|
+
return "";
|
|
13
|
+
let text = "";
|
|
14
|
+
for (const f of readdirSync(logDir)) {
|
|
15
|
+
if (!/^mcp-connector-.*\.log$/.test(f))
|
|
16
|
+
continue;
|
|
17
|
+
try {
|
|
18
|
+
text += readFileSync(join(logDir, f), "utf-8");
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
/* unreadable log file — skip, the scan degrades to the readable ones */
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return text;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Audit every account that has a connector registry. For each, emit:
|
|
28
|
+
* - `op=leak-scan outcome=clean|LEAK name=<n>` per stored secret (LEAK if its value
|
|
29
|
+
* is found in any connector log).
|
|
30
|
+
* - `op=reconcile name=<n> outcome=ok|missing-secret|orphan-secret` per registry/secret name.
|
|
31
|
+
* A secret value is never emitted.
|
|
32
|
+
*/
|
|
33
|
+
export function runAudit(platformRoot, logDir, emit) {
|
|
34
|
+
const accountsDir = resolve(platformRoot, "..", "data/accounts");
|
|
35
|
+
if (!existsSync(accountsDir))
|
|
36
|
+
return;
|
|
37
|
+
const logText = logDir ? readConnectorLogs(logDir) : "";
|
|
38
|
+
for (const accountId of readdirSync(accountsDir)) {
|
|
39
|
+
if (!existsSync(join(accountsDir, accountId, "secrets", "connectors.json")))
|
|
40
|
+
continue;
|
|
41
|
+
const reg = readRegistry(platformRoot, accountId);
|
|
42
|
+
const sec = readSecrets(platformRoot, accountId);
|
|
43
|
+
for (const [name, value] of Object.entries(sec)) {
|
|
44
|
+
if (!value)
|
|
45
|
+
continue;
|
|
46
|
+
// Match the raw value and its base64 form — the basic scheme leaks only as base64.
|
|
47
|
+
const b64 = Buffer.from(value).toString("base64");
|
|
48
|
+
const leaked = logText.includes(value) || logText.includes(b64);
|
|
49
|
+
emit(`[connector-audit] op=leak-scan outcome=${leaked ? "LEAK" : "clean"} name=${name}`);
|
|
50
|
+
}
|
|
51
|
+
for (const name of new Set([...Object.keys(reg), ...Object.keys(sec)])) {
|
|
52
|
+
const outcome = name in reg && name in sec ? "ok" : name in reg ? "missing-secret" : "orphan-secret";
|
|
53
|
+
emit(`[connector-audit] op=reconcile name=${name} outcome=${outcome}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// CLI entry: run only when invoked directly (not when imported by a test).
|
|
58
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
59
|
+
const platformRoot = process.env.PLATFORM_ROOT;
|
|
60
|
+
if (platformRoot)
|
|
61
|
+
runAudit(platformRoot, process.env.LOG_DIR ?? null, (line) => process.stderr.write(line + "\n"));
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=audit-connectors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-connectors.js","sourceRoot":"","sources":["../../src/scripts/audit-connectors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@maxy/connector",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"scripts": { "build": "tsc", "start": "node dist/index.js" },
|
|
8
|
+
"dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "zod": "^3.24.0" },
|
|
9
|
+
"devDependencies": { "typescript": "^5.7.0", "@types/node": "^22.0.0" }
|
|
10
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Connector
|
|
2
|
+
|
|
3
|
+
Connect any REST API by name so the agent can call it on your behalf — without a bespoke integration built for that one service. You register the API once with its address and a credential; from then on the agent can read from it and write to it, and the credential stays sealed inside your install.
|
|
4
|
+
|
|
5
|
+
This is the general-purpose bridge to the long tail of services that do not have their own dedicated connector. If a tool you use exposes a REST API and issues an API key or token, you can wire it up here.
|
|
6
|
+
|
|
7
|
+
## What you do once
|
|
8
|
+
|
|
9
|
+
**1. Get a credential from the service.** Most APIs give you an API key, a bearer token, or a username and password. How you get it depends on the service — usually a settings or developer page.
|
|
10
|
+
|
|
11
|
+
**2. Register the connector.** Tell the agent the service's name, its base web address, and the credential. The agent stores the credential in your install's protected secrets area. It is never shown back to you, never written into any project file, and never appears in logs.
|
|
12
|
+
|
|
13
|
+
**3. That's it.** The connector is ready. You can register as many as you like, each under its own name.
|
|
14
|
+
|
|
15
|
+
## What the agent does after that
|
|
16
|
+
|
|
17
|
+
- **Calls the API for you** — fetches data (GET) and makes changes (create, update, delete) against the service, with your credential attached automatically.
|
|
18
|
+
- **Stays on the registered service.** Every request is locked to the address you registered. A request that tries to reach a different site, an internal network address, or follows a redirect off the registered service is refused. The credential can only ever be sent to the service you pointed it at.
|
|
19
|
+
- **Lists what's connected** — the names and addresses of your connectors, on request. Never the credentials.
|
|
20
|
+
- **Disconnects on request** — removes a connector and erases its stored credential.
|
|
21
|
+
|
|
22
|
+
## Staying safe
|
|
23
|
+
|
|
24
|
+
The credential lives only inside your install, in the same protected store the other connectors use, readable only by your account. The agent injects it into each request and never repeats it back. A background check periodically confirms no credential has leaked into a log and that every connector still has its credential.
|
|
25
|
+
|
|
26
|
+
Because a connector can make authenticated requests to a live service, registering and using them is an admin action — done by you or through your admin agent, not handed to a public or assistant agent.
|
|
27
|
+
|
|
28
|
+
## What it can and can't reach
|
|
29
|
+
|
|
30
|
+
- **Can:** any HTTP or HTTPS REST API that authenticates with a bearer token, an API key in a header, or basic username-and-password.
|
|
31
|
+
- **Can't (yet):** services that require an OAuth sign-in dance or refreshing tokens — those need a different kind of connection. It also does not speak GraphQL or older SOAP APIs.
|
|
32
|
+
|
|
33
|
+
Once an API is connected, the actual workflow you want — "turn approved timesheets into an invoice", say — is something you teach the agent as a skill. The connector is the plumbing; the workflow is yours to define.
|
|
@@ -73,7 +73,7 @@ When the text field is empty, a microphone button appears in place of the send b
|
|
|
73
73
|
|
|
74
74
|
Voice recording requires a secure connection (HTTPS). When accessing {{productName}} over the local network via HTTP, use the tunnel URL for voice notes.
|
|
75
75
|
|
|
76
|
-
You can also drop, paste, or pick an audio file (`.opus`, `.ogg`, `.m4a`, `.mp3`, `.wav`, `.webm`) into the chat composer — for example a voice note forwarded from WhatsApp. The file is transcribed the same way the in-browser recording is, and only the transcript reaches {{productName}}; the audio itself is discarded after transcription. If you type a message and attach an audio file on the same send, both arrive — the transcript follows your typed text.
|
|
76
|
+
You can also drop, paste, or pick an audio file (`.opus`, `.ogg`, `.m4a`, `.mp4`, `.mp3`, `.wav`, `.webm`) into the chat composer — for example a voice note forwarded from WhatsApp. An `.mp4` video file works too: its audio track is extracted and transcribed. The file is transcribed the same way the in-browser recording is, and only the transcript reaches {{productName}}; the audio itself is discarded after transcription. If you type a message and attach an audio file on the same send, both arrive — the transcript follows your typed text.
|
|
77
77
|
|
|
78
78
|
## What {{productName}} Remembers
|
|
79
79
|
|