@agentconnect.md/setup 1.37.0-rc.4 → 1.37.0-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +77 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -56521,6 +56521,26 @@ async function disconnectPrisma() {
|
|
|
56521
56521
|
await singleton?.$disconnect();
|
|
56522
56522
|
singleton = void 0;
|
|
56523
56523
|
}
|
|
56524
|
+
//#endregion
|
|
56525
|
+
//#region ../control-plane/dist/secrets/scope.js
|
|
56526
|
+
/** Deployment-owned material (the deployment config document's secrets). */
|
|
56527
|
+
const DEPLOYMENT_SCOPE = { kind: "deployment" };
|
|
56528
|
+
/**
|
|
56529
|
+
* Envelope marker for values sealed under a SCOPED key. It carries a format
|
|
56530
|
+
* version and nothing else — deliberately not the tenant id (see above). Its
|
|
56531
|
+
* one job is to let `open` tell a scoped value apart from a pre-scoping one, so
|
|
56532
|
+
* the rollout needs no backfill.
|
|
56533
|
+
*/
|
|
56534
|
+
const SECRET_ENVELOPE_PREFIX = "acv1:";
|
|
56535
|
+
/**
|
|
56536
|
+
* Org key names default to `<deployment key>-org-`, so they inherit whatever
|
|
56537
|
+
* namespace the deployment key already occupies. Deployments commonly share one
|
|
56538
|
+
* transit mount and rely on key naming alone to stay separated; a fixed prefix
|
|
56539
|
+
* would collide their org keys, a derived one cannot.
|
|
56540
|
+
*/
|
|
56541
|
+
function effectiveOrgKeyPrefix(transitKey, configured) {
|
|
56542
|
+
return configured ?? `${transitKey}-org-`;
|
|
56543
|
+
}
|
|
56524
56544
|
const HttpUrlSchema = string().url().superRefine((value, ctx) => {
|
|
56525
56545
|
const url = new URL(value);
|
|
56526
56546
|
if (url.protocol !== "http:" && url.protocol !== "https:") ctx.addIssue({
|
|
@@ -56775,7 +56795,7 @@ var DeploymentConfigService = class {
|
|
|
56775
56795
|
for (const [rawKey, value] of Object.entries(secretPatch)) {
|
|
56776
56796
|
const key = DeploymentSecretKeySchema.parse(rawKey);
|
|
56777
56797
|
prepared[key] = value === null ? null : {
|
|
56778
|
-
sealedValue: await this.cipher.seal(value),
|
|
56798
|
+
sealedValue: await this.cipher.seal(value, DEPLOYMENT_SCOPE),
|
|
56779
56799
|
fingerprint: fingerprint(value)
|
|
56780
56800
|
};
|
|
56781
56801
|
}
|
|
@@ -56796,7 +56816,7 @@ var DeploymentConfigService = class {
|
|
|
56796
56816
|
const secrets = {};
|
|
56797
56817
|
for (const secret of row.secrets) {
|
|
56798
56818
|
const parsed = DeploymentSecretKeySchema.safeParse(secret.key);
|
|
56799
|
-
if (parsed.success && openedKeys.has(parsed.data)) secrets[parsed.data] = await this.cipher.open(secret.sealedValue);
|
|
56819
|
+
if (parsed.success && openedKeys.has(parsed.data)) secrets[parsed.data] = await this.cipher.open(secret.sealedValue, DEPLOYMENT_SCOPE);
|
|
56800
56820
|
}
|
|
56801
56821
|
return {
|
|
56802
56822
|
schemaVersion: 1,
|
|
@@ -56992,12 +57012,19 @@ var PgDeploymentConfigStore = class extends DeploymentConfigService {
|
|
|
56992
57012
|
*
|
|
56993
57013
|
* Envelope encryption as a service: the data key never leaves Vault; the CP
|
|
56994
57014
|
* sends base64 plaintext to `transit/encrypt/<key>` and stores the returned
|
|
56995
|
-
*
|
|
57015
|
+
* ciphertext, behind this deployment's envelope tag, in the existing text
|
|
57016
|
+
* columns. WHICH key is chosen comes from the {@link SecretScope} the caller
|
|
57017
|
+
* passes — the deployment's key, or `<orgKeyPrefix><orgId>` — so an
|
|
57018
|
+
* organization's material can be destroyed with its key
|
|
57019
|
+
* (docs/designs/per-org-secret-encryption.md).
|
|
56996
57020
|
*
|
|
56997
57021
|
* Contract (pinned on the port):
|
|
56998
|
-
* - `open` PASSES THROUGH values it did not seal (
|
|
56999
|
-
* return as-is): existing plaintext rows keep
|
|
57000
|
-
* next write re-seals them — the rollout is
|
|
57022
|
+
* - `open` PASSES THROUGH values it did not seal (neither the envelope tag nor
|
|
57023
|
+
* a bare `vault:vN:` prefix ⇒ return as-is): existing plaintext rows keep
|
|
57024
|
+
* reading after the flip, and the next write re-seals them — the rollout is
|
|
57025
|
+
* online, no backfill required.
|
|
57026
|
+
* - A value sealed under one scope does NOT open under another: Transit rejects
|
|
57027
|
+
* a ciphertext presented to a different key, which is the cross-tenant fence.
|
|
57001
57028
|
* - No argument or response body is ever logged; errors carry only the HTTP
|
|
57002
57029
|
* status and Vault's `errors[]` strings.
|
|
57003
57030
|
*
|
|
@@ -57008,11 +57035,13 @@ var PgDeploymentConfigStore = class extends DeploymentConfigService {
|
|
|
57008
57035
|
* that exact login wire shape ({role, jwt}), so nothing here is bound to
|
|
57009
57036
|
* Kubernetes — a k8s ServiceAccount token is just the common jwtPath.
|
|
57010
57037
|
*
|
|
57011
|
-
* `open` results are cached in-process keyed by
|
|
57012
|
-
* order eviction): reconcile opens every owned agent's
|
|
57013
|
-
* Transit ciphertexts are stable until re-sealed, so
|
|
57014
|
-
* one network call per distinct value.
|
|
57015
|
-
*
|
|
57038
|
+
* `open` results are cached in-process keyed by key name AND ciphertext
|
|
57039
|
+
* (bounded, insertion-order eviction): reconcile opens every owned agent's
|
|
57040
|
+
* secrets per register, and Transit ciphertexts are stable until re-sealed, so
|
|
57041
|
+
* the cache turns that into one network call per distinct value. The key name
|
|
57042
|
+
* belongs in the cache key because the stored string alone no longer implies
|
|
57043
|
+
* one. `seal` is never cached — Transit returns fresh ciphertext per call by
|
|
57044
|
+
* design.
|
|
57016
57045
|
*/
|
|
57017
57046
|
/** Transit ciphertext is self-describing: `vault:v<key-version>:<base64>`. */
|
|
57018
57047
|
const CIPHERTEXT_RE = /^vault:v\d+:/;
|
|
@@ -57021,6 +57050,7 @@ const RENEW_FRACTION = .8;
|
|
|
57021
57050
|
var VaultTransitSecretCipher = class {
|
|
57022
57051
|
base;
|
|
57023
57052
|
key;
|
|
57053
|
+
orgKeyPrefix;
|
|
57024
57054
|
mount;
|
|
57025
57055
|
namespace;
|
|
57026
57056
|
auth;
|
|
@@ -57033,6 +57063,7 @@ var VaultTransitSecretCipher = class {
|
|
|
57033
57063
|
constructor(opts) {
|
|
57034
57064
|
this.base = `${opts.addr.replace(/\/+$/, "")}/v1`;
|
|
57035
57065
|
this.key = opts.key;
|
|
57066
|
+
this.orgKeyPrefix = opts.orgKeyPrefix;
|
|
57036
57067
|
this.mount = opts.mount ?? "transit";
|
|
57037
57068
|
this.namespace = opts.namespace;
|
|
57038
57069
|
this.auth = opts.auth;
|
|
@@ -57040,19 +57071,31 @@ var VaultTransitSecretCipher = class {
|
|
|
57040
57071
|
this.now = opts.now ?? Date.now;
|
|
57041
57072
|
this.openCacheMax = opts.openCacheMax ?? 5e3;
|
|
57042
57073
|
}
|
|
57043
|
-
|
|
57044
|
-
|
|
57045
|
-
|
|
57046
|
-
|
|
57074
|
+
/**
|
|
57075
|
+
* The key a scope resolves to. Org keys are created lazily by Transit on the
|
|
57076
|
+
* first encrypt (the policy must permit creation on that path); nothing here
|
|
57077
|
+
* is coupled to organization creation.
|
|
57078
|
+
*/
|
|
57079
|
+
keyFor(scope) {
|
|
57080
|
+
return scope.kind === "deployment" ? this.key : `${this.orgKeyPrefix}${scope.orgId}`;
|
|
57047
57081
|
}
|
|
57048
|
-
async
|
|
57049
|
-
|
|
57050
|
-
|
|
57082
|
+
async seal(plaintext, scope) {
|
|
57083
|
+
const data = await this.transit("encrypt", this.keyFor(scope), { plaintext: Buffer.from(plaintext, "utf8").toString("base64") });
|
|
57084
|
+
if (typeof data.ciphertext !== "string") throw new Error("vault transit encrypt: no ciphertext in response");
|
|
57085
|
+
return `${SECRET_ENVELOPE_PREFIX}${data.ciphertext}`;
|
|
57086
|
+
}
|
|
57087
|
+
async open(stored, scope) {
|
|
57088
|
+
const scoped = stored.startsWith(SECRET_ENVELOPE_PREFIX);
|
|
57089
|
+
if (!scoped && !CIPHERTEXT_RE.test(stored)) return stored;
|
|
57090
|
+
const key = scoped ? this.keyFor(scope) : this.key;
|
|
57091
|
+
const ciphertext = scoped ? stored.slice(5) : stored;
|
|
57092
|
+
const cacheKey = `${key}\u0000${ciphertext}`;
|
|
57093
|
+
const hit = this.openCache.get(cacheKey);
|
|
57051
57094
|
if (hit !== void 0) return hit;
|
|
57052
|
-
const data = await this.transit("decrypt", { ciphertext
|
|
57095
|
+
const data = await this.transit("decrypt", key, { ciphertext });
|
|
57053
57096
|
if (typeof data.plaintext !== "string") throw new Error("vault transit decrypt: no plaintext in response");
|
|
57054
57097
|
const value = Buffer.from(data.plaintext, "base64").toString("utf8");
|
|
57055
|
-
this.cacheOpen(
|
|
57098
|
+
this.cacheOpen(cacheKey, value);
|
|
57056
57099
|
return value;
|
|
57057
57100
|
}
|
|
57058
57101
|
cacheOpen(ciphertext, plaintext) {
|
|
@@ -57062,8 +57105,8 @@ var VaultTransitSecretCipher = class {
|
|
|
57062
57105
|
}
|
|
57063
57106
|
this.openCache.set(ciphertext, plaintext);
|
|
57064
57107
|
}
|
|
57065
|
-
async transit(op, body) {
|
|
57066
|
-
const path = `${this.mount}/${op}/${
|
|
57108
|
+
async transit(op, key, body) {
|
|
57109
|
+
const path = `${this.mount}/${op}/${key}`;
|
|
57067
57110
|
let res = await this.post(path, body, await this.token());
|
|
57068
57111
|
if (res.status === 403 && this.auth.method === "jwt") {
|
|
57069
57112
|
this.clientToken = void 0;
|
|
@@ -57139,11 +57182,21 @@ async function describeError(res) {
|
|
|
57139
57182
|
* as Vault Transit stores ciphertext. The composition root selects one provider,
|
|
57140
57183
|
* so every store switches together.
|
|
57141
57184
|
*
|
|
57185
|
+
* Every call carries a {@link SecretScope} naming WHOSE key to use — the
|
|
57186
|
+
* deployment's, or one organization's (docs/designs/per-org-secret-encryption.md).
|
|
57187
|
+
* The scope comes from the caller, never from the stored value.
|
|
57188
|
+
*
|
|
57142
57189
|
* Contract for an encrypting implementation (e.g. Vault Transit):
|
|
57143
|
-
* - `seal` returns a self-describing value
|
|
57190
|
+
* - `seal` returns a self-describing value carrying the envelope version
|
|
57191
|
+
* (`acv1:` + Transit's own `vault:v1:…`).
|
|
57144
57192
|
* - `open` MUST pass through values it did not seal (no recognizable prefix ⇒
|
|
57145
57193
|
* return as-is): existing plaintext rows keep working, and re-sealing on the
|
|
57146
57194
|
* next write migrates them lazily — the flip is online, no backfill required.
|
|
57195
|
+
* Values sealed before scoping existed (a bare `vault:vN:`) open under the
|
|
57196
|
+
* deployment key whatever `scope` says; only the envelope-tagged arm is
|
|
57197
|
+
* scoped.
|
|
57198
|
+
* - A value sealed under one scope MUST NOT open under another. Failing closed
|
|
57199
|
+
* there is the point of passing the scope in.
|
|
57147
57200
|
* - Neither side ever logs its argument.
|
|
57148
57201
|
*
|
|
57149
57202
|
* This is NOT the C5 `SecretsProvider` lease broker (`providers/provider.ts`),
|
|
@@ -57184,6 +57237,7 @@ function makeSecretCipher(config) {
|
|
|
57184
57237
|
return new VaultTransitSecretCipher({
|
|
57185
57238
|
addr: config.VAULT_ADDR,
|
|
57186
57239
|
key: config.VAULT_TRANSIT_KEY,
|
|
57240
|
+
orgKeyPrefix: effectiveOrgKeyPrefix(config.VAULT_TRANSIT_KEY, config.VAULT_TRANSIT_ORG_KEY_PREFIX),
|
|
57187
57241
|
mount: config.VAULT_TRANSIT_MOUNT,
|
|
57188
57242
|
...config.VAULT_NAMESPACE ? { namespace: config.VAULT_NAMESPACE } : {},
|
|
57189
57243
|
auth
|